Initial commit: LACA parking management system

This commit is contained in:
2025-08-13 10:05:36 +07:00
commit 8b07467b61
275 changed files with 66828 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommentController } from './comment.controller';
import { CommentService } from './comment.service';
describe('CommentController', () => {
let controller: CommentController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CommentController],
providers: [CommentService],
}).compile();
controller = module.get<CommentController>(CommentController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,67 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Query,
} from '@nestjs/common';
import { CommentService } from './comment.service';
import { CreateCommentDto } from './dto/create-comment.dto';
import { UpdateCommentDto } from './dto/update-comment.dto';
import { AuthPayload, AuthUser } from 'src/auth/decorators/auth.payload';
import { SearchCommentDto } from './dto/search-comment,dto';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ApiTags('Comment API')
@ApiBearerAuth()
@Controller('comment')
export class CommentController {
constructor(private readonly commentService: CommentService) { }
@Post()
create(
@Body() createCommentDto: CreateCommentDto,
@AuthUser() user: AuthPayload,
) {
return this.commentService.create(createCommentDto, user);
}
@Get('by-slot/:slot_id')
findAll(
@Param('slot_id') slot_id: number,
@Query() query: SearchCommentDto,
@AuthUser() user: AuthPayload,
) {
return this.commentService.findAll(+slot_id, query, user);
}
@Post('react/:id')
react(
@Param('id') slot_id: number,
// @Query() query: SearchCommentDto,
@AuthUser() user: AuthPayload,
) {
return this.commentService.reactComment(+slot_id, user);
}
@Get(':id')
findOne(@Param('id') id: number) {
return this.commentService.findOne(+id);
}
@Patch(':id')
update(
@Param('id') id: number,
@Body() updateCommentDto: UpdateCommentDto,
@AuthUser() user: AuthPayload,
) {
return this.commentService.update(+id, updateCommentDto, user);
}
@Delete(':id')
remove(@Param('id') id: number, @AuthUser() user: AuthPayload,) {
return this.commentService.remove(+id, user);
}
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { CommentService } from './comment.service';
import { CommentController } from './comment.controller';
import { PrismaModule } from 'src/prisma/prisma.module';
@Module({
controllers: [CommentController],
providers: [CommentService],
imports: [PrismaModule],
exports: [CommentService],
})
export class CommentModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommentService } from './comment.service';
describe('CommentService', () => {
let service: CommentService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CommentService],
}).compile();
service = module.get<CommentService>(CommentService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,172 @@
import { Logger, Injectable, BadRequestException } from '@nestjs/common';
import { CreateCommentDto } from './dto/create-comment.dto';
import { UpdateCommentDto } from './dto/update-comment.dto';
import { PrismaService } from 'src/prisma/prisma.service';
import { AuthPayload } from 'src/auth/decorators/auth.payload';
import { COMMENT_NOTFOUND, SLOT_NOTFOUND } from 'src/share/eCode';
import { SearchCommentDto } from './dto/search-comment,dto';
import { eUserType } from 'src/common/auth/type';
@Injectable()
export class CommentService {
private readonly LOG = new Logger(CommentService.name);
constructor(private prisma: PrismaService) { }
async find_comment_by_user(id: number, user: AuthPayload) {
const comment = await this.prisma.comment.findFirst({
where: {
id,
userId: +user.id,
},
});
if (comment) return comment;
throw new BadRequestException(COMMENT_NOTFOUND);
}
async create(createCommentDto: CreateCommentDto, user: AuthPayload) {
try {
// check slot
const userCreate = {};
if (user.type == eUserType.TENANT) {
userCreate['tenantId'] = +user.id;
} else {
userCreate['userId'] = +user.id;
}
const slot = await this.prisma.slot.findFirst({
where: { id: createCommentDto.slot_id },
});
if (!slot) {
throw new BadRequestException(SLOT_NOTFOUND);
}
await this.prisma.$transaction([
this.prisma.comment.create({
data: { ...createCommentDto, ...userCreate },
}),
]);
return true;
} catch (e) {
this.LOG.error(e);
throw e;
}
}
async findAll(
slot_id: number,
query: SearchCommentDto,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
user: AuthPayload = null,
) {
try {
const { pageSize, pageNumber, sort, search } = query;
const take = pageSize;
const skip = (pageNumber - 1) * pageSize;
const condition = {
where: { slot_id },
skip,
take,
// orderBy: sort,
include: {
tenant: true,
user: true,
_count: { select: { likes: true } },
},
};
const [comments, total] = await this.prisma.$transaction([
this.prisma.comment.findMany(condition),
this.prisma.comment.count({ where: condition.where }),
]);
return {
comments: comments?.map((e) => {
delete e?.user?.password;
delete e?.user?.username;
delete e?.tenant?.username;
delete e?.tenant?.username;
return {
...e,
};
}),
total,
};
} catch (error) {
this.LOG.error(error);
throw error;
}
}
async findOne(id: number, user: AuthPayload = null) {
try {
const comment = await this.prisma.comment.findFirst({
where: {
id,
},
});
if (comment) return comment;
throw new BadRequestException(COMMENT_NOTFOUND);
} catch (error) {
this.LOG.error(error);
throw error;
}
}
async update(
id: number,
updateCommentDto: UpdateCommentDto,
user: AuthPayload,
) {
try {
await this.find_comment_by_user(id, user);
await this.prisma.$transaction([
this.prisma.comment.update({
where: { id },
data: {
content: updateCommentDto.content,
},
}),
]);
return `This action updates a #${id} comment`;
} catch (error) {
this.LOG.error(error);
throw error;
}
}
async remove(id: number, user: AuthPayload) {
try {
await this.find_comment_by_user(id, user);
await this.prisma.$transaction([
this.prisma.comment.deleteMany({
where: { id },
}),
]);
} catch (error) {
this.LOG.error(error);
throw error;
}
}
async reactComment(id: number, user: AuthPayload = null) {
try {
const comment = await this.find_comment_by_user(id, user);
const like = await this.prisma.like.findFirst({
where: {
commentId: comment.id,
userId: +user.id,
},
});
if (like) {
await this.prisma.like.delete({ where: { id: +like.id } });
} else {
await this.prisma.like.create({
data: {
commentId: comment.id,
userId: +user.id,
},
});
}
return true;
} catch (error) {
this.LOG.error(error);
throw error;
}
}
}

View File

@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import {
IsNotEmpty,
IsNumber,
IsString,
} from 'class-validator';
export class CreateCommentDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
content: string;
@ApiProperty()
@IsNumber()
@IsNotEmpty()
slot_id: number
}

View File

@@ -0,0 +1,6 @@
import { PagingReqDto } from 'src/common/dto/search';
export class SearchCommentDto extends PagingReqDto {
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateCommentDto } from './create-comment.dto';
export class UpdateCommentDto extends PartialType(CreateCommentDto) {}

View File

@@ -0,0 +1 @@
export class Comment {}