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 1 commit
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
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
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"
134 changes: 23 additions & 111 deletions packages/modules/user/src/models/invite.ts
Original file line number Diff line number Diff line change
@@ -1,112 +1,24 @@
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([
{
name: "IDX_invite_email",
on: ["email"],
unique: true,
where: "deleted_at IS NULL",
},
{
name: "IDX_invite_token",
on: ["token"],
where: "deleted_at IS NULL",
},
])
119 changes: 18 additions & 101 deletions packages/modules/user/src/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,19 @@
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([
{
name: "IDX_user_email",
unique: true,
on: ["email"],
where: "deleted_at IS NULL",
},
])
36 changes: 15 additions & 21 deletions packages/modules/user/src/services/user-module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Context,
DAL,
InferEntityType,
InternalModuleDeclaration,
ModulesSdkTypes,
UserTypes,
Expand All @@ -9,6 +10,7 @@ import {
arrayDifference,
CommonEvents,
EmitEvents,
generateEntityId,
InjectManager,
InjectTransactionManager,
MedusaContext,
Expand Down Expand Up @@ -41,8 +43,12 @@ export default class UserModuleService
{
protected baseRepository_: DAL.RepositoryService

protected readonly userService_: ModulesSdkTypes.IMedusaInternalService<User>
protected readonly inviteService_: ModulesSdkTypes.IMedusaInternalService<Invite>
protected readonly userService_: ModulesSdkTypes.IMedusaInternalService<
InferEntityType<typeof User>
>
protected readonly inviteService_: ModulesSdkTypes.IMedusaInternalService<
InferEntityType<typeof Invite>
>
protected readonly config: { jwtSecret: string; expiresIn: number }

constructor(
Expand Down Expand Up @@ -151,9 +157,7 @@ export default class UserModuleService
const updates = invites.map((invite) => {
return {
id: invite.id,
expires_at: new Date().setMilliseconds(
new Date().getMilliseconds() + this.config.expiresIn * 1000
),
expires_at: new Date(Date.now() + this.config.expiresIn * 1000),
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 was giving a big int instead of a date, now it does the same thing but provide a date instead

token: this.generateToken({ id: invite.id, email: invite.email }),
}
})
Expand Down Expand Up @@ -296,7 +300,7 @@ export default class UserModuleService
private async createInvites_(
data: UserTypes.CreateInviteDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<Invite[]> {
): Promise<InferEntityType<typeof Invite>[]> {
const alreadyExistingUsers = await this.listUsers({
email: data.map((d) => d.email),
})
Expand All @@ -311,26 +315,16 @@ export default class UserModuleService
}

const toCreate = data.map((invite) => {
const id = generateEntityId((invite as { id?: string }).id, "invite")
adrien2p marked this conversation as resolved.
Show resolved Hide resolved
return {
...invite,
expires_at: new Date(),
token: "placeholder",
id,
expires_at: new Date(Date.now() + this.config.expiresIn * 1000),
token: this.generateToken({ id, email: invite.email }),
}
})

const created = await this.inviteService_.create(toCreate, sharedContext)

const updates = created.map((invite) => {
return {
id: invite.id,
expires_at: new Date().setMilliseconds(
new Date().getMilliseconds() + this.config.expiresIn * 1000
),
token: this.generateToken({ id: invite.id, email: invite.email }),
}
})

return await this.inviteService_.update(updates, sharedContext)
return await this.inviteService_.create(toCreate, sharedContext)
}
Comment on lines +318 to 328
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 is a simplification, instead of doing a create and then an update, we only perform a create but the result remain unchanged


// @ts-ignore
Expand Down
Loading