diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 94f9cbd..f8f1e2f 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -128,6 +128,10 @@ export const enum AnnouncementMessage { CONTENT_IS_REQUIRED = "متن اطلاعیه مورد نیاز است", CONTENT_IS_STRING = "متن اطلاعیه باید رشته باشد", CONTENT_MUST_BE_LONGER = "متن اطلاعیه باید حداقل ۱۰ کاراکتر باشد", + PUBLISH_AT_IS_REQUIRED = "تاریخ انتشار اطلاعیه مورد نیاز است", + SERVICE_MUST_BE_UUID = "شناسه سرویس باید یک UUID معتبر باشد", + GROUP_MUST_BE_UUID = "شناسه گروه کاربران باید یک UUID معتبر باشد", + SERVICE_MUST_BE_ID = "ایدی سرویس اجباری است", } export const enum CriticismMessage { diff --git a/src/modules/announcements/DTO/create-announcement.dto.ts b/src/modules/announcements/DTO/create-announcement.dto.ts index c8c2dd8..4aeea1a 100644 --- a/src/modules/announcements/DTO/create-announcement.dto.ts +++ b/src/modules/announcements/DTO/create-announcement.dto.ts @@ -7,7 +7,6 @@ export class CreateAnnouncementDto { @IsNotEmpty({ message: AnnouncementMessage.TITLE_IS_REQUIRED }) @IsString({ message: AnnouncementMessage.TITLE_STRING }) @ApiProperty({ - type: String, description: "Title of the announcement", example: "اطلاعیه جدید", }) @@ -17,25 +16,47 @@ export class CreateAnnouncementDto { @IsString({ message: AnnouncementMessage.CONTENT_IS_STRING }) @MinLength(10, { message: AnnouncementMessage.CONTENT_MUST_BE_LONGER }) @ApiProperty({ - type: String, description: "Content of the announcement", example: "اطلاعیه جدید برای کاربران", }) content: string; + @IsOptional() + @IsNotEmpty({ message: AnnouncementMessage.PUBLISH_AT_IS_REQUIRED }) + @ApiProperty({ + description: "Publish date of the announcement", + example: "2023-10-01T10:00:00Z", + }) + publishAt: Date; + @IsBoolean() @ApiProperty({ - type: Boolean, - description: "Is this announcement important?", + description: "Is this announcement public?", example: false, }) isPublic: boolean; + @IsBoolean() + @ApiProperty({ + description: "Is this announcement important?", + example: false, + }) + isImportant: boolean; + + @IsOptional() + @IsNotEmpty({ message: AnnouncementMessage.SERVICE_MUST_BE_ID }) + @IsUUID("4", { message: AnnouncementMessage.SERVICE_MUST_BE_UUID }) + @ApiProperty({ + description: "Service ID", + example: "d290f1ee-6c54-4b01-90e6-d701748f0851", + }) + serviceId: string; + @IsOptional() @IsArray() @IsUUID("4", { each: true, - message: "شناسه هر گروه باید یک UUID معتبر نسخه ۴ باشد", + message: AnnouncementMessage.GROUP_MUST_BE_UUID, }) @ApiProperty({ type: [String], diff --git a/src/modules/announcements/DTO/search-announcement-query.dto.ts b/src/modules/announcements/DTO/search-announcement-query.dto.ts new file mode 100644 index 0000000..8f6c42a --- /dev/null +++ b/src/modules/announcements/DTO/search-announcement-query.dto.ts @@ -0,0 +1,22 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { IsDateString, IsOptional, IsString } from "class-validator"; + +import { PaginationDto } from "../../../common/DTO/pagination.dto"; +import { CriticismMessage } from "../../../common/enums/message.enum"; + +export class SearchAnnouncementQueryDto extends PaginationDto { + @IsOptional() + @IsDateString() + @ApiPropertyOptional({ description: "since query", example: "2025-01-27" }) + since?: string; + + @IsOptional() + @IsDateString() + @ApiPropertyOptional({ description: "publishAt query", example: "2025-01-27" }) + publishAt?: string; + + @IsOptional() + @IsString({ message: CriticismMessage.SEARCH_QUERY_STRING }) + @ApiPropertyOptional({ description: "Search query", example: "search query" }) + q?: string; +} diff --git a/src/modules/announcements/announcement.controller.ts b/src/modules/announcements/announcement.controller.ts index ef9c438..6f7a840 100644 --- a/src/modules/announcements/announcement.controller.ts +++ b/src/modules/announcements/announcement.controller.ts @@ -1,8 +1,44 @@ -import { Controller } from "@nestjs/common"; -import { ApiTags } from "@nestjs/swagger"; +import { Body, Controller, Get, Post, Query } from "@nestjs/common"; +import { ApiProperty, ApiTags } from "@nestjs/swagger"; + +import { AnnouncementService } from "./announcement.service"; +import { CreateAnnouncementDto } from "./DTO/create-announcement.dto"; +import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; +import { Roles } from "../../common/decorators/roles.decorator"; +import { SearchCriticismQueryDto } from "../criticisms/DTO/search-criticism-query.dto"; +import { RoleEnum } from "../users/enums/role.enum"; @ApiTags("Announcements") @Controller("announcements") export class AnnouncementController { - constructor() {} + constructor(private readonly announcementService: AnnouncementService) {} + + @AuthGuards() + @Roles(RoleEnum.ADMIN) + @ApiProperty({ + description: "Create a new announcement ===> login as admin", + }) + @Post() + async create(@Body() createAnnouncementDto: CreateAnnouncementDto) { + return await this.announcementService.createAnnouncement(createAnnouncementDto); + } + + @AuthGuards() + @Roles(RoleEnum.ADMIN) + @ApiProperty({ + description: "Get all announcements ===> login as admin", + }) + @Get() + async getAllAnnouncements(@Query() queryDto: SearchCriticismQueryDto) { + return await this.announcementService.getAllAnnouncements(queryDto); + } + + @AuthGuards() + @ApiProperty({ + description: "Get all public announcements", + }) + @Get("public") + async getPublicAnnouncements() { + return await this.announcementService.getPublicAnnouncements(); + } } diff --git a/src/modules/announcements/announcement.module.ts b/src/modules/announcements/announcement.module.ts index bc6d173..daa5505 100644 --- a/src/modules/announcements/announcement.module.ts +++ b/src/modules/announcements/announcement.module.ts @@ -5,9 +5,11 @@ import { AnnouncementController } from "./announcement.controller"; import { AnnouncementService } from "./announcement.service"; import { Announcement } from "./entities/announcement.entity"; import { AnnouncementRepository } from "./repositories/announcement.repository"; +import { DanakServicesModule } from "../danak-services/danak-services.module"; +import { UsersModule } from "../users/users.module"; @Module({ - imports: [TypeOrmModule.forFeature([Announcement])], + imports: [TypeOrmModule.forFeature([Announcement]), DanakServicesModule, UsersModule], providers: [AnnouncementService, AnnouncementRepository], controllers: [AnnouncementController], exports: [TypeOrmModule], diff --git a/src/modules/announcements/announcement.service.ts b/src/modules/announcements/announcement.service.ts index 631d2b4..f2dad20 100644 --- a/src/modules/announcements/announcement.service.ts +++ b/src/modules/announcements/announcement.service.ts @@ -1,10 +1,73 @@ import { Injectable } from "@nestjs/common"; -// import { AnnouncementRepository } from "./repositories/announcement.repository"; +import { CreateAnnouncementDto } from "./DTO/create-announcement.dto"; +import { SearchAnnouncementQueryDto } from "./DTO/search-announcement-query.dto"; +import { AnnouncementRepository } from "./repositories/announcement.repository"; +import { CommonMessage } from "../../common/enums/message.enum"; +import { DanakServicesService } from "../danak-services/providers/danak-services.service"; +import { UsersService } from "../users/providers/users.service"; +import { PaginationUtils } from "../utils/providers/pagination.utils"; @Injectable() export class AnnouncementService { - // constructor(private readonly announcementRepository: AnnouncementRepository) {} + constructor( + private readonly announcementRepository: AnnouncementRepository, + private readonly danakServicesService: DanakServicesService, + private readonly usersService: UsersService, + ) {} - async createAnnouncement() {} + async createAnnouncement(createAnnouncementDto: CreateAnnouncementDto) { + let service; + let groups; + if (createAnnouncementDto.serviceId) { + service = await this.danakServicesService.findServiceById(createAnnouncementDto.serviceId); + } + if (createAnnouncementDto.groupIds) { + groups = await this.usersService.getUserGroupsByIds(createAnnouncementDto.groupIds); + } + const announcement = this.announcementRepository.create({ + ...createAnnouncementDto, + service, + targetGroups: groups?.userGroups, + }); + await this.announcementRepository.save(announcement); + return { + message: CommonMessage.CREATED, + announcement, + }; + } + + async getAllAnnouncements(queryDto: SearchAnnouncementQueryDto) { + const { limit, skip } = PaginationUtils(queryDto); + + const queryBuilder = this.announcementRepository + .createQueryBuilder("announcement") + .leftJoin("announcement.service", "danak_service") + .addSelect(["danak_service.id", "danak_service.name"]) + .leftJoin("announcement.targetGroups", "group") + .addSelect(["group.id", "group.name"]); + + if (queryDto.since) { + queryBuilder.andWhere("announcement.createdAt >= :since", { since: queryDto.since }); + } + + if (queryDto.publishAt) { + queryBuilder.andWhere("announcement.publishAt >= :publishAt", { publishAt: queryDto.publishAt }); + } + + if (queryDto.q) { + queryBuilder.andWhere("announcement.title LIKE :q", { q: `%${queryDto.q}%` }); + } + + queryBuilder.orderBy("announcement.createdAt", "DESC"); + + const [announcements, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); + + return { announcements, count }; + } + + async getPublicAnnouncements() { + const announcements = await this.announcementRepository.find({ where: { isPublic: true }, relations: ["service", "targetGroups"] }); + return { announcements }; + } } diff --git a/src/modules/announcements/entities/announcement.entity.ts b/src/modules/announcements/entities/announcement.entity.ts index ed23e48..b675fe0 100644 --- a/src/modules/announcements/entities/announcement.entity.ts +++ b/src/modules/announcements/entities/announcement.entity.ts @@ -16,7 +16,6 @@ export class Announcement extends BaseEntity { @Column({ type: "timestamp", default: null, nullable: true }) publishAt: Date; - //TODO: Add service here //TODO: Add customer here @ManyToOne(() => DanakService, (service) => service.announcements, { eager: true, onDelete: "CASCADE" }) service: DanakService; diff --git a/src/modules/danak-services/providers/danak-services.service.ts b/src/modules/danak-services/providers/danak-services.service.ts index a88d179..7e05435 100644 --- a/src/modules/danak-services/providers/danak-services.service.ts +++ b/src/modules/danak-services/providers/danak-services.service.ts @@ -127,5 +127,12 @@ export class DanakServicesService { } /******************************************** */ + + async findServiceById(id: string) { + const service = await this.danakServicesRepository.findOneBy({ id }); + if (!service) throw new BadRequestException(ServiceMessage.SERVICE_NOT_EXIST); + return service; + } + /******************************************** */ } diff --git a/src/modules/users/providers/users.service.ts b/src/modules/users/providers/users.service.ts index e93d2d6..048ccbc 100644 --- a/src/modules/users/providers/users.service.ts +++ b/src/modules/users/providers/users.service.ts @@ -143,4 +143,14 @@ export class UsersService { userGroup, }; } + + ///****************************************************** */ + + async getUserGroupsByIds(ids: string[]) { + const userGroups = await this.userGroupRepository.find({ where: { id: In(ids) } }); + if (!userGroups) throw new BadRequestException(UserMessage.USER_GROUP_NOT_FOUND); + return { + userGroups, + }; + } }