Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notification migration #2063

Merged
merged 28 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f3b9101
rename database entity files to entity.db.ts, add notification v2 dto…
PooyaRaki Oct 27, 2024
2966f6b
rewrite notification v2 using typeorm
PooyaRaki Oct 27, 2024
823f149
call notification v2 from notification v1 to add compatibility
PooyaRaki Oct 27, 2024
1343ffb
refactor notification hooks to use notification v2
PooyaRaki Oct 27, 2024
a8dc9db
Mark Notification datasource as deprecated, as the logic has been mig…
PooyaRaki Oct 27, 2024
73889fd
Update Notification V2 controller tests to align with the new TypeORM…
PooyaRaki Oct 27, 2024
befed9e
adjust notification relations, add device_uuid to get safe subscription
PooyaRaki Oct 27, 2024
c85dba7
test: Mock PushNotificationModule to resolve missing env vars in e2e …
PooyaRaki Oct 28, 2024
3b437e9
Remove legacy notification datasource, Add tests for notification rep…
PooyaRaki Oct 30, 2024
b8a4682
Add integration tests to notification repository
PooyaRaki Nov 3, 2024
b3e77cd
chore: refactor notification tests
PooyaRaki Nov 4, 2024
c1cecbe
remove custom uuid type
PooyaRaki Nov 5, 2024
c008ae5
Improve typing and validation schemas
PooyaRaki Nov 5, 2024
efb613c
refactor notification controller tests
PooyaRaki Nov 5, 2024
ec4a650
Call notification v2 from v1 using Promise.all
PooyaRaki Nov 5, 2024
b05dc89
Add validation to notification controller v1, refactor insertSubscrip…
PooyaRaki Nov 5, 2024
2facc07
Add comment to insertSubscriptionNotificationTypes to make it more clear
PooyaRaki Nov 5, 2024
24b11d6
Add mising created_at to NotificationSubscriptionSchema
PooyaRaki Nov 6, 2024
f0a3d3c
Adds UUIDSchema to NotificationDeviceSchema
PooyaRaki Nov 6, 2024
fddcb78
Improve notification related types, refactor notification tests
PooyaRaki Nov 6, 2024
927d8c5
chore: Return device uuid directly from the database transaction handler
PooyaRaki Nov 6, 2024
9a0e6c1
Get transactionRunner from the callback instead of the database service
PooyaRaki Nov 6, 2024
b65acbd
fix notification repository unit tests
PooyaRaki Nov 6, 2024
a547686
fix typos, moves notification repository mock to __tests__ folder
PooyaRaki Nov 6, 2024
232e651
add row schema to notification subscription entity
PooyaRaki Nov 7, 2024
a80866a
Add a new migration to clean up notification tables before creating n…
PooyaRaki Nov 7, 2024
aeddbfe
Check whether the notification feature flag is enabled before calling…
PooyaRaki Nov 8, 2024
bdf9a89
refactor typing of notification controller
PooyaRaki Nov 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions migrations/1726452966034-notification_cleanup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { MigrationInterface, QueryRunner } from 'typeorm';

export class NotificationCleanup1726452966034 implements MigrationInterface {
name = 'NotificationCleanup1726452966034';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP TRIGGER IF EXISTS "update_push_notification_devices_updated_at" ON "push_notification_devices"`,
);
await queryRunner.query(
`DROP TRIGGER IF EXISTS "update_notification_subscriptions_updated_at" ON "notification_subscriptions"`,
);
await queryRunner.query(
`DROP TYPE IF EXISTS "notification_types_name_enum" CASCADE`,
);
await queryRunner.query(
`DROP TYPE IF EXISTS "push_notification_devices_device_type_enum" CASCADE`,
);
await queryRunner.query(`DROP FUNCTION IF EXISTS update_updated_at()`);
await queryRunner.query(
`DROP TABLE IF EXISTS "push_notification_devices" CASCADE`,
);
await queryRunner.query(
`DROP TABLE IF EXISTS "notification_subscriptions" CASCADE`,
);
await queryRunner.query(
`DROP TABLE IF EXISTS "notification_subscription_notification_types" CASCADE`,
);
await queryRunner.query(
`DROP TABLE IF EXISTS "notification_types" CASCADE`,
);
}

public async down(): Promise<void> {}
}
10 changes: 5 additions & 5 deletions migrations/1726752966034-notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ export class Notification1726752966034 implements MigrationInterface {
`CREATE TABLE "push_notification_devices" ("id" SERIAL NOT NULL, "device_type" character varying(255) NOT NULL, "device_uuid" uuid NOT NULL, "cloud_messaging_token" character varying(255) NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), CONSTRAINT "device_uuid" UNIQUE ("device_uuid"), CONSTRAINT "PK_e387f5cc5b4f66d63804d596c64" PRIMARY KEY ("id"))`,
PooyaRaki marked this conversation as resolved.
Show resolved Hide resolved
);
hectorgomezv marked this conversation as resolved.
Show resolved Hide resolved
await queryRunner.query(
`CREATE TABLE "notification_subscriptions" ("id" SERIAL NOT NULL, "chain_id" character varying(255) NOT NULL, "safe_address" character varying(42) NOT NULL, "signer_address" character varying(42), "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "pushNotificationDeviceId" integer, CONSTRAINT "UQ_3c2531929422835e4f2717ec5db" UNIQUE ("chain_id", "safe_address", "pushNotificationDeviceId", "signer_address"), CONSTRAINT "PK_8cfec5d2a549ff20d1f4e648226" PRIMARY KEY ("id"))`,
`CREATE TABLE "notification_subscriptions" ("id" SERIAL NOT NULL, "chain_id" character varying(255) NOT NULL, "safe_address" character varying(42) NOT NULL, "signer_address" character varying(42), "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "push_notification_device_id" integer, CONSTRAINT "UQ_3c2531929422835e4f2717ec5db" UNIQUE ("chain_id", "safe_address", "push_notification_device_id", "signer_address"), CONSTRAINT "PK_8cfec5d2a549ff20d1f4e648226" PRIMARY KEY ("id"))`,
);
hectorgomezv marked this conversation as resolved.
Show resolved Hide resolved
await queryRunner.query(
`CREATE TABLE "notification_subscription_notification_types" ("id" SERIAL NOT NULL, "notificationSubscriptionId" integer, "notificationTypeId" integer, CONSTRAINT "UQ_5e7563e15aa2f994bd7b07ecec8" UNIQUE ("notificationSubscriptionId", "notificationTypeId"), CONSTRAINT "PK_3754c1a419741973072e5ed92eb" PRIMARY KEY ("id"))`,
`CREATE TABLE "notification_subscription_notification_types" ("id" SERIAL NOT NULL, "notification_subscription_id" integer, "notification_type_id" integer, CONSTRAINT "UQ_5e7563e15aa2f994bd7b07ecec8" UNIQUE ("notification_subscription_id", "notification_type_id"), CONSTRAINT "PK_3754c1a419741973072e5ed92eb" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "notification_types" ("id" SERIAL NOT NULL, "name" character varying(255) NOT NULL, CONSTRAINT "name" UNIQUE ("name"), CONSTRAINT "PK_aa965e094494e2c4c5942cfb42d" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "notification_subscriptions" ADD CONSTRAINT "FK_9f59e655926203074b833d6f909" FOREIGN KEY ("pushNotificationDeviceId") REFERENCES "push_notification_devices"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
`ALTER TABLE "notification_subscriptions" ADD CONSTRAINT "FK_9f59e655926203074b833d6f909" FOREIGN KEY ("push_notification_device_id") REFERENCES "push_notification_devices"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "notification_subscription_notification_types" ADD CONSTRAINT "FK_44702b7d6132421d2049ed994de" FOREIGN KEY ("notificationSubscriptionId") REFERENCES "notification_subscriptions"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
`ALTER TABLE "notification_subscription_notification_types" ADD CONSTRAINT "FK_44702b7d6132421d2049ed994de" FOREIGN KEY ("notification_subscription_id") REFERENCES "notification_subscriptions"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "notification_subscription_notification_types" ADD CONSTRAINT "FK_3e3e49a32dc1862742a322a6149" FOREIGN KEY ("notificationTypeId") REFERENCES "notification_types"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
`ALTER TABLE "notification_subscription_notification_types" ADD CONSTRAINT "FK_3e3e49a32dc1862742a322a6149" FOREIGN KEY ("notification_type_id") REFERENCES "notification_types"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}

Expand Down
2 changes: 1 addition & 1 deletion migrations/1727451367471-notifications_enum.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import type { MigrationInterface, QueryRunner } from 'typeorm';

export class NotificationsEnum1727451367471 implements MigrationInterface {
name = 'NotificationsEnum1727451367471';
Expand Down
2 changes: 1 addition & 1 deletion migrations/1727701600427-update_timestamp_trigger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import type { MigrationInterface, QueryRunner } from 'typeorm';

export class UpdateTimestampTrigger1727701600427 implements MigrationInterface {
name = 'UpdateTimestampTrigger1727701600427';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import type { MigrationInterface, QueryRunner } from 'typeorm';

export class NotificationUpdateUpdatedAt1727701873513
implements MigrationInterface
Expand Down
2 changes: 1 addition & 1 deletion src/datasources/db/v1/entities/row.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export type Row = z.infer<typeof RowSchema>;
*/
export const RowSchema = z.object({
id: z.number().int(),
created_at: z.coerce.date(),
created_at: z.coerce.date(), // @TODO when migrated all the entities to TypeOrm Remove `.coerce`
updated_at: z.coerce.date(),
});

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { IBuilder } from '@/__tests__/builder';
import { Builder } from '@/__tests__/builder';
import type { NotificationDevice } from '@/datasources/notifications/entities/notification-devices.entity.db';
import { DeviceType } from '@/domain/notifications/v2/entities/device-type.entity';
import { faker } from '@faker-js/faker/.';
import type { UUID } from 'crypto';

export function notificationDeviceBuilder(): IBuilder<NotificationDevice> {
return new Builder<NotificationDevice>()
.with('id', faker.number.int())
.with('device_uuid', faker.string.uuid() as UUID)
.with('device_type', faker.helpers.enumValue(DeviceType))
.with(
'cloud_messaging_token',
faker.string.alphanumeric({ length: { min: 10, max: 255 } }),
)
.with('created_at', new Date())
.with('updated_at', new Date());
iamacook marked this conversation as resolved.
Show resolved Hide resolved
iamacook marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { IBuilder } from '@/__tests__/builder';
import { Builder } from '@/__tests__/builder';
import { faker } from '@faker-js/faker/.';
import { notificationTypeBuilder } from '@/datasources/notifications/entities/__tests__/notification-type.entity.db.builder';
import { notificationSubscriptionBuilder } from '@/datasources/notifications/entities/__tests__/notification-subscription.entity.db.builder';
import type { NotificationSubscriptionNotificationType } from '@/datasources/notifications/entities/notification-subscription-notification-type.entity.db';

export function notificationSubscriptionNotificationTypeTypeBuilder(): IBuilder<NotificationSubscriptionNotificationType> {
return new Builder<NotificationSubscriptionNotificationType>()
.with('id', faker.number.int())
.with(
'notification_subscription',
notificationSubscriptionBuilder().build(),
)
.with('notification_type', notificationTypeBuilder().build());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { IBuilder } from '@/__tests__/builder';
import { Builder } from '@/__tests__/builder';
import { notificationDeviceBuilder } from '@/datasources/notifications/entities/__tests__/notification-devices.entity.db.builder';
import type { NotificationSubscription } from '@/datasources/notifications/entities/notification-subscription.entity.db';
import { faker } from '@faker-js/faker/.';
import { getAddress } from 'viem';

export function notificationSubscriptionBuilder(): IBuilder<NotificationSubscription> {
return new Builder<NotificationSubscription>()
.with('id', faker.number.int())
.with('chain_id', faker.number.int({ min: 1, max: 100 }).toString())
iamacook marked this conversation as resolved.
Show resolved Hide resolved
.with('safe_address', getAddress(faker.finance.ethereumAddress()))
.with('signer_address', getAddress(faker.finance.ethereumAddress()))
.with('created_at', new Date())
.with('updated_at', new Date())
iamacook marked this conversation as resolved.
Show resolved Hide resolved
iamacook marked this conversation as resolved.
Show resolved Hide resolved
.with('notification_subscription_notification_type', [])
.with('push_notification_device', notificationDeviceBuilder().build());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { IBuilder } from '@/__tests__/builder';
import { Builder } from '@/__tests__/builder';
import type { NotificationType } from '@/datasources/notifications/entities/notification-type.entity.db';
import { faker } from '@faker-js/faker/.';
import { NotificationType as NotificationTypeEnum } from '@/domain/notifications/v2/entities/notification.entity';

export function notificationTypeBuilder(): IBuilder<NotificationType> {
return new Builder<NotificationType>()
.with('id', faker.number.int())
.with('name', faker.helpers.enumValue(NotificationTypeEnum))
.with('notification_subscription_notification_type', []);
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import { NotificationSubscription } from '@/datasources/notifications/entities/notification-subscription.entity';
import { NotificationSubscription } from '@/datasources/notifications/entities/notification-subscription.entity.db';
import { RowSchema } from '@/datasources/db/v1/entities/row.entity';
import { DeviceType } from '@/domain/notifications/v2/entities/device-type.entity';
import { UuidSchema } from '@/validation/entities/schemas/uuid.schema';
import type { UUID } from 'crypto';
import {
Check,
Column,
Entity,
Unique,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { z } from 'zod';

export const NotificationDeviceSchema = RowSchema.extend({
device_type: z.nativeEnum(DeviceType),
device_uuid: UuidSchema,
cloud_messaging_token: z.string(),
});

@Entity('push_notification_devices')
@Unique('device_uuid', ['device_uuid'])
@Check('device_type', 'device_type IN ("ANDROID", "IOS", "WEB")')
export class NotificationDevice {
export class NotificationDevice
implements z.infer<typeof NotificationDeviceSchema>
{
@PrimaryGeneratedColumn()
id!: number;

@Column({
type: 'varchar',
length: 255,
type: 'enum',
enum: DeviceType,
})
device_type!: string;
device_type!: DeviceType;

@Column({ type: 'uuid' })
device_uuid!: UUID;
Expand All @@ -41,8 +51,12 @@ export class NotificationDevice {
})
updated_at!: Date;

@OneToMany(() => NotificationSubscription, (device) => device.id, {
onDelete: 'CASCADE',
})
@OneToMany(
() => NotificationSubscription,
(subscription) => subscription.id,
{
onDelete: 'CASCADE',
},
)
notification_subscriptions!: NotificationSubscription[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Unique,
} from 'typeorm';
import { NotificationType } from '@/datasources/notifications/entities/notification-type.entity.db';
import { NotificationSubscription } from '@/datasources/notifications/entities/notification-subscription.entity.db';
import { z } from 'zod';

export const NotificationSubscriptionNotificationTypeSchema = z.object({
id: z.number(),
});

@Entity('notification_subscription_notification_types')
@Unique(['notification_subscription', 'notification_type'])
export class NotificationSubscriptionNotificationType
implements z.infer<typeof NotificationSubscriptionNotificationTypeSchema>
{
@PrimaryGeneratedColumn()
id!: number;

@ManyToOne(
() => NotificationSubscription,
(subscription) => subscription.id,
{ onDelete: 'CASCADE' },
)
@JoinColumn({
name: 'notification_subscription_id',
})
notification_subscription!: NotificationSubscription;

@ManyToOne(
() => NotificationType,
(notificationType) =>
notificationType.notification_subscription_notification_type,
{
onDelete: 'CASCADE',
},
)
@JoinColumn({ name: 'notification_type_id' })
notification_type!: NotificationType;
}

This file was deleted.

Loading
Loading