diff --git a/services/api/src/core/notifications/application/contracts/dtos/notification.dto.ts b/services/api/src/core/notifications/application/contracts/dtos/notification.dto.ts new file mode 100644 index 00000000..73d0439f --- /dev/null +++ b/services/api/src/core/notifications/application/contracts/dtos/notification.dto.ts @@ -0,0 +1,4 @@ +export class NotificationDetails { + id!: string; + resourcePath!: string; +} diff --git a/services/api/src/core/notifications/application/contracts/queries/get-notification-list.query.ts b/services/api/src/core/notifications/application/contracts/queries/get-notification-list.query.ts new file mode 100644 index 00000000..0de05bdb --- /dev/null +++ b/services/api/src/core/notifications/application/contracts/queries/get-notification-list.query.ts @@ -0,0 +1,3 @@ +import { BaseQuery } from "src/core/query"; + +export class GetNotificationListQuery extends BaseQuery {} diff --git a/services/api/src/core/notifications/application/handlers/query-handlers/get-notification-list.handler.ts b/services/api/src/core/notifications/application/handlers/query-handlers/get-notification-list.handler.ts new file mode 100644 index 00000000..8b386d36 --- /dev/null +++ b/services/api/src/core/notifications/application/handlers/query-handlers/get-notification-list.handler.ts @@ -0,0 +1,53 @@ +import { IQueryHandler, QueryHandler } from "@nestjs/cqrs"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; + +import { mapArray } from "src/core/mapper"; +import { QueryService } from "src/core/query"; +import { CurrentOrganizationService } from "src/features/organizations"; +import { CurrentProfileService } from "src/features/profiles"; + +import { Notification } from "../../../infrastructure/entities/notification.entity"; +import { NotificationDetails } from "../../contracts/dtos/notification.dto"; +import { GetNotificationListQuery } from "../../contracts/queries/get-notification-list.query"; + +@QueryHandler(GetNotificationListQuery) +export class GetNotificationListHandler + implements IQueryHandler +{ + constructor( + private readonly currentOrganizationService: CurrentOrganizationService, + private readonly currentProfileService: CurrentProfileService, + @InjectRepository(Notification) + private readonly notifications: Repository, + private readonly queryService: QueryService, + ) {} + + async execute(query: GetNotificationListQuery) { + const organizationId = + await this.currentOrganizationService.fetchCurrentOrganizationId(); + const profileId = await this.currentProfileService.fetchCurrentProfileId(); + + const fountNotifications = await this.queryService.find( + this.notifications, + { + required: { + where: { + organizationId, + profileId, + }, + }, + query, + }, + ); + + return mapArray( + NotificationDetails, + fountNotifications, + (notification) => ({ + id: notification.id, + resourcePath: notification.resourcePath, + }), + ); + } +} diff --git a/services/api/src/core/notifications/client/controllers/notifications.controller.ts b/services/api/src/core/notifications/client/controllers/notifications.controller.ts new file mode 100644 index 00000000..040aa7e9 --- /dev/null +++ b/services/api/src/core/notifications/client/controllers/notifications.controller.ts @@ -0,0 +1,19 @@ +import { Controller, Get, Query } from "@nestjs/common"; +import { QueryBus } from "@nestjs/cqrs"; +import { ApiTags } from "@nestjs/swagger"; + +import { NotificationDetails } from "../../application/contracts/dtos/notification.dto"; +import { GetNotificationListQuery } from "../../application/contracts/queries/get-notification-list.query"; + +@Controller("notifications") +@ApiTags("notifications") +export class NotificationsController { + constructor(private queryBus: QueryBus) {} + + @Get() + async getNotifactions( + @Query() query: GetNotificationListQuery, + ): Promise { + return await this.queryBus.execute(query); + } +} diff --git a/services/api/src/core/notifications/notification.service.ts b/services/api/src/core/notifications/domain/services/notification.service.ts similarity index 94% rename from services/api/src/core/notifications/notification.service.ts rename to services/api/src/core/notifications/domain/services/notification.service.ts index 48dd0013..223e7a26 100644 --- a/services/api/src/core/notifications/notification.service.ts +++ b/services/api/src/core/notifications/domain/services/notification.service.ts @@ -3,14 +3,14 @@ import { InjectRepository } from "@nestjs/typeorm"; import { In, Not, Repository } from "typeorm"; import { UserIdService } from "src/core/authentication"; +import { EmailService } from "src/core/email/domain/services/email.service"; import { Logger } from "src/core/logging"; import { OrganizationMembership } from "src/features/organizations/infrastructure/entities/organization-membership.entity"; import { Profile } from "src/features/profiles"; import { getRequiredStringConfig } from "src/utils/config.helper"; -import { EmailService } from "../email/domain/services/email.service"; -import { NotificationGateway } from "./notification.gateway"; -import { INotification } from "./types"; +import { NotificationGateway } from "../../notification.gateway"; +import { INotification } from "../../types"; const UI_DOMAIN = getRequiredStringConfig("UI_DOMAIN"); diff --git a/services/api/src/core/notifications/index.ts b/services/api/src/core/notifications/index.ts index 90af7e7f..5cd805ef 100644 --- a/services/api/src/core/notifications/index.ts +++ b/services/api/src/core/notifications/index.ts @@ -1,2 +1,2 @@ export { NotificationEventsConstants } from "./constants/notification-events.constants"; -export { NotificationService } from "./notification.service"; +export { NotificationService } from "./domain/services/notification.service"; diff --git a/services/api/src/core/notifications/infrastructure/entities/notification.entity.ts b/services/api/src/core/notifications/infrastructure/entities/notification.entity.ts new file mode 100644 index 00000000..f2a5f4c5 --- /dev/null +++ b/services/api/src/core/notifications/infrastructure/entities/notification.entity.ts @@ -0,0 +1,40 @@ +import { Column, Entity, ManyToOne } from "typeorm"; + +import { BaseEntity } from "src/core/database"; +import { Organization } from "src/features/organizations"; +import { Profile } from "src/features/profiles"; + +@Entity() +export class Notification extends BaseEntity { + @Column({ type: "varchar", length: 4096, default: "" }) + description!: string; + + @Column({ type: "varchar", length: 2048, default: "" }) + message!: string; + + @ManyToOne(() => Organization) + organization!: Organization; + + @Column() + organizationId!: number; + + @ManyToOne(() => Profile) + profile!: Profile; + + @Column() + profileId!: number; + + @Column() + read!: boolean; + + @Column({ + type: "timestamp without time zone", + }) + readAt!: Date; + + @Column({ type: "varchar", length: 2048, default: "" }) + resourcePath!: string; + + @Column({ type: "varchar", length: 256, default: "" }) + type!: string; +} diff --git a/services/api/src/core/notifications/infrastructure/migrations/1698494107594-AddNotification.ts b/services/api/src/core/notifications/infrastructure/migrations/1698494107594-AddNotification.ts new file mode 100644 index 00000000..bbece07f --- /dev/null +++ b/services/api/src/core/notifications/infrastructure/migrations/1698494107594-AddNotification.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddNotification1698494107594 implements MigrationInterface { + name = 'AddNotification1698494107594' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "notification" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "description" character varying(4096) NOT NULL DEFAULT '', "message" character varying(2048) NOT NULL DEFAULT '', "organizationId" integer NOT NULL, "profileId" integer NOT NULL, "read" boolean NOT NULL, "readAt" TIMESTAMP NOT NULL, "resourcePath" character varying(2048) NOT NULL DEFAULT '', "type" character varying(256) NOT NULL DEFAULT '', CONSTRAINT "PK_705b6c7cdf9b2c2ff7ac7872cb7" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_4bb0507e70fc50c02e221326f8e" FOREIGN KEY ("organizationId") REFERENCES "organization"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_4dd039be3d37179110ff3e14901" FOREIGN KEY ("profileId") REFERENCES "profile"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_4dd039be3d37179110ff3e14901"`); + await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_4bb0507e70fc50c02e221326f8e"`); + await queryRunner.query(`DROP TABLE "notification"`); + } + +} diff --git a/services/api/src/core/notifications/notification.module.ts b/services/api/src/core/notifications/notification.module.ts index cbce82ac..6bcfc538 100644 --- a/services/api/src/core/notifications/notification.module.ts +++ b/services/api/src/core/notifications/notification.module.ts @@ -1,21 +1,37 @@ import { Module } from "@nestjs/common"; +import { CqrsModule } from "@nestjs/cqrs"; import { TypeOrmModule } from "@nestjs/typeorm"; import { EmailModule } from "src/core/email/email.module"; import { OrganizationMembership } from "src/features/organizations/infrastructure/entities/organization-membership.entity"; +import { OrganizationModule } from "src/features/organizations/organization.module"; import { Profile } from "src/features/profiles"; +import { ProfileModule } from "src/features/profiles/profile.module"; import { AuthenticationModule } from "../authentication/authentication.module"; +import { QueryModule } from "../query/query.module"; +import { GetNotificationListHandler } from "./application/handlers/query-handlers/get-notification-list.handler"; +import { NotificationsController } from "./client/controllers/notifications.controller"; +import { NotificationService } from "./domain/services/notification.service"; +import { Notification } from "./infrastructure/entities/notification.entity"; import { NotificationGateway } from "./notification.gateway"; -import { NotificationService } from "./notification.service"; @Module({ + controllers: [NotificationsController], exports: [NotificationService], imports: [ AuthenticationModule, + CqrsModule, EmailModule, - TypeOrmModule.forFeature([OrganizationMembership, Profile]), + OrganizationModule, + ProfileModule, + QueryModule, + TypeOrmModule.forFeature([Notification, OrganizationMembership, Profile]), + ], + providers: [ + NotificationGateway, + GetNotificationListHandler, + NotificationService, ], - providers: [NotificationGateway, NotificationService], }) export class NotificationModule {} diff --git a/services/api/src/core/notifications/test/mocks/notification.service.mock.ts b/services/api/src/core/notifications/test/mocks/notification.service.mock.ts index e8bd1151..d03fb479 100644 --- a/services/api/src/core/notifications/test/mocks/notification.service.mock.ts +++ b/services/api/src/core/notifications/test/mocks/notification.service.mock.ts @@ -1,4 +1,4 @@ -import { NotificationService } from "../../notification.service"; +import { NotificationService } from "../../domain/services/notification.service"; /* eslint-disable unicorn/consistent-function-scoping */