diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index a493234..3729548 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -132,6 +132,7 @@ export const enum AnnouncementMessage { SERVICE_MUST_BE_UUID = "شناسه سرویس باید یک UUID معتبر باشد", GROUP_MUST_BE_UUID = "شناسه گروه کاربران باید یک UUID معتبر باشد", SERVICE_MUST_BE_ID = "ایدی سرویس اجباری است", + NOT_FOUND = "اطلاعیه ای با این ایدی یافت نشد", } export const enum CriticismMessage { diff --git a/src/modules/announcements/announcement.controller.ts b/src/modules/announcements/announcement.controller.ts index a1db40d..b3dcb69 100644 --- a/src/modules/announcements/announcement.controller.ts +++ b/src/modules/announcements/announcement.controller.ts @@ -5,7 +5,10 @@ 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 { UserDec } from "../../common/decorators/user.decorator"; +import { ParamDto } from "../../common/DTO/param.dto"; import { SearchCriticismQueryDto } from "../criticisms/DTO/search-criticism-query.dto"; +import { User } from "../users/entities/user.entity"; import { RoleEnum } from "../users/enums/role.enum"; @ApiTags("Announcements") @@ -24,13 +27,33 @@ export class AnnouncementController { } @AuthGuards() - @Roles(RoleEnum.ADMIN) + @Roles(RoleEnum.ADMIN, RoleEnum.USER) @ApiProperty({ description: "Get all announcements ===> login as admin", }) @Get() - async getAllAnnouncements(@Query() queryDto: SearchCriticismQueryDto) { - return await this.announcementService.getAllAnnouncements(queryDto); + async getAllAnnouncements(@Query() queryDto: SearchCriticismQueryDto, @UserDec() user: User) { + return await this.announcementService.getAllAnnouncements(queryDto, user.id); + } + + @AuthGuards() + @Roles(RoleEnum.ADMIN, RoleEnum.USER, RoleEnum.VISITOR) + @ApiProperty({ + description: "Get all announcements ===> login as user", + }) + @Get("user") + async getAllAnnouncementsByUser(@Query() queryDto: SearchCriticismQueryDto, @UserDec() user: User) { + return await this.announcementService.getAllAnnouncementsByUser(queryDto, user.id); + } + + @AuthGuards() + @Roles(RoleEnum.ADMIN, RoleEnum.USER, RoleEnum.VISITOR) + @ApiProperty({ + description: "Get one announcements with id", + }) + @Get(":id") + getAnnouncement(@Param() paramDto: ParamDto, @UserDec() user: User) { + return this.announcementService.getOneAnnouncement(paramDto.id, user.id); } @AuthGuards() @@ -46,7 +69,7 @@ export class AnnouncementController { @ApiProperty({ description: "Get all public announcements", }) - @Get(":serviceId") + @Get("/service/:serviceId") async getAnnouncementsByService(@Query() queryDto: SearchCriticismQueryDto, @Param("serviceId") serviceId: string) { return await this.announcementService.getAnnouncementByService(queryDto, serviceId); } diff --git a/src/modules/announcements/announcement.module.ts b/src/modules/announcements/announcement.module.ts index daa5505..6a0fa61 100644 --- a/src/modules/announcements/announcement.module.ts +++ b/src/modules/announcements/announcement.module.ts @@ -7,10 +7,12 @@ 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"; +import { UserAnnouncement } from "./entities/user-announcement.entity"; +import { UserAnnouncementRepository } from "./repositories/user-announcement.repository"; @Module({ - imports: [TypeOrmModule.forFeature([Announcement]), DanakServicesModule, UsersModule], - providers: [AnnouncementService, AnnouncementRepository], + imports: [TypeOrmModule.forFeature([Announcement, UserAnnouncement]), DanakServicesModule, UsersModule], + providers: [AnnouncementService, AnnouncementRepository, UserAnnouncementRepository], controllers: [AnnouncementController], exports: [TypeOrmModule], }) diff --git a/src/modules/announcements/announcement.service.ts b/src/modules/announcements/announcement.service.ts index 31bedbd..7692e53 100644 --- a/src/modules/announcements/announcement.service.ts +++ b/src/modules/announcements/announcement.service.ts @@ -1,9 +1,10 @@ -import { Injectable } from "@nestjs/common"; +import { BadRequestException, Injectable } from "@nestjs/common"; 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 { UserAnnouncementRepository } from "./repositories/user-announcement.repository"; +import { AnnouncementMessage, 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"; @@ -12,6 +13,7 @@ import { PaginationUtils } from "../utils/providers/pagination.utils"; export class AnnouncementService { constructor( private readonly announcementRepository: AnnouncementRepository, + private readonly userAnnouncementRepository: UserAnnouncementRepository, private readonly danakServicesService: DanakServicesService, private readonly usersService: UsersService, ) {} @@ -38,7 +40,7 @@ export class AnnouncementService { }; } - async getAllAnnouncements(queryDto: SearchAnnouncementQueryDto) { + async getAllAnnouncements(queryDto: SearchAnnouncementQueryDto, userId: string) { const { limit, skip } = PaginationUtils(queryDto); const queryBuilder = this.announcementRepository @@ -46,7 +48,8 @@ export class AnnouncementService { .leftJoin("announcement.service", "danak_service") .addSelect(["danak_service.id", "danak_service.name"]) .leftJoin("announcement.targetGroups", "group") - .addSelect(["group.id", "group.name"]); + .addSelect(["group.id", "group.name"]) + .leftJoinAndSelect("announcement.userAnnouncements", "userAnnouncements", "userAnnouncements.userId = :userId", { userId }); if (queryDto.since) { queryBuilder.andWhere("announcement.createdAt >= :since", { since: queryDto.since }); @@ -67,6 +70,68 @@ export class AnnouncementService { return { announcements, count }; } + async getAllAnnouncementsByUser(queryDto: SearchAnnouncementQueryDto, userId: string) { + 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"]) + .leftJoin("group.users", "groupUser") + .leftJoinAndSelect("announcement.userAnnouncements", "userAnnouncements", "userAnnouncements.userId = :userId", { userId }) + .where("announcement.isPublic = :isPublic", { isPublic: true }) // Check if the announcement is public + .orWhere("groupUser.id = :userId", { userId }); + + 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 rawResults = await queryBuilder.getRawMany(); + + const [, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount(); + + const transformedResults = rawResults.map((raw) => ({ + id: raw.announcement_id, + createdAt: raw.announcement_createdAt, + updatedAt: raw.announcement_updatedAt, + title: raw.announcement_title, + content: raw.announcement_content, + publishAt: raw.announcement_publishAt, + service: { + id: raw.danak_service_id, + name: raw.danak_service_name, + }, + isImportant: raw.announcement_isImportant, + targetGroups: raw.group_id ? [{ id: raw.group_id, name: raw.group_name }] : [], + isPublic: raw.announcement_isPublic, + isRead: raw.userAnnouncements_isRead ? true : false, + })); + + return { transformedResults, count }; + } + + async getOneAnnouncement(id: string, userId: string) { + const announcement = await this.announcementRepository.findOneBy({ id }); + if (!announcement) throw new BadRequestException(AnnouncementMessage.NOT_FOUND); + const userAnnouncement = this.userAnnouncementRepository.create({ + announcement: { id: announcement.id }, + user: { id: userId }, + isRead: true, + }); + await this.userAnnouncementRepository.save(userAnnouncement); + return { announcement }; + } + 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 b675fe0..498bb66 100644 --- a/src/modules/announcements/entities/announcement.entity.ts +++ b/src/modules/announcements/entities/announcement.entity.ts @@ -1,5 +1,6 @@ -import { Column, Entity, Index, ManyToMany, ManyToOne } from "typeorm"; +import { Column, Entity, Index, ManyToMany, ManyToOne, OneToMany } from "typeorm"; +import { UserAnnouncement } from "./user-announcement.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DanakService } from "../../danak-services/entities/danak-service.entity"; import { UserGroup } from "../../users/entities/user-group.entity"; @@ -32,4 +33,7 @@ export class Announcement extends BaseEntity { isPublicAnnouncement(): boolean { return this.isPublic; } + + @OneToMany(() => UserAnnouncement, (userAnnouncement) => userAnnouncement.announcement) + userAnnouncements: UserAnnouncement[]; } diff --git a/src/modules/announcements/entities/user-announcement.entity.ts b/src/modules/announcements/entities/user-announcement.entity.ts index e69de29..05ca0c8 100644 --- a/src/modules/announcements/entities/user-announcement.entity.ts +++ b/src/modules/announcements/entities/user-announcement.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; + +import { Announcement } from "./announcement.entity"; +import { BaseEntity } from "../../../common/entities/base.entity"; +import { User } from "../../users/entities/user.entity"; // Assuming you have a User entity + +@Entity() +export class UserAnnouncement extends BaseEntity { + @ManyToOne(() => User, (user) => user.userAnnouncements, { onDelete: "CASCADE" }) + @JoinColumn({ name: "userId" }) + user: User; + + @ManyToOne(() => Announcement, (announcement) => announcement.userAnnouncements, { onDelete: "CASCADE" }) + @JoinColumn({ name: "announcementId" }) + announcement: Announcement; + + @Column({ type: "boolean", default: false }) + isRead: boolean; +} diff --git a/src/modules/announcements/repositories/user-announcement.repository.ts b/src/modules/announcements/repositories/user-announcement.repository.ts new file mode 100644 index 0000000..869534c --- /dev/null +++ b/src/modules/announcements/repositories/user-announcement.repository.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { UserAnnouncement } from "../entities/user-announcement.entity"; + +@Injectable() +export class UserAnnouncementRepository extends Repository { + constructor(@InjectRepository(UserAnnouncement) userAnnouncementRepository: Repository) { + super(userAnnouncementRepository.target, userAnnouncementRepository.manager, userAnnouncementRepository.queryRunner); + } +} diff --git a/src/modules/user-settings/entities/user-settings.entity.ts b/src/modules/user-settings/entities/user-settings.entity.ts new file mode 100644 index 0000000..2b7b778 --- /dev/null +++ b/src/modules/user-settings/entities/user-settings.entity.ts @@ -0,0 +1,12 @@ +import { Column, Entity } from "typeorm"; + +import { BaseEntity } from "../../../common/entities/base.entity"; + +@Entity() +export class UserSettings extends BaseEntity { + @Column({ type: "varchar", length: 150 }) + title: string; + + @Column({ type: "json" }) + fields: Record; +} diff --git a/src/modules/user-settings/entities/user-setttings.entity.ts b/src/modules/user-settings/entities/user-setttings.entity.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/modules/user-settings/providers/user-setttings.service.ts b/src/modules/user-settings/providers/user-setttings.service.ts index e69de29..7434936 100644 --- a/src/modules/user-settings/providers/user-setttings.service.ts +++ b/src/modules/user-settings/providers/user-setttings.service.ts @@ -0,0 +1,6 @@ +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class UserSettingsService { + constructor() {} +} diff --git a/src/modules/user-settings/repositories/user-settings.repository.ts b/src/modules/user-settings/repositories/user-settings.repository.ts new file mode 100644 index 0000000..2ffc623 --- /dev/null +++ b/src/modules/user-settings/repositories/user-settings.repository.ts @@ -0,0 +1,12 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { UserSettings } from "../entities/user-settings.entity"; + +@Injectable() +export class UserSettingsRepository extends Repository { + constructor(@InjectRepository(UserSettings) userSettingsRepository: Repository) { + super(userSettingsRepository.target, userSettingsRepository.manager, userSettingsRepository.queryRunner); + } +} diff --git a/src/modules/user-settings/user-settings.controller.ts b/src/modules/user-settings/user-settings.controller.ts new file mode 100644 index 0000000..d8ba68a --- /dev/null +++ b/src/modules/user-settings/user-settings.controller.ts @@ -0,0 +1,6 @@ +import { Controller } from "@nestjs/common"; + +@Controller("user-settings") +export class UserSettingsController { + constructor() {} +} diff --git a/src/modules/user-settings/user-settings.module.ts b/src/modules/user-settings/user-settings.module.ts index e69de29..f3f692c 100644 --- a/src/modules/user-settings/user-settings.module.ts +++ b/src/modules/user-settings/user-settings.module.ts @@ -0,0 +1,15 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; + +import { UserSettings } from "./entities/user-settings.entity"; +import { UserSettingsService } from "./providers/user-setttings.service"; +import { UserSettingsRepository } from "./repositories/user-settings.repository"; +import { UserSettingsController } from "./user-settings.controller"; + +@Module({ + imports: [TypeOrmModule.forFeature([UserSettings])], + providers: [UserSettingsService, UserSettingsRepository], + controllers: [UserSettingsController], + exports: [TypeOrmModule], +}) +export class UserSettingsModule {} diff --git a/src/modules/user-settings/user-setttings.controller.ts b/src/modules/user-settings/user-setttings.controller.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/modules/users/entities/user.entity.ts b/src/modules/users/entities/user.entity.ts index 9855913..1fd5618 100644 --- a/src/modules/users/entities/user.entity.ts +++ b/src/modules/users/entities/user.entity.ts @@ -4,6 +4,7 @@ import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany } from "typ import { Role } from "./role.entity"; import { UserGroup } from "./user-group.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; +import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity"; import { Criticism } from "../../criticisms/entities/criticism.entity"; import { DanakService } from "../../danak-services/entities/danak-service.entity"; import { TicketMessage } from "../../tickets/entities/ticket-message.entity"; @@ -54,4 +55,7 @@ export class User extends BaseEntity { @ManyToMany(() => DanakService, (danakService) => danakService.users) @JoinTable() danakServices: DanakService[]; + + @OneToMany(() => UserAnnouncement, (userAnnouncement) => userAnnouncement.user) + userAnnouncements: UserAnnouncement[]; }