feat: add notification data model and migration
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddNotificationTables1785600000000 implements MigrationInterface {
|
||||||
|
name = 'AddNotificationTables1785600000000';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE TABLE "notification" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"teamId" integer NOT NULL,
|
||||||
|
"event" character varying NOT NULL,
|
||||||
|
"actorUserId" integer NOT NULL,
|
||||||
|
"payload" text NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT "PK_notification_id" PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_notification_team_id" ON "notification" ("teamId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "notification"
|
||||||
|
ADD CONSTRAINT "FK_notification_team"
|
||||||
|
FOREIGN KEY ("teamId") REFERENCES "team"("id")
|
||||||
|
ON DELETE CASCADE
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE TABLE "notification_recipient" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"notificationId" integer NOT NULL,
|
||||||
|
"userId" integer NOT NULL,
|
||||||
|
"read" boolean NOT NULL DEFAULT false,
|
||||||
|
"readAt" TIMESTAMP,
|
||||||
|
CONSTRAINT "PK_notification_recipient_id" PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_notification_recipient_notification_id" ON "notification_recipient" ("notificationId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_notification_recipient_user_id" ON "notification_recipient" ("userId")`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "notification_recipient"
|
||||||
|
ADD CONSTRAINT "FK_notification_recipient_notification"
|
||||||
|
FOREIGN KEY ("notificationId") REFERENCES "notification"("id")
|
||||||
|
ON DELETE CASCADE
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP TABLE "notification_recipient"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "notification"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
describe('AddNotificationTables1785600000000', () => {
|
||||||
|
it('creates the notification and notification_recipient tables with their indexes and foreign keys', async () => {
|
||||||
|
const migrationModule = require('./1785600000000-AddNotificationTables');
|
||||||
|
const migration = new migrationModule.AddNotificationTables1785600000000();
|
||||||
|
const queryRunner = { query: jest.fn() } as any;
|
||||||
|
|
||||||
|
await migration.up(queryRunner);
|
||||||
|
|
||||||
|
const calls: string[] = queryRunner.query.mock.calls.map((c: any) => c[0]);
|
||||||
|
expect(calls).toHaveLength(7);
|
||||||
|
expect(calls.some((sql) => sql.includes('CREATE TABLE "notification"'))).toBe(true);
|
||||||
|
expect(calls.some((sql) => sql.includes('CREATE TABLE "notification_recipient"'))).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(calls.some((sql) => sql.includes('IDX_notification_team_id'))).toBe(true);
|
||||||
|
expect(
|
||||||
|
calls.some((sql) => sql.includes('IDX_notification_recipient_notification_id')),
|
||||||
|
).toBe(true);
|
||||||
|
expect(calls.some((sql) => sql.includes('IDX_notification_recipient_user_id'))).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(calls.some((sql) => sql.includes('FK_notification_team'))).toBe(true);
|
||||||
|
expect(calls.some((sql) => sql.includes('FK_notification_recipient_notification'))).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drops both tables on down, recipient first to respect the foreign key', async () => {
|
||||||
|
const migrationModule = require('./1785600000000-AddNotificationTables');
|
||||||
|
const migration = new migrationModule.AddNotificationTables1785600000000();
|
||||||
|
const queryRunner = { query: jest.fn() } as any;
|
||||||
|
|
||||||
|
await migration.down(queryRunner);
|
||||||
|
|
||||||
|
const calls: string[] = queryRunner.query.mock.calls.map((c: any) => c[0]);
|
||||||
|
expect(calls).toEqual(['DROP TABLE "notification_recipient"', 'DROP TABLE "notification"']);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
import { EntityHelper } from 'src/utils/entity-helper';
|
||||||
|
import { Notification } from './notification.entity';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class NotificationRecipient extends EntityHelper {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Index('IDX_notification_recipient_notification_id')
|
||||||
|
@ManyToOne(() => Notification, { onDelete: 'CASCADE' })
|
||||||
|
notification: Notification;
|
||||||
|
|
||||||
|
@Index('IDX_notification_recipient_user_id')
|
||||||
|
@Column()
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@Column({ default: false })
|
||||||
|
read: boolean;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
readAt: Date | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { EntityHelper } from 'src/utils/entity-helper';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { NOTIFICATION_EVENT } from '../model/notification-event.type';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Notification extends EntityHelper {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Index('IDX_notification_team_id')
|
||||||
|
@ManyToOne(() => Team, { eager: false })
|
||||||
|
team: Team;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
event: NOTIFICATION_EVENT;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
actorUserId: number;
|
||||||
|
|
||||||
|
@Column({ type: 'text' })
|
||||||
|
payload: string;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export type NOTIFICATION_EVENT =
|
||||||
|
| 'player_active_update'
|
||||||
|
| 'player_team_role_update'
|
||||||
|
| 'player_creation'
|
||||||
|
| 'public_access_enabled'
|
||||||
|
| 'public_access_rotated'
|
||||||
|
| 'user_invite_link_create';
|
||||||
|
|
||||||
|
export const NOTIFICATION_EVENT_VALUES: NOTIFICATION_EVENT[] = [
|
||||||
|
'player_active_update',
|
||||||
|
'player_team_role_update',
|
||||||
|
'player_creation',
|
||||||
|
'public_access_enabled',
|
||||||
|
'public_access_rotated',
|
||||||
|
'user_invite_link_create',
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user