From e3cec875783afd26f5dc8e603f2fbf2771f96f76 Mon Sep 17 00:00:00 2001 From: Akshay Mayekar Date: Wed, 15 Oct 2025 17:49:08 +0530 Subject: [PATCH] webcast CRUD --- prisma/schema.prisma | 5 +- src/app.module.ts | 2 + .../controllers/webcasts.controller.ts | 94 +++++++++++ src/modules/webcast/dto/create-webcast.dto.ts | 22 +++ src/modules/webcast/dto/update-webcast.dto.ts | 4 + .../webcast/dto/webcast-response.dto.ts | 24 +++ .../webcast/services/webcasts.service.ts | 153 ++++++++++++++++++ src/modules/webcast/webcasts.module.ts | 12 ++ 8 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 src/modules/webcast/controllers/webcasts.controller.ts create mode 100644 src/modules/webcast/dto/create-webcast.dto.ts create mode 100644 src/modules/webcast/dto/update-webcast.dto.ts create mode 100644 src/modules/webcast/dto/webcast-response.dto.ts create mode 100644 src/modules/webcast/services/webcasts.service.ts create mode 100644 src/modules/webcast/webcasts.module.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3d925d9..9ce8709 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,6 +34,9 @@ enum UserRole { USER ADMIN HR + + @@schema("usr") + } model Blog { @@ -148,7 +151,7 @@ model KlcArchive { tags String[] @default([]) @map("tags") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") - deletedAt DateTime? @map("delecase_studies + deletedAt DateTime? @map("deleted_at") @@map("klc_archive") @@schema("cnt") } diff --git a/src/app.module.ts b/src/app.module.ts index f3c14c8..5d9007f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { ThrottlerModule } from '@nestjs/throttler'; import { PrismaModule } from './common/database/prisma.module'; import { AuthModule } from './modules/auth/auth.module'; import { BlogsModule } from './modules/blog/blogs.module'; +import { WebcastsModule } from './modules/webcast/webcasts.module'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @@ -29,6 +30,7 @@ import { AppService } from './app.service'; // Feature modules AuthModule, BlogsModule, + WebcastsModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/modules/webcast/controllers/webcasts.controller.ts b/src/modules/webcast/controllers/webcasts.controller.ts new file mode 100644 index 0000000..f354cc5 --- /dev/null +++ b/src/modules/webcast/controllers/webcasts.controller.ts @@ -0,0 +1,94 @@ +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + Query, + ParseIntPipe, + UseGuards, +} from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { WebcastsService } from '../services/webcasts.service'; +import { CreateWebcastDto } from '../dto/create-webcast.dto'; +import { UpdateWebcastDto } from '../dto/update-webcast.dto'; +import { WebcastResponseDto } from '../dto/webcast-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('webcasts') +@Controller('webcasts') +export class WebcastsController { + constructor(private readonly webcastsService: WebcastsService) {} + + @Post() + // @UseGuards(JwtAuthGuard, RolesGuard) + // @Roles('ADMIN', 'HR') + // @ApiBearerAuth() + @ApiOperation({ summary: 'Create a new webcast' }) + @ApiResponse({ status: 201, description: 'Webcast created successfully', type: WebcastResponseDto }) + @ApiResponse({ status: 401, description: 'Unauthorized' }) + @ApiResponse({ status: 403, description: 'Forbidden' }) + async create(@Body() createWebcastDto: CreateWebcastDto): Promise { + return this.webcastsService.create(createWebcastDto); + } + + @Get() + @ApiOperation({ summary: 'Get all webcasts with pagination' }) + @ApiResponse({ status: 200, description: 'Webcasts retrieved successfully' }) + async findAll(@Query() paginationDto: PaginationDto) { + return this.webcastsService.findAll(paginationDto); + } + + @Get('tag/:tag') + @ApiOperation({ summary: 'Get webcasts by tag' }) + @ApiResponse({ status: 200, description: 'Webcasts retrieved successfully' }) + async findByTag( + @Param('tag') tag: string, + @Query() paginationDto: PaginationDto, + ) { + return this.webcastsService.findByTag(tag, paginationDto); + } + + @Get(':id') + @ApiOperation({ summary: 'Get webcast by ID' }) + @ApiResponse({ status: 200, description: 'Webcast retrieved successfully', type: WebcastResponseDto }) + @ApiResponse({ status: 404, description: 'Webcast not found' }) + async findOne(@Param('id', ParseIntPipe) id: number): Promise { + return this.webcastsService.findOne(id); + } + + @Patch(':id') + // @UseGuards(JwtAuthGuard, RolesGuard) + // @Roles('ADMIN', 'HR') + // @ApiBearerAuth() + @ApiOperation({ summary: 'Update webcast' }) + @ApiResponse({ status: 200, description: 'Webcast updated successfully', type: WebcastResponseDto }) + @ApiResponse({ status: 404, description: 'Webcast not found' }) + @ApiResponse({ status: 401, description: 'Unauthorized' }) + @ApiResponse({ status: 403, description: 'Forbidden' }) + async update( + @Param('id', ParseIntPipe) id: number, + @Body() updateWebcastDto: UpdateWebcastDto, + ): Promise { + return this.webcastsService.update(id, updateWebcastDto); + } + + @Delete(':id') + // @UseGuards(JwtAuthGuard, RolesGuard) + // @Roles('ADMIN', 'HR') + // @ApiBearerAuth() + @ApiOperation({ summary: 'Delete webcast (soft delete)' }) + @ApiResponse({ status: 200, description: 'Webcast deleted successfully' }) + @ApiResponse({ status: 404, description: 'Webcast not found' }) + @ApiResponse({ status: 401, description: 'Unauthorized' }) + @ApiResponse({ status: 403, description: 'Forbidden' }) + async remove(@Param('id', ParseIntPipe) id: number): Promise<{ message: string }> { + await this.webcastsService.remove(id); + return { message: 'Webcast deleted successfully' }; + } +} diff --git a/src/modules/webcast/dto/create-webcast.dto.ts b/src/modules/webcast/dto/create-webcast.dto.ts new file mode 100644 index 0000000..ea8c53a --- /dev/null +++ b/src/modules/webcast/dto/create-webcast.dto.ts @@ -0,0 +1,22 @@ +import { IsString, IsOptional, IsArray } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class CreateWebcastDto { + @ApiProperty({ description: 'Webcast title' }) + @IsString() + title: string; + + @ApiProperty({ description: 'Webcast description' }) + @IsString() + description: string; + + @ApiProperty({ description: 'Webcast file URL' }) + @IsString() + fileUrl: string; + + @ApiPropertyOptional({ description: 'Webcast tags', type: [String] }) + @IsArray() + @IsString({ each: true }) + @IsOptional() + tags?: string[]; +} diff --git a/src/modules/webcast/dto/update-webcast.dto.ts b/src/modules/webcast/dto/update-webcast.dto.ts new file mode 100644 index 0000000..2c242ab --- /dev/null +++ b/src/modules/webcast/dto/update-webcast.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateWebcastDto } from './create-webcast.dto'; + +export class UpdateWebcastDto extends PartialType(CreateWebcastDto) {} diff --git a/src/modules/webcast/dto/webcast-response.dto.ts b/src/modules/webcast/dto/webcast-response.dto.ts new file mode 100644 index 0000000..1129dd4 --- /dev/null +++ b/src/modules/webcast/dto/webcast-response.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class WebcastResponseDto { + @ApiProperty({ description: 'Webcast ID' }) + id: number; + + @ApiProperty({ description: 'Webcast title' }) + title: string; + + @ApiProperty({ description: 'Webcast description' }) + description: string; + + @ApiProperty({ description: 'Webcast file URL' }) + fileUrl: string; + + @ApiProperty({ description: 'Webcast tags', type: [String] }) + tags: string[]; + + @ApiPropertyOptional({ description: 'Creation date' }) + createdAt: Date; + + @ApiPropertyOptional({ description: 'Last update date' }) + updatedAt: Date; +} diff --git a/src/modules/webcast/services/webcasts.service.ts b/src/modules/webcast/services/webcasts.service.ts new file mode 100644 index 0000000..bd002aa --- /dev/null +++ b/src/modules/webcast/services/webcasts.service.ts @@ -0,0 +1,153 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { CreateWebcastDto } from '../dto/create-webcast.dto'; +import { UpdateWebcastDto } from '../dto/update-webcast.dto'; +import { WebcastResponseDto } from '../dto/webcast-response.dto'; +import { PaginationDto } from '../../../common/dto/pagination.dto'; +import { paginate } from '../../../common/utils/pagination.util'; + +@Injectable() +export class WebcastsService { + constructor(private readonly prisma: PrismaService) {} + + async create(createWebcastDto: CreateWebcastDto): Promise { + const webcast = await this.prisma.webcast.create({ + data: { + ...createWebcastDto, + tags: createWebcastDto.tags || [], + }, + }); + + return this.mapToResponseDto(webcast); + } + + async findAll(paginationDto: PaginationDto) { + const { page, limit } = paginationDto; + const skip = (page - 1) * limit; + + const [webcasts, total] = await Promise.all([ + this.prisma.webcast.findMany({ + where: { + deletedAt: null, + }, + orderBy: { + createdAt: 'desc', + }, + skip, + take: limit, + }), + this.prisma.webcast.count({ + where: { + deletedAt: null, + }, + }), + ]); + + const mappedWebcasts = webcasts.map(webcast => this.mapToResponseDto(webcast)); + + return paginate(mappedWebcasts, { page, limit }, total); + } + + async findOne(id: number): Promise { + const webcast = await this.prisma.webcast.findFirst({ + where: { + id, + deletedAt: null, + }, + }); + + if (!webcast) { + throw new NotFoundException(`Webcast with ID ${id} not found`); + } + + return this.mapToResponseDto(webcast); + } + + async update(id: number, updateWebcastDto: UpdateWebcastDto): Promise { + const existingWebcast = await this.prisma.webcast.findFirst({ + where: { + id, + deletedAt: null, + }, + }); + + if (!existingWebcast) { + throw new NotFoundException(`Webcast with ID ${id} not found`); + } + + const webcast = await this.prisma.webcast.update({ + where: { id }, + data: { + ...updateWebcastDto, + updatedAt: new Date(), + }, + }); + + return this.mapToResponseDto(webcast); + } + + async remove(id: number): Promise { + const existingWebcast = await this.prisma.webcast.findFirst({ + where: { + id, + deletedAt: null, + }, + }); + + if (!existingWebcast) { + throw new NotFoundException(`Webcast with ID ${id} not found`); + } + + await this.prisma.webcast.update({ + where: { id }, + data: { + deletedAt: new Date(), + }, + }); + } + + async findByTag(tag: string, paginationDto: PaginationDto) { + const { page, limit } = paginationDto; + const skip = (page - 1) * limit; + + const [webcasts, total] = await Promise.all([ + this.prisma.webcast.findMany({ + where: { + tags: { + has: tag, + }, + deletedAt: null, + }, + orderBy: { + createdAt: 'desc', + }, + skip, + take: limit, + }), + this.prisma.webcast.count({ + where: { + tags: { + has: tag, + }, + deletedAt: null, + }, + }), + ]); + + const mappedWebcasts = webcasts.map(webcast => this.mapToResponseDto(webcast)); + + return paginate(mappedWebcasts, { page, limit }, total); + } + + private mapToResponseDto(webcast: any): WebcastResponseDto { + return { + id: webcast.id, + title: webcast.title, + description: webcast.description, + fileUrl: webcast.fileUrl, + tags: webcast.tags, + createdAt: webcast.createdAt, + updatedAt: webcast.updatedAt, + }; + } +} diff --git a/src/modules/webcast/webcasts.module.ts b/src/modules/webcast/webcasts.module.ts new file mode 100644 index 0000000..ae339af --- /dev/null +++ b/src/modules/webcast/webcasts.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { WebcastsService } from './services/webcasts.service'; +import { WebcastsController } from './controllers/webcasts.controller'; +import { PrismaModule } from '../../common/database/prisma.module'; + +@Module({ + imports: [PrismaModule], + controllers: [WebcastsController], + providers: [WebcastsService], + exports: [WebcastsService], +}) +export class WebcastsModule {}