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

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
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
eef5cee
rename database entity files to entity.db.ts, add notification v2 dto…
PooyaRaki Oct 27, 2024
466879b
rewrite notification v2 using typeorm
PooyaRaki Oct 27, 2024
d965041
call notification v2 from notification v1 to add compatibility
PooyaRaki Oct 27, 2024
a625267
refactor notification hooks to use notification v2
PooyaRaki Oct 27, 2024
f90ab41
Mark Notification datasource as deprecated, as the logic has been mig…
PooyaRaki Oct 27, 2024
68af49c
Update Notification V2 controller tests to align with the new TypeORM…
PooyaRaki Oct 27, 2024
82dae89
adjust notification relations, add device_uuid to get safe subscription
PooyaRaki Oct 27, 2024
a7f7046
test: Mock PushNotificationModule to resolve missing env vars in e2e …
PooyaRaki Oct 28, 2024
d557e27
Remove legacy notification datasource, Add tests for notification rep…
PooyaRaki Oct 30, 2024
80509a5
Add integration tests to notification repository
PooyaRaki Nov 3, 2024
807e5b7
chore: refactor notification tests
PooyaRaki Nov 4, 2024
1ca06b4
remove custom uuid type
PooyaRaki Nov 5, 2024
e2660f4
Improve typing and validation schemas
PooyaRaki Nov 5, 2024
58d4f7e
refactor notification controller tests
PooyaRaki Nov 5, 2024
5c9c9aa
Call notification v2 from v1 using Promise.all
PooyaRaki Nov 5, 2024
48448c1
Add validation to notification controller v1, refactor insertSubscrip…
PooyaRaki Nov 5, 2024
1e97306
Add comment to insertSubscriptionNotificationTypes to make it more clear
PooyaRaki Nov 5, 2024
84f47b5
Add mising created_at to NotificationSubscriptionSchema
PooyaRaki Nov 6, 2024
efd9bdd
Adds UUIDSchema to NotificationDeviceSchema
PooyaRaki Nov 6, 2024
639e553
Improve notification related types, refactor notification tests
PooyaRaki Nov 6, 2024
bafd933
chore: Return device uuid directly from the database transaction handler
PooyaRaki Nov 6, 2024
aab7fe2
Get transactionRunner from the callback instead of the database service
PooyaRaki Nov 6, 2024
ca98349
fix notification repository unit tests
PooyaRaki Nov 6, 2024
27b10ff
fix typos, moves notification repository mock to __tests__ folder
PooyaRaki Nov 6, 2024
91e8cb2
add row schema to notification subscription entity
PooyaRaki Nov 7, 2024
eea1b3f
Add a new migration to clean up notification tables before creating n…
PooyaRaki Nov 7, 2024
156d8e4
Check whether the notification feature flag is enabled before calling…
PooyaRaki Nov 8, 2024
5e5999d
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