Skip to content

Commit be3646a

Browse files
authored
Merge pull request #577 from johanbook/notifications
feat(api): add notification entity
2 parents deff9ee + cabb036 commit be3646a

File tree

10 files changed

+161
-8
lines changed

10 files changed

+161
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export class NotificationDetails {
2+
id!: string;
3+
resourcePath!: string;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { BaseQuery } from "src/core/query";
2+
3+
export class GetNotificationListQuery extends BaseQuery {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { IQueryHandler, QueryHandler } from "@nestjs/cqrs";
2+
import { InjectRepository } from "@nestjs/typeorm";
3+
import { Repository } from "typeorm";
4+
5+
import { mapArray } from "src/core/mapper";
6+
import { QueryService } from "src/core/query";
7+
import { CurrentOrganizationService } from "src/features/organizations";
8+
import { CurrentProfileService } from "src/features/profiles";
9+
10+
import { Notification } from "../../../infrastructure/entities/notification.entity";
11+
import { NotificationDetails } from "../../contracts/dtos/notification.dto";
12+
import { GetNotificationListQuery } from "../../contracts/queries/get-notification-list.query";
13+
14+
@QueryHandler(GetNotificationListQuery)
15+
export class GetNotificationListHandler
16+
implements IQueryHandler<GetNotificationListQuery, NotificationDetails[]>
17+
{
18+
constructor(
19+
private readonly currentOrganizationService: CurrentOrganizationService,
20+
private readonly currentProfileService: CurrentProfileService,
21+
@InjectRepository(Notification)
22+
private readonly notifications: Repository<Notification>,
23+
private readonly queryService: QueryService<Notification>,
24+
) {}
25+
26+
async execute(query: GetNotificationListQuery) {
27+
const organizationId =
28+
await this.currentOrganizationService.fetchCurrentOrganizationId();
29+
const profileId = await this.currentProfileService.fetchCurrentProfileId();
30+
31+
const fountNotifications = await this.queryService.find(
32+
this.notifications,
33+
{
34+
required: {
35+
where: {
36+
organizationId,
37+
profileId,
38+
},
39+
},
40+
query,
41+
},
42+
);
43+
44+
return mapArray(
45+
NotificationDetails,
46+
fountNotifications,
47+
(notification) => ({
48+
id: notification.id,
49+
resourcePath: notification.resourcePath,
50+
}),
51+
);
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Controller, Get, Query } from "@nestjs/common";
2+
import { QueryBus } from "@nestjs/cqrs";
3+
import { ApiTags } from "@nestjs/swagger";
4+
5+
import { NotificationDetails } from "../../application/contracts/dtos/notification.dto";
6+
import { GetNotificationListQuery } from "../../application/contracts/queries/get-notification-list.query";
7+
8+
@Controller("notifications")
9+
@ApiTags("notifications")
10+
export class NotificationsController {
11+
constructor(private queryBus: QueryBus) {}
12+
13+
@Get()
14+
async getNotifactions(
15+
@Query() query: GetNotificationListQuery,
16+
): Promise<NotificationDetails[]> {
17+
return await this.queryBus.execute(query);
18+
}
19+
}

services/api/src/core/notifications/notification.service.ts renamed to services/api/src/core/notifications/domain/services/notification.service.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { InjectRepository } from "@nestjs/typeorm";
33
import { In, Not, Repository } from "typeorm";
44

55
import { UserIdService } from "src/core/authentication";
6+
import { EmailService } from "src/core/email/domain/services/email.service";
67
import { Logger } from "src/core/logging";
78
import { OrganizationMembership } from "src/features/organizations/infrastructure/entities/organization-membership.entity";
89
import { Profile } from "src/features/profiles";
910
import { getRequiredStringConfig } from "src/utils/config.helper";
1011

11-
import { EmailService } from "../email/domain/services/email.service";
12-
import { NotificationGateway } from "./notification.gateway";
13-
import { INotification } from "./types";
12+
import { NotificationGateway } from "../../notification.gateway";
13+
import { INotification } from "../../types";
1414

1515
const UI_DOMAIN = getRequiredStringConfig("UI_DOMAIN");
1616

Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export { NotificationEventsConstants } from "./constants/notification-events.constants";
2-
export { NotificationService } from "./notification.service";
2+
export { NotificationService } from "./domain/services/notification.service";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Column, Entity, ManyToOne } from "typeorm";
2+
3+
import { BaseEntity } from "src/core/database";
4+
import { Organization } from "src/features/organizations";
5+
import { Profile } from "src/features/profiles";
6+
7+
@Entity()
8+
export class Notification extends BaseEntity {
9+
@Column({ type: "varchar", length: 4096, default: "" })
10+
description!: string;
11+
12+
@Column({ type: "varchar", length: 2048, default: "" })
13+
message!: string;
14+
15+
@ManyToOne(() => Organization)
16+
organization!: Organization;
17+
18+
@Column()
19+
organizationId!: number;
20+
21+
@ManyToOne(() => Profile)
22+
profile!: Profile;
23+
24+
@Column()
25+
profileId!: number;
26+
27+
@Column()
28+
read!: boolean;
29+
30+
@Column({
31+
type: "timestamp without time zone",
32+
})
33+
readAt!: Date;
34+
35+
@Column({ type: "varchar", length: 2048, default: "" })
36+
resourcePath!: string;
37+
38+
@Column({ type: "varchar", length: 256, default: "" })
39+
type!: string;
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class AddNotification1698494107594 implements MigrationInterface {
4+
name = 'AddNotification1698494107594'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
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"))`);
8+
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_4bb0507e70fc50c02e221326f8e" FOREIGN KEY ("organizationId") REFERENCES "organization"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
9+
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_4dd039be3d37179110ff3e14901" FOREIGN KEY ("profileId") REFERENCES "profile"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
10+
}
11+
12+
public async down(queryRunner: QueryRunner): Promise<void> {
13+
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_4dd039be3d37179110ff3e14901"`);
14+
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_4bb0507e70fc50c02e221326f8e"`);
15+
await queryRunner.query(`DROP TABLE "notification"`);
16+
}
17+
18+
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
import { Module } from "@nestjs/common";
2+
import { CqrsModule } from "@nestjs/cqrs";
23
import { TypeOrmModule } from "@nestjs/typeorm";
34

45
import { EmailModule } from "src/core/email/email.module";
56
import { OrganizationMembership } from "src/features/organizations/infrastructure/entities/organization-membership.entity";
7+
import { OrganizationModule } from "src/features/organizations/organization.module";
68
import { Profile } from "src/features/profiles";
9+
import { ProfileModule } from "src/features/profiles/profile.module";
710

811
import { AuthenticationModule } from "../authentication/authentication.module";
12+
import { QueryModule } from "../query/query.module";
13+
import { GetNotificationListHandler } from "./application/handlers/query-handlers/get-notification-list.handler";
14+
import { NotificationsController } from "./client/controllers/notifications.controller";
15+
import { NotificationService } from "./domain/services/notification.service";
16+
import { Notification } from "./infrastructure/entities/notification.entity";
917
import { NotificationGateway } from "./notification.gateway";
10-
import { NotificationService } from "./notification.service";
1118

1219
@Module({
20+
controllers: [NotificationsController],
1321
exports: [NotificationService],
1422
imports: [
1523
AuthenticationModule,
24+
CqrsModule,
1625
EmailModule,
17-
TypeOrmModule.forFeature([OrganizationMembership, Profile]),
26+
OrganizationModule,
27+
ProfileModule,
28+
QueryModule,
29+
TypeOrmModule.forFeature([Notification, OrganizationMembership, Profile]),
30+
],
31+
providers: [
32+
NotificationGateway,
33+
GetNotificationListHandler,
34+
NotificationService,
1835
],
19-
providers: [NotificationGateway, NotificationService],
2036
})
2137
export class NotificationModule {}

services/api/src/core/notifications/test/mocks/notification.service.mock.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NotificationService } from "../../notification.service";
1+
import { NotificationService } from "../../domain/services/notification.service";
22

33
/* eslint-disable unicorn/consistent-function-scoping */
44

0 commit comments

Comments
 (0)