Merge branch 'Paritosh' of http://git.wdipl.com/Akshay.Mayekar/Klc_backend
This commit is contained in:
@@ -4,6 +4,12 @@ import { ThrottlerModule } from '@nestjs/throttler';
|
|||||||
import { PrismaModule } from './common/database/prisma.module';
|
import { PrismaModule } from './common/database/prisma.module';
|
||||||
import { AuthModule } from './modules/auth/auth.module';
|
import { AuthModule } from './modules/auth/auth.module';
|
||||||
import { BlogsModule } from './modules/blog/blogs.module';
|
import { BlogsModule } from './modules/blog/blogs.module';
|
||||||
|
import { FaqModule } from './modules/faq/faq.module';
|
||||||
|
import { PodcastsModule } from './modules/podcasts/podcasts.module';
|
||||||
|
import { CaseStudiesModule } from './modules/case-studies/case-studies.module';
|
||||||
|
import { KlcArchiveModule } from './modules/klc-archive/klc-archive.module';
|
||||||
|
import { ReadingMaterialsModule } from './modules/reading-materials/reading-materials.module';
|
||||||
|
import { TrainingMaterialsModule } from './modules/training-materials/training-materials.module';
|
||||||
import { WebcastsModule } from './modules/webcast/webcasts.module';
|
import { WebcastsModule } from './modules/webcast/webcasts.module';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
@@ -30,6 +36,12 @@ import { AppService } from './app.service';
|
|||||||
// Feature modules
|
// Feature modules
|
||||||
AuthModule,
|
AuthModule,
|
||||||
BlogsModule,
|
BlogsModule,
|
||||||
|
FaqModule,
|
||||||
|
PodcastsModule,
|
||||||
|
CaseStudiesModule,
|
||||||
|
KlcArchiveModule,
|
||||||
|
ReadingMaterialsModule,
|
||||||
|
TrainingMaterialsModule,
|
||||||
WebcastsModule,
|
WebcastsModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
|
|||||||
12
src/modules/case-studies/case-studies.module.ts
Normal file
12
src/modules/case-studies/case-studies.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { CaseStudiesController } from './controllers/case-studies.controller';
|
||||||
|
import { CaseStudiesService } from './services/case-studies.service';
|
||||||
|
import { PrismaModule } from '../../common/database/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [CaseStudiesController],
|
||||||
|
providers: [CaseStudiesService],
|
||||||
|
exports: [CaseStudiesService],
|
||||||
|
})
|
||||||
|
export class CaseStudiesModule {}
|
||||||
110
src/modules/case-studies/controllers/case-studies.controller.ts
Normal file
110
src/modules/case-studies/controllers/case-studies.controller.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
Query,
|
||||||
|
ParseIntPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { CaseStudiesService } from '../services/case-studies.service';
|
||||||
|
import { CreateCaseStudyDto } from '../dto/create-case-study.dto';
|
||||||
|
import { UpdateCaseStudyDto } from '../dto/update-case-study.dto';
|
||||||
|
import { CaseStudyResponseDto } from '../dto/case-study-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
|
||||||
|
@ApiTags('case-studies')
|
||||||
|
@Controller('case-studies')
|
||||||
|
export class CaseStudiesController {
|
||||||
|
constructor(private readonly caseStudiesService: CaseStudiesService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Create a new case study' })
|
||||||
|
@ApiResponse({ status: 201, description: 'Case study created successfully', type: CaseStudyResponseDto })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async create(@Body() createCaseStudyDto: CreateCaseStudyDto): Promise<CaseStudyResponseDto> {
|
||||||
|
return this.caseStudiesService.create(createCaseStudyDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get all case studies with pagination' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Case studies retrieved successfully' })
|
||||||
|
async findAll(@Query() paginationDto: PaginationDto) {
|
||||||
|
return this.caseStudiesService.findAll(paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/title')
|
||||||
|
@ApiOperation({ summary: 'Search case studies by title' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Case studies retrieved successfully' })
|
||||||
|
async searchByTitle(
|
||||||
|
@Query('q') title: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.caseStudiesService.searchByTitle(title, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/description')
|
||||||
|
@ApiOperation({ summary: 'Search case studies by description' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Case studies retrieved successfully' })
|
||||||
|
async searchByDescription(
|
||||||
|
@Query('q') description: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.caseStudiesService.searchByDescription(description, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('tag/:tag')
|
||||||
|
@ApiOperation({ summary: 'Get case studies by tag' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Case studies retrieved successfully' })
|
||||||
|
async findByTag(
|
||||||
|
@Param('tag') tag: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.caseStudiesService.findByTag(tag, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get case study by ID' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Case study retrieved successfully', type: CaseStudyResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'Case study not found' })
|
||||||
|
async findOne(@Param('id', ParseIntPipe) id: number): Promise<CaseStudyResponseDto> {
|
||||||
|
return this.caseStudiesService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Update case study' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Case study updated successfully', type: CaseStudyResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'Case study not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async update(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() updateCaseStudyDto: UpdateCaseStudyDto,
|
||||||
|
): Promise<CaseStudyResponseDto> {
|
||||||
|
return this.caseStudiesService.update(id, updateCaseStudyDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Delete case study (soft delete)' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Case study deleted successfully' })
|
||||||
|
@ApiResponse({ status: 404, description: 'Case study not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async remove(@Param('id', ParseIntPipe) id: number): Promise<{ message: string }> {
|
||||||
|
await this.caseStudiesService.remove(id);
|
||||||
|
return { message: 'Case study deleted successfully' };
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/modules/case-studies/dto/case-study-response.dto.ts
Normal file
24
src/modules/case-studies/dto/case-study-response.dto.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CaseStudyResponseDto {
|
||||||
|
@ApiProperty({ description: 'Case study ID' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Case study title' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Case study description' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Case study file URL' })
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Case study tags', type: [String] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Creation date' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Last update date' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
22
src/modules/case-studies/dto/create-case-study.dto.ts
Normal file
22
src/modules/case-studies/dto/create-case-study.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { IsString, IsOptional, IsArray, IsUrl } from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateCaseStudyDto {
|
||||||
|
@ApiProperty({ description: 'Case study title' })
|
||||||
|
@IsString()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Case study description' })
|
||||||
|
@IsString()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Case study file URL' })
|
||||||
|
@IsUrl()
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'Case study tags', type: [String] })
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
4
src/modules/case-studies/dto/update-case-study.dto.ts
Normal file
4
src/modules/case-studies/dto/update-case-study.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateCaseStudyDto } from './create-case-study.dto';
|
||||||
|
|
||||||
|
export class UpdateCaseStudyDto extends PartialType(CreateCaseStudyDto) {}
|
||||||
223
src/modules/case-studies/services/case-studies.service.ts
Normal file
223
src/modules/case-studies/services/case-studies.service.ts
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../../../common/database/prisma.service';
|
||||||
|
import { CreateCaseStudyDto } from '../dto/create-case-study.dto';
|
||||||
|
import { UpdateCaseStudyDto } from '../dto/update-case-study.dto';
|
||||||
|
import { CaseStudyResponseDto } from '../dto/case-study-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
import { paginate } from '../../../common/utils/pagination.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CaseStudiesService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async create(createCaseStudyDto: CreateCaseStudyDto): Promise<CaseStudyResponseDto> {
|
||||||
|
const caseStudy = await this.prisma.caseStudies.create({
|
||||||
|
data: {
|
||||||
|
...createCaseStudyDto,
|
||||||
|
tags: createCaseStudyDto.tags || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(caseStudy);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [caseStudies, total] = await Promise.all([
|
||||||
|
this.prisma.caseStudies.findMany({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.caseStudies.count({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedCaseStudies = caseStudies.map(caseStudy => this.mapToResponseDto(caseStudy));
|
||||||
|
|
||||||
|
return paginate(mappedCaseStudies, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: number): Promise<CaseStudyResponseDto> {
|
||||||
|
const caseStudy = await this.prisma.caseStudies.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!caseStudy) {
|
||||||
|
throw new NotFoundException(`Case study with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapToResponseDto(caseStudy);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, updateCaseStudyDto: UpdateCaseStudyDto): Promise<CaseStudyResponseDto> {
|
||||||
|
const existingCaseStudy = await this.prisma.caseStudies.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingCaseStudy) {
|
||||||
|
throw new NotFoundException(`Case study with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const caseStudy = await this.prisma.caseStudies.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...updateCaseStudyDto,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(caseStudy);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<void> {
|
||||||
|
const existingCaseStudy = await this.prisma.caseStudies.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingCaseStudy) {
|
||||||
|
throw new NotFoundException(`Case study with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.caseStudies.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByTag(tag: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [caseStudies, total] = await Promise.all([
|
||||||
|
this.prisma.caseStudies.findMany({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.caseStudies.count({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedCaseStudies = caseStudies.map(caseStudy => this.mapToResponseDto(caseStudy));
|
||||||
|
|
||||||
|
return paginate(mappedCaseStudies, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByTitle(title: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [caseStudies, total] = await Promise.all([
|
||||||
|
this.prisma.caseStudies.findMany({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.caseStudies.count({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedCaseStudies = caseStudies.map(caseStudy => this.mapToResponseDto(caseStudy));
|
||||||
|
|
||||||
|
return paginate(mappedCaseStudies, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByDescription(description: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [caseStudies, total] = await Promise.all([
|
||||||
|
this.prisma.caseStudies.findMany({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.caseStudies.count({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedCaseStudies = caseStudies.map(caseStudy => this.mapToResponseDto(caseStudy));
|
||||||
|
|
||||||
|
return paginate(mappedCaseStudies, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToResponseDto(caseStudy: any): CaseStudyResponseDto {
|
||||||
|
return {
|
||||||
|
id: caseStudy.id,
|
||||||
|
title: caseStudy.title,
|
||||||
|
description: caseStudy.description,
|
||||||
|
fileUrl: caseStudy.fileUrl,
|
||||||
|
tags: caseStudy.tags,
|
||||||
|
createdAt: caseStudy.createdAt,
|
||||||
|
updatedAt: caseStudy.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/modules/faq/controllers/faq.controller.ts
Normal file
114
src/modules/faq/controllers/faq.controller.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
Query,
|
||||||
|
ParseIntPipe,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import { FaqService } from '../services/faq.service';
|
||||||
|
import { CreateFaqDto } from '../dto/create-faq.dto';
|
||||||
|
import { UpdateFaqDto } from '../dto/update-faq.dto';
|
||||||
|
import { FaqResponseDto } from '../dto/faq-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||||
|
import { RolesGuard } from '../../../common/guards/roles.guard';
|
||||||
|
import { Roles } from '../../../common/decorators/roles.decorator';
|
||||||
|
|
||||||
|
@ApiTags('faq')
|
||||||
|
@Controller('faq')
|
||||||
|
export class FaqController {
|
||||||
|
constructor(private readonly faqService: FaqService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Create a new FAQ' })
|
||||||
|
@ApiResponse({ status: 201, description: 'FAQ created successfully', type: FaqResponseDto })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async create(@Body() createFaqDto: CreateFaqDto): Promise<FaqResponseDto> {
|
||||||
|
return this.faqService.create(createFaqDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get all FAQs with pagination' })
|
||||||
|
@ApiResponse({ status: 200, description: 'FAQs retrieved successfully' })
|
||||||
|
async findAll(@Query() paginationDto: PaginationDto) {
|
||||||
|
return this.faqService.findAll(paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('category/:category')
|
||||||
|
@ApiOperation({ summary: 'Get FAQs by category' })
|
||||||
|
@ApiResponse({ status: 200, description: 'FAQs retrieved successfully' })
|
||||||
|
async findByCategory(
|
||||||
|
@Param('category') category: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.faqService.findByCategory(category, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('tag/:tag')
|
||||||
|
@ApiOperation({ summary: 'Get FAQs by tag' })
|
||||||
|
@ApiResponse({ status: 200, description: 'FAQs retrieved successfully' })
|
||||||
|
async findByTag(
|
||||||
|
@Param('tag') tag: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.faqService.findByTag(tag, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('global-tag/:globalTag')
|
||||||
|
@ApiOperation({ summary: 'Get FAQs by global tag' })
|
||||||
|
@ApiResponse({ status: 200, description: 'FAQs retrieved successfully' })
|
||||||
|
async findByGlobalTag(
|
||||||
|
@Param('globalTag') globalTag: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.faqService.findByGlobalTag(globalTag, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get FAQ by ID' })
|
||||||
|
@ApiResponse({ status: 200, description: 'FAQ retrieved successfully', type: FaqResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'FAQ not found' })
|
||||||
|
async findOne(@Param('id', ParseIntPipe) id: number): Promise<FaqResponseDto> {
|
||||||
|
return this.faqService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Update FAQ' })
|
||||||
|
@ApiResponse({ status: 200, description: 'FAQ updated successfully', type: FaqResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'FAQ not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async update(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() updateFaqDto: UpdateFaqDto,
|
||||||
|
): Promise<FaqResponseDto> {
|
||||||
|
return this.faqService.update(id, updateFaqDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Delete FAQ (soft delete)' })
|
||||||
|
@ApiResponse({ status: 200, description: 'FAQ deleted successfully' })
|
||||||
|
@ApiResponse({ status: 404, description: 'FAQ not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async remove(@Param('id', ParseIntPipe) id: number): Promise<{ message: string }> {
|
||||||
|
await this.faqService.remove(id);
|
||||||
|
return { message: 'FAQ deleted successfully' };
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/modules/faq/dto/create-faq.dto.ts
Normal file
29
src/modules/faq/dto/create-faq.dto.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { IsString, IsOptional, IsArray } from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateFaqDto {
|
||||||
|
@ApiProperty({ description: 'FAQ question' })
|
||||||
|
@IsString()
|
||||||
|
question: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'FAQ category' })
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
category?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'FAQ answer' })
|
||||||
|
@IsString()
|
||||||
|
answer: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'FAQ tags', type: [String] })
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
tags?: string[];
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'Global tags', type: [String] })
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
globalTag?: string[];
|
||||||
|
}
|
||||||
27
src/modules/faq/dto/faq-response.dto.ts
Normal file
27
src/modules/faq/dto/faq-response.dto.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class FaqResponseDto {
|
||||||
|
@ApiProperty({ description: 'FAQ ID' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'FAQ question' })
|
||||||
|
question: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'FAQ category' })
|
||||||
|
category?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'FAQ answer' })
|
||||||
|
answer: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'FAQ tags', type: [String] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Global tags', type: [String] })
|
||||||
|
globalTag: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Creation date' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Last update date' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
4
src/modules/faq/dto/update-faq.dto.ts
Normal file
4
src/modules/faq/dto/update-faq.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateFaqDto } from './create-faq.dto';
|
||||||
|
|
||||||
|
export class UpdateFaqDto extends PartialType(CreateFaqDto) {}
|
||||||
12
src/modules/faq/faq.module.ts
Normal file
12
src/modules/faq/faq.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { FaqController } from './controllers/faq.controller';
|
||||||
|
import { FaqService } from './services/faq.service';
|
||||||
|
import { PrismaModule } from '../../common/database/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [FaqController],
|
||||||
|
providers: [FaqService],
|
||||||
|
exports: [FaqService],
|
||||||
|
})
|
||||||
|
export class FaqModule {}
|
||||||
217
src/modules/faq/services/faq.service.ts
Normal file
217
src/modules/faq/services/faq.service.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../../../common/database/prisma.service';
|
||||||
|
import { CreateFaqDto } from '../dto/create-faq.dto';
|
||||||
|
import { UpdateFaqDto } from '../dto/update-faq.dto';
|
||||||
|
import { FaqResponseDto } from '../dto/faq-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
import { paginate } from '../../../common/utils/pagination.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FaqService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async create(createFaqDto: CreateFaqDto): Promise<FaqResponseDto> {
|
||||||
|
const faq = await this.prisma.fAQ.create({
|
||||||
|
data: {
|
||||||
|
...createFaqDto,
|
||||||
|
tags: createFaqDto.tags || [],
|
||||||
|
globalTag: createFaqDto.globalTag || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(faq);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [faqs, total] = await Promise.all([
|
||||||
|
this.prisma.fAQ.findMany({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.fAQ.count({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedFaqs = faqs.map(faq => this.mapToResponseDto(faq));
|
||||||
|
|
||||||
|
return paginate(mappedFaqs, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: number): Promise<FaqResponseDto> {
|
||||||
|
const faq = await this.prisma.fAQ.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!faq) {
|
||||||
|
throw new NotFoundException(`FAQ with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapToResponseDto(faq);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, updateFaqDto: UpdateFaqDto): Promise<FaqResponseDto> {
|
||||||
|
const existingFaq = await this.prisma.fAQ.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingFaq) {
|
||||||
|
throw new NotFoundException(`FAQ with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const faq = await this.prisma.fAQ.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...updateFaqDto,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(faq);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<void> {
|
||||||
|
const existingFaq = await this.prisma.fAQ.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingFaq) {
|
||||||
|
throw new NotFoundException(`FAQ with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.fAQ.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByCategory(category: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [faqs, total] = await Promise.all([
|
||||||
|
this.prisma.fAQ.findMany({
|
||||||
|
where: {
|
||||||
|
category,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.fAQ.count({
|
||||||
|
where: {
|
||||||
|
category,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedFaqs = faqs.map(faq => this.mapToResponseDto(faq));
|
||||||
|
|
||||||
|
return paginate(mappedFaqs, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByTag(tag: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [faqs, total] = await Promise.all([
|
||||||
|
this.prisma.fAQ.findMany({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.fAQ.count({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedFaqs = faqs.map(faq => this.mapToResponseDto(faq));
|
||||||
|
|
||||||
|
return paginate(mappedFaqs, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByGlobalTag(globalTag: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [faqs, total] = await Promise.all([
|
||||||
|
this.prisma.fAQ.findMany({
|
||||||
|
where: {
|
||||||
|
globalTag: {
|
||||||
|
has: globalTag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.fAQ.count({
|
||||||
|
where: {
|
||||||
|
globalTag: {
|
||||||
|
has: globalTag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedFaqs = faqs.map(faq => this.mapToResponseDto(faq));
|
||||||
|
|
||||||
|
return paginate(mappedFaqs, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToResponseDto(faq: any): FaqResponseDto {
|
||||||
|
return {
|
||||||
|
id: faq.id,
|
||||||
|
question: faq.question,
|
||||||
|
category: faq.category,
|
||||||
|
answer: faq.answer,
|
||||||
|
tags: faq.tags,
|
||||||
|
globalTag: faq.globalTag,
|
||||||
|
createdAt: faq.createdAt,
|
||||||
|
updatedAt: faq.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/modules/klc-archive/controllers/klc-archive.controller.ts
Normal file
110
src/modules/klc-archive/controllers/klc-archive.controller.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
Query,
|
||||||
|
ParseIntPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { KlcArchiveService } from '../services/klc-archive.service';
|
||||||
|
import { CreateKlcArchiveDto } from '../dto/create-klc-archive.dto';
|
||||||
|
import { UpdateKlcArchiveDto } from '../dto/update-klc-archive.dto';
|
||||||
|
import { KlcArchiveResponseDto } from '../dto/klc-archive-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
|
||||||
|
@ApiTags('klc-archive')
|
||||||
|
@Controller('klc-archive')
|
||||||
|
export class KlcArchiveController {
|
||||||
|
constructor(private readonly klcArchiveService: KlcArchiveService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Create a new KLC Archive entry' })
|
||||||
|
@ApiResponse({ status: 201, description: 'KLC Archive created successfully', type: KlcArchiveResponseDto })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async create(@Body() createKlcArchiveDto: CreateKlcArchiveDto): Promise<KlcArchiveResponseDto> {
|
||||||
|
return this.klcArchiveService.create(createKlcArchiveDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get all KLC Archive entries with pagination' })
|
||||||
|
@ApiResponse({ status: 200, description: 'KLC Archive entries retrieved successfully' })
|
||||||
|
async findAll(@Query() paginationDto: PaginationDto) {
|
||||||
|
return this.klcArchiveService.findAll(paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/title')
|
||||||
|
@ApiOperation({ summary: 'Search KLC Archive entries by title' })
|
||||||
|
@ApiResponse({ status: 200, description: 'KLC Archive entries retrieved successfully' })
|
||||||
|
async searchByTitle(
|
||||||
|
@Query('q') title: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.klcArchiveService.searchByTitle(title, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/description')
|
||||||
|
@ApiOperation({ summary: 'Search KLC Archive entries by description' })
|
||||||
|
@ApiResponse({ status: 200, description: 'KLC Archive entries retrieved successfully' })
|
||||||
|
async searchByDescription(
|
||||||
|
@Query('q') description: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.klcArchiveService.searchByDescription(description, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('tag/:tag')
|
||||||
|
@ApiOperation({ summary: 'Get KLC Archive entries by tag' })
|
||||||
|
@ApiResponse({ status: 200, description: 'KLC Archive entries retrieved successfully' })
|
||||||
|
async findByTag(
|
||||||
|
@Param('tag') tag: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.klcArchiveService.findByTag(tag, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get KLC Archive entry by ID' })
|
||||||
|
@ApiResponse({ status: 200, description: 'KLC Archive entry retrieved successfully', type: KlcArchiveResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'KLC Archive entry not found' })
|
||||||
|
async findOne(@Param('id', ParseIntPipe) id: number): Promise<KlcArchiveResponseDto> {
|
||||||
|
return this.klcArchiveService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Update KLC Archive entry' })
|
||||||
|
@ApiResponse({ status: 200, description: 'KLC Archive entry updated successfully', type: KlcArchiveResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'KLC Archive entry not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async update(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() updateKlcArchiveDto: UpdateKlcArchiveDto,
|
||||||
|
): Promise<KlcArchiveResponseDto> {
|
||||||
|
return this.klcArchiveService.update(id, updateKlcArchiveDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Delete KLC Archive entry (soft delete)' })
|
||||||
|
@ApiResponse({ status: 200, description: 'KLC Archive entry deleted successfully' })
|
||||||
|
@ApiResponse({ status: 404, description: 'KLC Archive entry not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async remove(@Param('id', ParseIntPipe) id: number): Promise<{ message: string }> {
|
||||||
|
await this.klcArchiveService.remove(id);
|
||||||
|
return { message: 'KLC Archive entry deleted successfully' };
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/modules/klc-archive/dto/create-klc-archive.dto.ts
Normal file
22
src/modules/klc-archive/dto/create-klc-archive.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { IsString, IsOptional, IsArray, IsUrl } from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateKlcArchiveDto {
|
||||||
|
@ApiProperty({ description: 'KLC Archive title' })
|
||||||
|
@IsString()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'KLC Archive description' })
|
||||||
|
@IsString()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'KLC Archive file URL' })
|
||||||
|
@IsUrl()
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'KLC Archive tags', type: [String] })
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
24
src/modules/klc-archive/dto/klc-archive-response.dto.ts
Normal file
24
src/modules/klc-archive/dto/klc-archive-response.dto.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class KlcArchiveResponseDto {
|
||||||
|
@ApiProperty({ description: 'KLC Archive ID' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'KLC Archive title' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'KLC Archive description' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'KLC Archive file URL' })
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'KLC Archive tags', type: [String] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Creation date' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Last update date' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
4
src/modules/klc-archive/dto/update-klc-archive.dto.ts
Normal file
4
src/modules/klc-archive/dto/update-klc-archive.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateKlcArchiveDto } from './create-klc-archive.dto';
|
||||||
|
|
||||||
|
export class UpdateKlcArchiveDto extends PartialType(CreateKlcArchiveDto) {}
|
||||||
12
src/modules/klc-archive/klc-archive.module.ts
Normal file
12
src/modules/klc-archive/klc-archive.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { KlcArchiveController } from './controllers/klc-archive.controller';
|
||||||
|
import { KlcArchiveService } from './services/klc-archive.service';
|
||||||
|
import { PrismaModule } from '../../common/database/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [KlcArchiveController],
|
||||||
|
providers: [KlcArchiveService],
|
||||||
|
exports: [KlcArchiveService],
|
||||||
|
})
|
||||||
|
export class KlcArchiveModule {}
|
||||||
223
src/modules/klc-archive/services/klc-archive.service.ts
Normal file
223
src/modules/klc-archive/services/klc-archive.service.ts
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../../../common/database/prisma.service';
|
||||||
|
import { CreateKlcArchiveDto } from '../dto/create-klc-archive.dto';
|
||||||
|
import { UpdateKlcArchiveDto } from '../dto/update-klc-archive.dto';
|
||||||
|
import { KlcArchiveResponseDto } from '../dto/klc-archive-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
import { paginate } from '../../../common/utils/pagination.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class KlcArchiveService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async create(createKlcArchiveDto: CreateKlcArchiveDto): Promise<KlcArchiveResponseDto> {
|
||||||
|
const klcArchive = await this.prisma.klcArchive.create({
|
||||||
|
data: {
|
||||||
|
...createKlcArchiveDto,
|
||||||
|
tags: createKlcArchiveDto.tags || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(klcArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [klcArchives, total] = await Promise.all([
|
||||||
|
this.prisma.klcArchive.findMany({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.klcArchive.count({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedKlcArchives = klcArchives.map(klcArchive => this.mapToResponseDto(klcArchive));
|
||||||
|
|
||||||
|
return paginate(mappedKlcArchives, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: number): Promise<KlcArchiveResponseDto> {
|
||||||
|
const klcArchive = await this.prisma.klcArchive.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!klcArchive) {
|
||||||
|
throw new NotFoundException(`KLC Archive with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapToResponseDto(klcArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, updateKlcArchiveDto: UpdateKlcArchiveDto): Promise<KlcArchiveResponseDto> {
|
||||||
|
const existingKlcArchive = await this.prisma.klcArchive.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingKlcArchive) {
|
||||||
|
throw new NotFoundException(`KLC Archive with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const klcArchive = await this.prisma.klcArchive.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...updateKlcArchiveDto,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(klcArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<void> {
|
||||||
|
const existingKlcArchive = await this.prisma.klcArchive.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingKlcArchive) {
|
||||||
|
throw new NotFoundException(`KLC Archive with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.klcArchive.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByTag(tag: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [klcArchives, total] = await Promise.all([
|
||||||
|
this.prisma.klcArchive.findMany({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.klcArchive.count({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedKlcArchives = klcArchives.map(klcArchive => this.mapToResponseDto(klcArchive));
|
||||||
|
|
||||||
|
return paginate(mappedKlcArchives, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByTitle(title: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [klcArchives, total] = await Promise.all([
|
||||||
|
this.prisma.klcArchive.findMany({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.klcArchive.count({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedKlcArchives = klcArchives.map(klcArchive => this.mapToResponseDto(klcArchive));
|
||||||
|
|
||||||
|
return paginate(mappedKlcArchives, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByDescription(description: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [klcArchives, total] = await Promise.all([
|
||||||
|
this.prisma.klcArchive.findMany({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.klcArchive.count({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedKlcArchives = klcArchives.map(klcArchive => this.mapToResponseDto(klcArchive));
|
||||||
|
|
||||||
|
return paginate(mappedKlcArchives, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToResponseDto(klcArchive: any): KlcArchiveResponseDto {
|
||||||
|
return {
|
||||||
|
id: klcArchive.id,
|
||||||
|
title: klcArchive.title,
|
||||||
|
description: klcArchive.description,
|
||||||
|
fileUrl: klcArchive.fileUrl,
|
||||||
|
tags: klcArchive.tags,
|
||||||
|
createdAt: klcArchive.createdAt,
|
||||||
|
updatedAt: klcArchive.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/modules/podcasts/controllers/podcasts.controller.ts
Normal file
114
src/modules/podcasts/controllers/podcasts.controller.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
Query,
|
||||||
|
ParseIntPipe,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import { PodcastsService } from '../services/podcasts.service';
|
||||||
|
import { CreatePodcastDto } from '../dto/create-podcast.dto';
|
||||||
|
import { UpdatePodcastDto } from '../dto/update-podcast.dto';
|
||||||
|
import { PodcastResponseDto } from '../dto/podcast-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||||
|
import { RolesGuard } from '../../../common/guards/roles.guard';
|
||||||
|
import { Roles } from '../../../common/decorators/roles.decorator';
|
||||||
|
|
||||||
|
@ApiTags('podcasts')
|
||||||
|
@Controller('podcasts')
|
||||||
|
export class PodcastsController {
|
||||||
|
constructor(private readonly podcastsService: PodcastsService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Create a new podcast' })
|
||||||
|
@ApiResponse({ status: 201, description: 'Podcast created successfully', type: PodcastResponseDto })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async create(@Body() createPodcastDto: CreatePodcastDto): Promise<PodcastResponseDto> {
|
||||||
|
return this.podcastsService.create(createPodcastDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get all podcasts with pagination' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Podcasts retrieved successfully' })
|
||||||
|
async findAll(@Query() paginationDto: PaginationDto) {
|
||||||
|
return this.podcastsService.findAll(paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/title')
|
||||||
|
@ApiOperation({ summary: 'Search podcasts by title' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Podcasts retrieved successfully' })
|
||||||
|
async searchByTitle(
|
||||||
|
@Query('q') title: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.podcastsService.searchByTitle(title, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/description')
|
||||||
|
@ApiOperation({ summary: 'Search podcasts by description' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Podcasts retrieved successfully' })
|
||||||
|
async searchByDescription(
|
||||||
|
@Query('q') description: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.podcastsService.searchByDescription(description, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('tag/:tag')
|
||||||
|
@ApiOperation({ summary: 'Get podcasts by tag' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Podcasts retrieved successfully' })
|
||||||
|
async findByTag(
|
||||||
|
@Param('tag') tag: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.podcastsService.findByTag(tag, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get podcast by ID' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Podcast retrieved successfully', type: PodcastResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'Podcast not found' })
|
||||||
|
async findOne(@Param('id', ParseIntPipe) id: number): Promise<PodcastResponseDto> {
|
||||||
|
return this.podcastsService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Update podcast' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Podcast updated successfully', type: PodcastResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'Podcast not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async update(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() updatePodcastDto: UpdatePodcastDto,
|
||||||
|
): Promise<PodcastResponseDto> {
|
||||||
|
return this.podcastsService.update(id, updatePodcastDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Delete podcast (soft delete)' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Podcast deleted successfully' })
|
||||||
|
@ApiResponse({ status: 404, description: 'Podcast not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async remove(@Param('id', ParseIntPipe) id: number): Promise<{ message: string }> {
|
||||||
|
await this.podcastsService.remove(id);
|
||||||
|
return { message: 'Podcast deleted successfully' };
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/modules/podcasts/dto/create-podcast.dto.ts
Normal file
22
src/modules/podcasts/dto/create-podcast.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { IsString, IsOptional, IsArray, IsUrl } from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreatePodcastDto {
|
||||||
|
@ApiProperty({ description: 'Podcast title' })
|
||||||
|
@IsString()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Podcast description' })
|
||||||
|
@IsString()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Podcast file URL' })
|
||||||
|
@IsUrl()
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'Podcast tags', type: [String] })
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
24
src/modules/podcasts/dto/podcast-response.dto.ts
Normal file
24
src/modules/podcasts/dto/podcast-response.dto.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class PodcastResponseDto {
|
||||||
|
@ApiProperty({ description: 'Podcast ID' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Podcast title' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Podcast description' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Podcast file URL' })
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Podcast tags', type: [String] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Creation date' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Last update date' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
4
src/modules/podcasts/dto/update-podcast.dto.ts
Normal file
4
src/modules/podcasts/dto/update-podcast.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreatePodcastDto } from './create-podcast.dto';
|
||||||
|
|
||||||
|
export class UpdatePodcastDto extends PartialType(CreatePodcastDto) {}
|
||||||
12
src/modules/podcasts/podcasts.module.ts
Normal file
12
src/modules/podcasts/podcasts.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PodcastsController } from './controllers/podcasts.controller';
|
||||||
|
import { PodcastsService } from './services/podcasts.service';
|
||||||
|
import { PrismaModule } from '../../common/database/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [PodcastsController],
|
||||||
|
providers: [PodcastsService],
|
||||||
|
exports: [PodcastsService],
|
||||||
|
})
|
||||||
|
export class PodcastsModule {}
|
||||||
223
src/modules/podcasts/services/podcasts.service.ts
Normal file
223
src/modules/podcasts/services/podcasts.service.ts
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../../../common/database/prisma.service';
|
||||||
|
import { CreatePodcastDto } from '../dto/create-podcast.dto';
|
||||||
|
import { UpdatePodcastDto } from '../dto/update-podcast.dto';
|
||||||
|
import { PodcastResponseDto } from '../dto/podcast-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
import { paginate } from '../../../common/utils/pagination.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PodcastsService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async create(createPodcastDto: CreatePodcastDto): Promise<PodcastResponseDto> {
|
||||||
|
const podcast = await this.prisma.podcasts.create({
|
||||||
|
data: {
|
||||||
|
...createPodcastDto,
|
||||||
|
tags: createPodcastDto.tags || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(podcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [podcasts, total] = await Promise.all([
|
||||||
|
this.prisma.podcasts.findMany({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.podcasts.count({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedPodcasts = podcasts.map(podcast => this.mapToResponseDto(podcast));
|
||||||
|
|
||||||
|
return paginate(mappedPodcasts, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: number): Promise<PodcastResponseDto> {
|
||||||
|
const podcast = await this.prisma.podcasts.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!podcast) {
|
||||||
|
throw new NotFoundException(`Podcast with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapToResponseDto(podcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, updatePodcastDto: UpdatePodcastDto): Promise<PodcastResponseDto> {
|
||||||
|
const existingPodcast = await this.prisma.podcasts.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingPodcast) {
|
||||||
|
throw new NotFoundException(`Podcast with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const podcast = await this.prisma.podcasts.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...updatePodcastDto,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(podcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<void> {
|
||||||
|
const existingPodcast = await this.prisma.podcasts.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingPodcast) {
|
||||||
|
throw new NotFoundException(`Podcast with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.podcasts.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByTag(tag: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [podcasts, total] = await Promise.all([
|
||||||
|
this.prisma.podcasts.findMany({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.podcasts.count({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedPodcasts = podcasts.map(podcast => this.mapToResponseDto(podcast));
|
||||||
|
|
||||||
|
return paginate(mappedPodcasts, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByTitle(title: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [podcasts, total] = await Promise.all([
|
||||||
|
this.prisma.podcasts.findMany({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.podcasts.count({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedPodcasts = podcasts.map(podcast => this.mapToResponseDto(podcast));
|
||||||
|
|
||||||
|
return paginate(mappedPodcasts, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByDescription(description: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [podcasts, total] = await Promise.all([
|
||||||
|
this.prisma.podcasts.findMany({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.podcasts.count({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedPodcasts = podcasts.map(podcast => this.mapToResponseDto(podcast));
|
||||||
|
|
||||||
|
return paginate(mappedPodcasts, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToResponseDto(podcast: any): PodcastResponseDto {
|
||||||
|
return {
|
||||||
|
id: podcast.id,
|
||||||
|
title: podcast.title,
|
||||||
|
description: podcast.description,
|
||||||
|
fileUrl: podcast.fileUrl,
|
||||||
|
tags: podcast.tags,
|
||||||
|
createdAt: podcast.createdAt,
|
||||||
|
updatedAt: podcast.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
Query,
|
||||||
|
ParseIntPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { ReadingMaterialsService } from '../services/reading-materials.service';
|
||||||
|
import { CreateReadingMaterialDto } from '../dto/create-reading-material.dto';
|
||||||
|
import { UpdateReadingMaterialDto } from '../dto/update-reading-material.dto';
|
||||||
|
import { ReadingMaterialResponseDto } from '../dto/reading-material-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
|
||||||
|
@ApiTags('reading-materials')
|
||||||
|
@Controller('reading-materials')
|
||||||
|
export class ReadingMaterialsController {
|
||||||
|
constructor(private readonly readingMaterialsService: ReadingMaterialsService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Create a new reading material' })
|
||||||
|
@ApiResponse({ status: 201, description: 'Reading material created successfully', type: ReadingMaterialResponseDto })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async create(@Body() createReadingMaterialDto: CreateReadingMaterialDto): Promise<ReadingMaterialResponseDto> {
|
||||||
|
return this.readingMaterialsService.create(createReadingMaterialDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get all reading materials with pagination' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Reading materials retrieved successfully' })
|
||||||
|
async findAll(@Query() paginationDto: PaginationDto) {
|
||||||
|
return this.readingMaterialsService.findAll(paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/title')
|
||||||
|
@ApiOperation({ summary: 'Search reading materials by title' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Reading materials retrieved successfully' })
|
||||||
|
async searchByTitle(
|
||||||
|
@Query('q') title: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.readingMaterialsService.searchByTitle(title, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/description')
|
||||||
|
@ApiOperation({ summary: 'Search reading materials by description' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Reading materials retrieved successfully' })
|
||||||
|
async searchByDescription(
|
||||||
|
@Query('q') description: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.readingMaterialsService.searchByDescription(description, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('tag/:tag')
|
||||||
|
@ApiOperation({ summary: 'Get reading materials by tag' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Reading materials retrieved successfully' })
|
||||||
|
async findByTag(
|
||||||
|
@Param('tag') tag: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.readingMaterialsService.findByTag(tag, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get reading material by ID' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Reading material retrieved successfully', type: ReadingMaterialResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'Reading material not found' })
|
||||||
|
async findOne(@Param('id', ParseIntPipe) id: number): Promise<ReadingMaterialResponseDto> {
|
||||||
|
return this.readingMaterialsService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Update reading material' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Reading material updated successfully', type: ReadingMaterialResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'Reading material not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async update(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() updateReadingMaterialDto: UpdateReadingMaterialDto,
|
||||||
|
): Promise<ReadingMaterialResponseDto> {
|
||||||
|
return this.readingMaterialsService.update(id, updateReadingMaterialDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Delete reading material (soft delete)' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Reading material deleted successfully' })
|
||||||
|
@ApiResponse({ status: 404, description: 'Reading material not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async remove(@Param('id', ParseIntPipe) id: number): Promise<{ message: string }> {
|
||||||
|
await this.readingMaterialsService.remove(id);
|
||||||
|
return { message: 'Reading material deleted successfully' };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { IsString, IsOptional, IsArray, IsUrl } from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateReadingMaterialDto {
|
||||||
|
@ApiProperty({ description: 'Reading material title' })
|
||||||
|
@IsString()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Reading material description' })
|
||||||
|
@IsString()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Reading material file URL' })
|
||||||
|
@IsUrl()
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'Reading material tags', type: [String] })
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class ReadingMaterialResponseDto {
|
||||||
|
@ApiProperty({ description: 'Reading material ID' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Reading material title' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Reading material description' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Reading material file URL' })
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Reading material tags', type: [String] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Creation date' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Last update date' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateReadingMaterialDto } from './create-reading-material.dto';
|
||||||
|
|
||||||
|
export class UpdateReadingMaterialDto extends PartialType(CreateReadingMaterialDto) {}
|
||||||
12
src/modules/reading-materials/reading-materials.module.ts
Normal file
12
src/modules/reading-materials/reading-materials.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ReadingMaterialsController } from './controllers/reading-materials.controller';
|
||||||
|
import { ReadingMaterialsService } from './services/reading-materials.service';
|
||||||
|
import { PrismaModule } from '../../common/database/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [ReadingMaterialsController],
|
||||||
|
providers: [ReadingMaterialsService],
|
||||||
|
exports: [ReadingMaterialsService],
|
||||||
|
})
|
||||||
|
export class ReadingMaterialsModule {}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../../../common/database/prisma.service';
|
||||||
|
import { CreateReadingMaterialDto } from '../dto/create-reading-material.dto';
|
||||||
|
import { UpdateReadingMaterialDto } from '../dto/update-reading-material.dto';
|
||||||
|
import { ReadingMaterialResponseDto } from '../dto/reading-material-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
import { paginate } from '../../../common/utils/pagination.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ReadingMaterialsService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async create(createReadingMaterialDto: CreateReadingMaterialDto): Promise<ReadingMaterialResponseDto> {
|
||||||
|
const readingMaterial = await this.prisma.readingMaterials.create({
|
||||||
|
data: {
|
||||||
|
...createReadingMaterialDto,
|
||||||
|
tags: createReadingMaterialDto.tags || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(readingMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [readingMaterials, total] = await Promise.all([
|
||||||
|
this.prisma.readingMaterials.findMany({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.readingMaterials.count({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedReadingMaterials = readingMaterials.map(readingMaterial => this.mapToResponseDto(readingMaterial));
|
||||||
|
|
||||||
|
return paginate(mappedReadingMaterials, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: number): Promise<ReadingMaterialResponseDto> {
|
||||||
|
const readingMaterial = await this.prisma.readingMaterials.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!readingMaterial) {
|
||||||
|
throw new NotFoundException(`Reading material with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapToResponseDto(readingMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, updateReadingMaterialDto: UpdateReadingMaterialDto): Promise<ReadingMaterialResponseDto> {
|
||||||
|
const existingReadingMaterial = await this.prisma.readingMaterials.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingReadingMaterial) {
|
||||||
|
throw new NotFoundException(`Reading material with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const readingMaterial = await this.prisma.readingMaterials.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...updateReadingMaterialDto,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(readingMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<void> {
|
||||||
|
const existingReadingMaterial = await this.prisma.readingMaterials.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingReadingMaterial) {
|
||||||
|
throw new NotFoundException(`Reading material with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.readingMaterials.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByTag(tag: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [readingMaterials, total] = await Promise.all([
|
||||||
|
this.prisma.readingMaterials.findMany({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.readingMaterials.count({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedReadingMaterials = readingMaterials.map(readingMaterial => this.mapToResponseDto(readingMaterial));
|
||||||
|
|
||||||
|
return paginate(mappedReadingMaterials, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByTitle(title: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [readingMaterials, total] = await Promise.all([
|
||||||
|
this.prisma.readingMaterials.findMany({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.readingMaterials.count({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedReadingMaterials = readingMaterials.map(readingMaterial => this.mapToResponseDto(readingMaterial));
|
||||||
|
|
||||||
|
return paginate(mappedReadingMaterials, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByDescription(description: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [readingMaterials, total] = await Promise.all([
|
||||||
|
this.prisma.readingMaterials.findMany({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.readingMaterials.count({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedReadingMaterials = readingMaterials.map(readingMaterial => this.mapToResponseDto(readingMaterial));
|
||||||
|
|
||||||
|
return paginate(mappedReadingMaterials, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToResponseDto(readingMaterial: any): ReadingMaterialResponseDto {
|
||||||
|
return {
|
||||||
|
id: readingMaterial.id,
|
||||||
|
title: readingMaterial.title,
|
||||||
|
description: readingMaterial.description,
|
||||||
|
fileUrl: readingMaterial.fileUrl,
|
||||||
|
tags: readingMaterial.tags,
|
||||||
|
createdAt: readingMaterial.createdAt,
|
||||||
|
updatedAt: readingMaterial.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Patch,
|
||||||
|
Param,
|
||||||
|
Delete,
|
||||||
|
Query,
|
||||||
|
ParseIntPipe,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { TrainingMaterialsService } from '../services/training-materials.service';
|
||||||
|
import { CreateTrainingMaterialDto } from '../dto/create-training-material.dto';
|
||||||
|
import { UpdateTrainingMaterialDto } from '../dto/update-training-material.dto';
|
||||||
|
import { TrainingMaterialResponseDto } from '../dto/training-material-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
|
||||||
|
@ApiTags('training-materials')
|
||||||
|
@Controller('training-materials')
|
||||||
|
export class TrainingMaterialsController {
|
||||||
|
constructor(private readonly trainingMaterialsService: TrainingMaterialsService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Create a new training material' })
|
||||||
|
@ApiResponse({ status: 201, description: 'Training material created successfully', type: TrainingMaterialResponseDto })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async create(@Body() createTrainingMaterialDto: CreateTrainingMaterialDto): Promise<TrainingMaterialResponseDto> {
|
||||||
|
return this.trainingMaterialsService.create(createTrainingMaterialDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@ApiOperation({ summary: 'Get all training materials with pagination' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Training materials retrieved successfully' })
|
||||||
|
async findAll(@Query() paginationDto: PaginationDto) {
|
||||||
|
return this.trainingMaterialsService.findAll(paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/title')
|
||||||
|
@ApiOperation({ summary: 'Search training materials by title' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Training materials retrieved successfully' })
|
||||||
|
async searchByTitle(
|
||||||
|
@Query('q') title: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.trainingMaterialsService.searchByTitle(title, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('search/description')
|
||||||
|
@ApiOperation({ summary: 'Search training materials by description' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Training materials retrieved successfully' })
|
||||||
|
async searchByDescription(
|
||||||
|
@Query('q') description: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.trainingMaterialsService.searchByDescription(description, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('tag/:tag')
|
||||||
|
@ApiOperation({ summary: 'Get training materials by tag' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Training materials retrieved successfully' })
|
||||||
|
async findByTag(
|
||||||
|
@Param('tag') tag: string,
|
||||||
|
@Query() paginationDto: PaginationDto,
|
||||||
|
) {
|
||||||
|
return this.trainingMaterialsService.findByTag(tag, paginationDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get training material by ID' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Training material retrieved successfully', type: TrainingMaterialResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'Training material not found' })
|
||||||
|
async findOne(@Param('id', ParseIntPipe) id: number): Promise<TrainingMaterialResponseDto> {
|
||||||
|
return this.trainingMaterialsService.findOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Update training material' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Training material updated successfully', type: TrainingMaterialResponseDto })
|
||||||
|
@ApiResponse({ status: 404, description: 'Training material not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async update(
|
||||||
|
@Param('id', ParseIntPipe) id: number,
|
||||||
|
@Body() updateTrainingMaterialDto: UpdateTrainingMaterialDto,
|
||||||
|
): Promise<TrainingMaterialResponseDto> {
|
||||||
|
return this.trainingMaterialsService.update(id, updateTrainingMaterialDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
// @UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
// @Roles('ADMIN', 'HR')
|
||||||
|
// @ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Delete training material (soft delete)' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Training material deleted successfully' })
|
||||||
|
@ApiResponse({ status: 404, description: 'Training material not found' })
|
||||||
|
@ApiResponse({ status: 401, description: 'Unauthorized' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Forbidden' })
|
||||||
|
async remove(@Param('id', ParseIntPipe) id: number): Promise<{ message: string }> {
|
||||||
|
await this.trainingMaterialsService.remove(id);
|
||||||
|
return { message: 'Training material deleted successfully' };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { IsString, IsOptional, IsArray, IsUrl } from 'class-validator';
|
||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateTrainingMaterialDto {
|
||||||
|
@ApiProperty({ description: 'Training material title' })
|
||||||
|
@IsString()
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Training material description' })
|
||||||
|
@IsString()
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Training material file URL' })
|
||||||
|
@IsUrl()
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'Training material tags', type: [String] })
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsOptional()
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class TrainingMaterialResponseDto {
|
||||||
|
@ApiProperty({ description: 'Training material ID' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Training material title' })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Training material description' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Training material file URL' })
|
||||||
|
fileUrl: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Training material tags', type: [String] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Creation date' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Last update date' })
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateTrainingMaterialDto } from './create-training-material.dto';
|
||||||
|
|
||||||
|
export class UpdateTrainingMaterialDto extends PartialType(CreateTrainingMaterialDto) {}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../../../common/database/prisma.service';
|
||||||
|
import { CreateTrainingMaterialDto } from '../dto/create-training-material.dto';
|
||||||
|
import { UpdateTrainingMaterialDto } from '../dto/update-training-material.dto';
|
||||||
|
import { TrainingMaterialResponseDto } from '../dto/training-material-response.dto';
|
||||||
|
import { PaginationDto } from '../../../common/dto/pagination.dto';
|
||||||
|
import { paginate } from '../../../common/utils/pagination.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TrainingMaterialsService {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async create(createTrainingMaterialDto: CreateTrainingMaterialDto): Promise<TrainingMaterialResponseDto> {
|
||||||
|
const trainingMaterial = await this.prisma.trainingMaterials.create({
|
||||||
|
data: {
|
||||||
|
...createTrainingMaterialDto,
|
||||||
|
tags: createTrainingMaterialDto.tags || [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(trainingMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll(paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [trainingMaterials, total] = await Promise.all([
|
||||||
|
this.prisma.trainingMaterials.findMany({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.trainingMaterials.count({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedTrainingMaterials = trainingMaterials.map(trainingMaterial => this.mapToResponseDto(trainingMaterial));
|
||||||
|
|
||||||
|
return paginate(mappedTrainingMaterials, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: number): Promise<TrainingMaterialResponseDto> {
|
||||||
|
const trainingMaterial = await this.prisma.trainingMaterials.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!trainingMaterial) {
|
||||||
|
throw new NotFoundException(`Training material with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapToResponseDto(trainingMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: number, updateTrainingMaterialDto: UpdateTrainingMaterialDto): Promise<TrainingMaterialResponseDto> {
|
||||||
|
const existingTrainingMaterial = await this.prisma.trainingMaterials.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingTrainingMaterial) {
|
||||||
|
throw new NotFoundException(`Training material with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const trainingMaterial = await this.prisma.trainingMaterials.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...updateTrainingMaterialDto,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.mapToResponseDto(trainingMaterial);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: number): Promise<void> {
|
||||||
|
const existingTrainingMaterial = await this.prisma.trainingMaterials.findFirst({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingTrainingMaterial) {
|
||||||
|
throw new NotFoundException(`Training material with ID ${id} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.trainingMaterials.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByTag(tag: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [trainingMaterials, total] = await Promise.all([
|
||||||
|
this.prisma.trainingMaterials.findMany({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.trainingMaterials.count({
|
||||||
|
where: {
|
||||||
|
tags: {
|
||||||
|
has: tag,
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedTrainingMaterials = trainingMaterials.map(trainingMaterial => this.mapToResponseDto(trainingMaterial));
|
||||||
|
|
||||||
|
return paginate(mappedTrainingMaterials, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByTitle(title: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [trainingMaterials, total] = await Promise.all([
|
||||||
|
this.prisma.trainingMaterials.findMany({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.trainingMaterials.count({
|
||||||
|
where: {
|
||||||
|
title: {
|
||||||
|
contains: title,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedTrainingMaterials = trainingMaterials.map(trainingMaterial => this.mapToResponseDto(trainingMaterial));
|
||||||
|
|
||||||
|
return paginate(mappedTrainingMaterials, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchByDescription(description: string, paginationDto: PaginationDto) {
|
||||||
|
const { page, limit } = paginationDto;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
const [trainingMaterials, total] = await Promise.all([
|
||||||
|
this.prisma.trainingMaterials.findMany({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
this.prisma.trainingMaterials.count({
|
||||||
|
where: {
|
||||||
|
description: {
|
||||||
|
contains: description,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const mappedTrainingMaterials = trainingMaterials.map(trainingMaterial => this.mapToResponseDto(trainingMaterial));
|
||||||
|
|
||||||
|
return paginate(mappedTrainingMaterials, { page, limit }, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapToResponseDto(trainingMaterial: any): TrainingMaterialResponseDto {
|
||||||
|
return {
|
||||||
|
id: trainingMaterial.id,
|
||||||
|
title: trainingMaterial.title,
|
||||||
|
description: trainingMaterial.description,
|
||||||
|
fileUrl: trainingMaterial.fileUrl,
|
||||||
|
tags: trainingMaterial.tags,
|
||||||
|
createdAt: trainingMaterial.createdAt,
|
||||||
|
updatedAt: trainingMaterial.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/modules/training-materials/training-materials.module.ts
Normal file
12
src/modules/training-materials/training-materials.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TrainingMaterialsController } from './controllers/training-materials.controller';
|
||||||
|
import { TrainingMaterialsService } from './services/training-materials.service';
|
||||||
|
import { PrismaModule } from '../../common/database/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [TrainingMaterialsController],
|
||||||
|
providers: [TrainingMaterialsService],
|
||||||
|
exports: [TrainingMaterialsService],
|
||||||
|
})
|
||||||
|
export class TrainingMaterialsModule {}
|
||||||
Reference in New Issue
Block a user