diff --git a/src/common/entities/base.entity.ts b/src/common/entities/base.entity.ts index cca19c8..b5cdca3 100644 --- a/src/common/entities/base.entity.ts +++ b/src/common/entities/base.entity.ts @@ -4,7 +4,9 @@ export abstract class BaseEntity { @PrimaryGeneratedColumn("uuid") id: string; - @CreateDateColumn() + @CreateDateColumn({ + type: "timestamptz", + }) createdAt: Date; @UpdateDateColumn() diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index e94720d..de2e532 100644 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -267,6 +267,6 @@ export const enum NotificationMessage { USERID_IS_UUID = "شناسه کاربر باید یک UUID معتبر باشد", USERID_IS_STRING = "شناسه کاربر باید یک رشته متنی باشد", LOGIN = "لاگین", - LOGIN_MESSAGE = "در تاریخ [DATE] یک لاگین به حساب کاربری شما صورت گرفت", + LOGIN_MESSAGE = "یک لاگین به حساب کاربری شما صورت گرفت", NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED = "اعلان یافت نشد یا دسترسی غیرمجاز است", } diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index cf7bb31..bd8ecdc 100644 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -89,6 +89,8 @@ export class AuthService { const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); + await this.notificationService.createLoginNotification(user.id); + return { message: AuthMessage.PASSWORD_LOGIN_SUCCESS, ...tokens, @@ -159,8 +161,7 @@ export class AuthService { const tokens = this.tokensService.generateAccessAndRefreshToken({ sub: user.id, role: user.role.name }); - const { notification } = await this.notificationService.createLoginNotification(user.id); - console.log(notification); + await this.notificationService.createLoginNotification(user.id); return { message: AuthMessage.LOGIN_SUCCESS, ...tokens, diff --git a/src/modules/notifications/DTO/search-notification-query.dto.ts b/src/modules/notifications/DTO/search-notification-query.dto.ts new file mode 100644 index 0000000..416346f --- /dev/null +++ b/src/modules/notifications/DTO/search-notification-query.dto.ts @@ -0,0 +1,12 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { IsOptional } from "class-validator"; + +import { PaginationDto } from "../../../common/DTO/pagination.dto"; + +export class SearchNotificationQueryDto extends PaginationDto { + @IsOptional() + @Type(() => Number) + @ApiPropertyOptional({ description: "Notification status", example: 1 }) + isRead?: number; +} diff --git a/src/modules/notifications/notifications.controller.ts b/src/modules/notifications/notifications.controller.ts index d669cd7..92ca9c5 100644 --- a/src/modules/notifications/notifications.controller.ts +++ b/src/modules/notifications/notifications.controller.ts @@ -1,11 +1,11 @@ import { Controller, Get, Param, Patch, Query } from "@nestjs/common"; import { ApiOperation, ApiTags } from "@nestjs/swagger"; +import { SearchNotificationQueryDto } from "./DTO/search-notification-query.dto"; import { NotificationsService } from "./providers/notifications.service"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; -import { PaginationDto } from "../../common/DTO/pagination.dto"; @Controller("notifications") @ApiTags("Notifications") @@ -18,9 +18,9 @@ export class NotificationController { @Pagination() @AuthGuards() @Get() - getAllNotifications(@UserDec("id") userId: string, @Query() paginationDto: PaginationDto) { + getAllNotifications(@UserDec("id") userId: string, @Query() queryDto: SearchNotificationQueryDto) { console.log(userId); - return this.notificationService.getAllNotifications(paginationDto, userId); + return this.notificationService.getAllNotifications(queryDto, userId); } //********************* */ @@ -31,4 +31,13 @@ export class NotificationController { markAsRead(@Param("id") id: string, @UserDec("id") userId: string) { return this.notificationService.markAsRead(id, userId); } + + //********************* */ + + @ApiOperation({ summary: "mark user all notification as read" }) + @AuthGuards() + @Patch("read-all") + markAllAsRead(@UserDec("id") userId: string) { + return this.notificationService.markAllAsRead(userId); + } } diff --git a/src/modules/notifications/providers/notifications.service.ts b/src/modules/notifications/providers/notifications.service.ts index a5a193d..7e9eeae 100644 --- a/src/modules/notifications/providers/notifications.service.ts +++ b/src/modules/notifications/providers/notifications.service.ts @@ -1,10 +1,9 @@ import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common"; -import { PaginationDto } from "../../../common/DTO/pagination.dto"; -import { NotificationMessage, UserMessage } from "../../../common/enums/message.enum"; +import { CommonMessage, NotificationMessage, UserMessage } from "../../../common/enums/message.enum"; import { UsersService } from "../../users/providers/users.service"; -import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { CreateNotificationDto } from "../DTO/create-notification.dto"; +import { SearchNotificationQueryDto } from "../DTO/search-notification-query.dto"; import { NotificationRepository } from "../repositories/notifications.repository"; @Injectable() @@ -44,22 +43,39 @@ export class NotificationsService { //************************ */ async createLoginNotification(recipientId: string) { - const message = NotificationMessage.LOGIN_MESSAGE.replace("[DATE]", new Date().toLocaleString("fa-IR")); + const message = NotificationMessage.LOGIN_MESSAGE; return this.createNotification({ title: NotificationMessage.LOGIN, message, recipientId }); } //************************ */ - async getAllNotifications(paginationDto: PaginationDto, recipientId: string) { - const { skip, limit } = PaginationUtils(paginationDto); - const [notifications, count] = await this.notificationRepository - .createQueryBuilder("notification") - .where("notification.recipientId = :recipientId", { recipientId }) - .orderBy("notification.createdAt", "DESC") - .skip(skip) - .take(limit) - .getManyAndCount(); + async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) { + return await this.notificationRepository.getAllNotifications(queryDto, recipientId); + } - return { notifications, count, paginate: true }; + //************************ */ + + async getOneNotification(id: string) { + const notification = await this.notificationRepository.findOneBy({ id }); + if (!notification) throw new BadRequestException(NotificationMessage.NOTIFICATION_NOT_FOUND_OR_ACCESS_DENIED); + return { notification }; + } + + //************************ */ + + async markAllAsRead(userId: string) { + await this.notificationRepository.update( + { + recipient: { + id: userId, + }, + }, + { + isRead: true, + }, + ); + return { + message: CommonMessage.UPDATE_SUCCESS, + }; } } diff --git a/src/modules/notifications/repositories/notifications.repository.ts b/src/modules/notifications/repositories/notifications.repository.ts index eaef556..1bb1ce6 100644 --- a/src/modules/notifications/repositories/notifications.repository.ts +++ b/src/modules/notifications/repositories/notifications.repository.ts @@ -2,6 +2,8 @@ import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; +import { PaginationUtils } from "../../utils/providers/pagination.utils"; +import { SearchNotificationQueryDto } from "../DTO/search-notification-query.dto"; import { Notification } from "../entities/notification.entity"; @Injectable() @@ -9,4 +11,18 @@ export class NotificationRepository extends Repository { constructor(@InjectRepository(Notification) notificationRepository: Repository) { super(notificationRepository.target, notificationRepository.manager, notificationRepository.queryRunner); } + + async getAllNotifications(queryDto: SearchNotificationQueryDto, recipientId: string) { + const { skip, limit } = PaginationUtils(queryDto); + + const queryBuilder = this.createQueryBuilder("notification").where("notification.recipientId = :recipientId", { recipientId }); + + if (queryDto.isRead !== undefined) { + queryBuilder.andWhere("notification.isRead = :isRead", { isRead: queryDto.isRead === 1 }); + } + + const [notifications, count] = await queryBuilder.orderBy("notification.createdAt", "DESC").skip(skip).take(limit).getManyAndCount(); + + return { notifications, count, paginate: true }; + } }