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

feat(user): Migrate user module to DML #10389

Merged
merged 5 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/rotten-tigers-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@medusajs/user": minor
---

feat(user): Migrate user module to DML
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,16 @@ import jwt, { JwtPayload } from "jsonwebtoken"

jest.setTimeout(30000)

const expireDate = new Date().setMilliseconds(
new Date().getMilliseconds() + 60 * 60 * 24
)

const defaultInviteData = [
{
id: "1",
email: "[email protected]",
token: "test",
expires_at: expireDate,
},
{
id: "2",
email: "[email protected]",
token: "test",
expires_at: expireDate,
Comment on lines -11 to -26
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: The given value was overritten by the module so it was not doing anything

},
]

Expand Down
42 changes: 24 additions & 18 deletions packages/modules/user/src/migrations/.snapshot-medusa-user.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"namespaces": ["public"],
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
Expand Down Expand Up @@ -97,32 +99,34 @@
"schema": "public",
"indexes": [
{
"keyName": "IDX_invite_email",
"columnNames": ["email"],
"keyName": "IDX_invite_deleted_at",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_invite_email\" ON \"invite\" (email) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_invite_deleted_at\" ON \"invite\" (deleted_at) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_invite_token",
"columnNames": ["token"],
"keyName": "IDX_invite_email_unique",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_invite_token\" ON \"invite\" (token) WHERE deleted_at IS NULL"
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_invite_email_unique\" ON \"invite\" (email) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_invite_deleted_at",
"columnNames": ["deleted_at"],
"keyName": "IDX_invite_token",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_invite_deleted_at\" ON \"invite\" (deleted_at) WHERE deleted_at IS NOT NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_invite_token\" ON \"invite\" (token) WHERE deleted_at IS NULL"
},
{
"keyName": "invite_pkey",
"columnNames": ["id"],
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
Expand Down Expand Up @@ -224,24 +228,26 @@
"schema": "public",
"indexes": [
{
"keyName": "IDX_user_email",
"columnNames": ["email"],
"keyName": "IDX_user_deleted_at",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_user_email\" ON \"user\" (email) WHERE deleted_at IS NULL"
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_user_deleted_at\" ON \"user\" (deleted_at) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_user_deleted_at",
"columnNames": ["deleted_at"],
"keyName": "IDX_user_email_unique",
"columnNames": [],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_user_deleted_at\" ON \"user\" (deleted_at) WHERE deleted_at IS NOT NULL"
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_user_email_unique\" ON \"user\" (email) WHERE deleted_at IS NULL"
},
{
"keyName": "user_pkey",
"columnNames": ["id"],
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
Expand Down
21 changes: 21 additions & 0 deletions packages/modules/user/src/migrations/Migration20241202103352.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Migration } from '@mikro-orm/migrations';

export class Migration20241202103352 extends Migration {

async up(): Promise<void> {
this.addSql('drop index if exists "IDX_invite_email";');
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_invite_email_unique" ON "invite" (email) WHERE deleted_at IS NULL;');

this.addSql('drop index if exists "IDX_user_email";');
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email_unique" ON "user" (email) WHERE deleted_at IS NULL;');
Comment on lines +6 to +10
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE: this apply the right name based on convention

}

async down(): Promise<void> {
this.addSql('drop index if exists "IDX_invite_email_unique";');
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_invite_email" ON "invite" (email) WHERE deleted_at IS NULL;');

this.addSql('drop index if exists "IDX_user_email_unique";');
this.addSql('CREATE UNIQUE INDEX IF NOT EXISTS "IDX_user_email" ON "user" (email) WHERE deleted_at IS NULL;');
}

}
4 changes: 2 additions & 2 deletions packages/modules/user/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as User } from "./user"
export { default as Invite } from "./invite"
export { User } from "./user"
export { Invite } from "./invite"
132 changes: 21 additions & 111 deletions packages/modules/user/src/models/invite.ts
Original file line number Diff line number Diff line change
@@ -1,112 +1,22 @@
import {
BeforeCreate,
Entity,
Filter,
Index,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"

import { DAL } from "@medusajs/framework/types"
import {
DALUtils,
createPsqlIndexStatementHelper,
generateEntityId,
Searchable,
} from "@medusajs/framework/utils"

const inviteEmailIndexName = "IDX_invite_email"
const inviteEmailIndexStatement = createPsqlIndexStatementHelper({
name: inviteEmailIndexName,
tableName: "invite",
columns: "email",
where: "deleted_at IS NULL",
unique: true,
}).expression

const inviteTokenIndexName = "IDX_invite_token"
const inviteTokenIndexStatement = createPsqlIndexStatementHelper({
name: inviteTokenIndexName,
tableName: "invite",
columns: "token",
where: "deleted_at IS NULL",
}).expression

const inviteDeletedAtIndexName = "IDX_invite_deleted_at"
const inviteDeletedAtIndexStatement = createPsqlIndexStatementHelper({
name: inviteDeletedAtIndexName,
tableName: "invite",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
}).expression

type OptionalFields =
| "metadata"
| "accepted"
| DAL.SoftDeletableModelDateColumns
@Entity({ tableName: "invite" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class Invite {
[OptionalProps]: OptionalFields

@PrimaryKey({ columnType: "text" })
id: string

@Index({
name: inviteEmailIndexName,
expression: inviteEmailIndexStatement,
})
@Searchable()
@Property({ columnType: "text" })
email: string

@Property({ columnType: "boolean" })
accepted: boolean = false

@Index({
name: inviteTokenIndexName,
expression: inviteTokenIndexStatement,
import { model } from "@medusajs/framework/utils"

export const Invite = model
.define("invite", {
id: model.id({ prefix: "invite" }).primaryKey(),
email: model.text().searchable(),
accepted: model.boolean().default(false),
token: model.text(),
expires_at: model.dateTime(),
metadata: model.json().nullable(),
})
@Property({ columnType: "text" })
token: string

@Property({ columnType: "timestamptz" })
expires_at: Date

@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null

@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date

@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date

@Index({
name: inviteDeletedAtIndexName,
expression: inviteDeletedAtIndexStatement,
})
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null

@OnInit()
onInit() {
this.id = generateEntityId(this.id, "invite")
}

@BeforeCreate()
beforeCreate() {
this.id = generateEntityId(this.id, "invite")
}
}
.indexes([
{
on: ["email"],
unique: true,
where: "deleted_at IS NULL",
},
{
on: ["token"],
where: "deleted_at IS NULL",
},
])
118 changes: 17 additions & 101 deletions packages/modules/user/src/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,18 @@
import {
BeforeCreate,
Entity,
Filter,
Index,
OnInit,
OptionalProps,
PrimaryKey,
Property,
} from "@mikro-orm/core"

import { DAL } from "@medusajs/framework/types"
import {
createPsqlIndexStatementHelper,
DALUtils,
generateEntityId,
Searchable,
} from "@medusajs/framework/utils"

const userEmailIndexName = "IDX_user_email"
const userEmailIndexStatement = createPsqlIndexStatementHelper({
name: userEmailIndexName,
unique: true,
tableName: "user",
columns: "email",
where: "deleted_at IS NULL",
})

const userDeletedAtIndexName = "IDX_user_deleted_at"
const userDeletedAtIndexStatement = createPsqlIndexStatementHelper({
name: userDeletedAtIndexName,
tableName: "user",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
}).expression

type OptionalFields =
| "first_name"
| "last_name"
| "metadata"
| "avatar_url"
| DAL.SoftDeletableModelDateColumns

@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class User {
[OptionalProps]?: OptionalFields

@PrimaryKey({ columnType: "text" })
id!: string

@Searchable()
@Property({ columnType: "text", nullable: true })
first_name: string | null = null

@Searchable()
@Property({ columnType: "text", nullable: true })
last_name: string | null = null

@userEmailIndexStatement.MikroORMIndex()
@Searchable()
@Property({ columnType: "text" })
email: string

@Property({ columnType: "text", nullable: true })
avatar_url: string | null = null

@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null

@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date

@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
import { model } from "@medusajs/framework/utils"

export const User = model
.define("user", {
id: model.id({ prefix: "user" }).primaryKey(),
first_name: model.text().searchable().nullable(),
last_name: model.text().searchable().nullable(),
email: model.text().searchable().searchable(),
avatar_url: model.text().nullable(),
adrien2p marked this conversation as resolved.
Show resolved Hide resolved
metadata: model.json().nullable(),
})
updated_at: Date

@Index({
name: userDeletedAtIndexName,
expression: userDeletedAtIndexStatement,
})
@Property({ columnType: "timestamptz", nullable: true })
deleted_at?: Date | null = null

@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "user")
}

@OnInit()
onInit() {
this.id = generateEntityId(this.id, "user")
}
}
.indexes([
{
unique: true,
on: ["email"],
where: "deleted_at IS NULL",
},
])
Loading
Loading