feat(user, types): add invite and user properties + migration (#6327)

**What**
- Add invite model 
- Populate user model
This commit is contained in:
Philip Korsholm
2024-02-14 21:58:14 +08:00
committed by GitHub
parent 16927469eb
commit 4d51f095b3
45 changed files with 1751 additions and 100 deletions
@@ -14,11 +14,245 @@
"primary": false,
"nullable": false,
"mappedType": "text"
},
"email": {
"name": "email",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"accepted": {
"name": "accepted",
"type": "boolean",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"default": "false",
"mappedType": "boolean"
},
"token": {
"name": "token",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"expires_at": {
"name": "expires_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"mappedType": "datetime"
},
"metadata": {
"name": "metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
}
},
"name": "invite",
"schema": "public",
"indexes": [
{
"keyName": "IDX_invite_email",
"columnNames": [
"email"
],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_invite_email\" ON \"invite\" (email) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_invite_token",
"columnNames": [
"token"
],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_invite_token\" ON \"invite\" (token) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_invite_deleted_at",
"columnNames": [
"deleted_at"
],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_invite_token\" ON \"invite\" (deleted_at) WHERE deleted_at IS NOT NULL"
},
{
"keyName": "invite_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
},
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"first_name": {
"name": "first_name",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"last_name": {
"name": "last_name",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"email": {
"name": "email",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"metadata": {
"name": "metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
}
},
"name": "user",
"schema": "public",
"indexes": [
{
"keyName": "IDX_user_email",
"columnNames": [
"email"
],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_user_email\" ON \"user\" (email) WHERE deleted_at IS NULL"
},
{
"keyName": "IDX_user_deleted_at",
"columnNames": [
"deleted_at"
],
"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"
},
{
"keyName": "user_pkey",
"columnNames": [
@@ -1,13 +0,0 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20240201081925 extends Migration {
async up(): Promise<void> {
this.addSql('create table "user" ("id" text not null, constraint "user_pkey" primary key ("id"));');
}
async down(): Promise<void> {
this.addSql('drop table if exists "user" cascade;');
}
}
@@ -0,0 +1,53 @@
import { generatePostgresAlterColummnIfExistStatement } from "@medusajs/utils"
import { Migration } from "@mikro-orm/migrations"
export class Migration20240214033943 extends Migration {
async up(): Promise<void> {
this.addSql(
'create table if not exists "invite" ("id" text not null, "email" text not null, "accepted" boolean not null default false, "token" text not null, "expires_at" timestamptz not null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "invite_pkey" primary key ("id"));'
)
this.addSql(
'alter table "invite" add column if not exists "email" text not null;'
)
this.addSql(
generatePostgresAlterColummnIfExistStatement(
"invite",
["user_email"],
"DROP NOT NULL"
)
)
this.addSql(
'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_invite_email" ON "invite" (email) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_invite_token" ON "invite" (token) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_invite_deleted_at" ON "invite" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
this.addSql(
'create table if not exists "user" ("id" text not null, "first_name" text null, "last_name" text null, "email" text not null, "avatar_url" text null, "metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "user_pkey" primary key ("id"));'
)
this.addSql(
'alter table "user" add column if not exists "avatar_url" text null;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_user_email" ON "user" (email) WHERE deleted_at IS NULL;'
)
this.addSql(
'CREATE INDEX IF NOT EXISTS "IDX_user_deleted_at" ON "user" (deleted_at) WHERE deleted_at IS NOT NULL;'
)
}
async down(): Promise<void> {
this.addSql('drop table if exists "invite" cascade;')
this.addSql('drop table if exists "user" cascade;')
}
}
+1
View File
@@ -1 +1,2 @@
export { default as User } from "./user"
export { default as Invite } from "./invite"
+110
View File
@@ -0,0 +1,110 @@
import {
BeforeCreate,
Entity,
OnInit,
PrimaryKey,
Property,
Index,
Filter,
OptionalProps,
} from "@mikro-orm/core"
import {
DALUtils,
generateEntityId,
createPsqlIndexStatementHelper,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
const inviteEmailIndexName = "IDX_invite_email"
const inviteEmailIndexStatement = createPsqlIndexStatementHelper({
name: inviteEmailIndexName,
tableName: "invite",
columns: "email",
where: "deleted_at IS NULL",
unique: true,
})
const inviteTokenIndexName = "IDX_invite_token"
const inviteTokenIndexStatement = createPsqlIndexStatementHelper({
name: inviteTokenIndexName,
tableName: "invite",
columns: "token",
where: "deleted_at IS NULL",
})
const inviteDeletedAtIndexName = "IDX_invite_deleted_at"
const inviteDeletedAtIndexStatement = createPsqlIndexStatementHelper({
name: inviteDeletedAtIndexName,
tableName: "invite",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
type OptionalFields =
| "metadata"
| "accepted"
| DAL.SoftDeletableEntityDateColumns
@Entity({ tableName: "invite" })
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class Invite {
[OptionalProps]: OptionalFields
@PrimaryKey({ columnType: "text" })
id: string
@Index({
name: inviteEmailIndexName,
expression: inviteEmailIndexStatement,
})
@Property({ columnType: "text" })
email: string
@Property({ columnType: "boolean" })
accepted: boolean = false
@Index({
name: inviteTokenIndexName,
expression: inviteTokenIndexStatement,
})
@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")
}
}
+73 -1
View File
@@ -3,16 +3,88 @@ import {
Entity,
OnInit,
PrimaryKey,
Property,
Index,
OptionalProps,
Filter,
} from "@mikro-orm/core"
import { generateEntityId } from "@medusajs/utils"
import { DALUtils, generateEntityId } from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import { createPsqlIndexStatementHelper } from "@medusajs/utils"
const userEmailIndexName = "IDX_user_email"
const userEmailIndexStatement = createPsqlIndexStatementHelper({
name: userEmailIndexName,
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",
})
type OptionalFields =
| "first_name"
| "last_name"
| "metadata"
| "avatar_url"
| DAL.SoftDeletableEntityDateColumns
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class User {
[OptionalProps]?: OptionalFields
@PrimaryKey({ columnType: "text" })
id!: string
@Property({ columnType: "text", nullable: true })
first_name: string | null = null
@Property({ columnType: "text", nullable: true })
last_name: string | null = null
@Property({ columnType: "text" })
@Index({
name: userEmailIndexName,
expression: userEmailIndexStatement,
})
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()",
})
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")
+93 -33
View File
@@ -4,9 +4,6 @@ import {
InternalModuleDeclaration,
ModuleJoinerConfig,
UserTypes,
CreateUserDTO,
UpdateUserDTO,
UserDTO,
ModulesSdkTypes,
} from "@medusajs/types"
import {
@@ -17,20 +14,28 @@ import {
} from "@medusajs/utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import { User } from "@models"
import { Invite, User } from "@models"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
userService: ModulesSdkTypes.InternalModuleService<any>
inviteService: ModulesSdkTypes.InternalModuleService<any>
}
const generateMethodForModels = []
const generateMethodForModels = [Invite]
export default class UserModuleService<TUser extends User = User>
export default class UserModuleService<
TUser extends User = User,
TInvite extends Invite = Invite
>
extends ModulesSdkUtils.abstractModuleServiceFactory<
InjectedDependencies,
UserDTO,
{}
UserTypes.UserDTO,
{
Invite: {
dto: UserTypes.InviteDTO
}
}
>(User, generateMethodForModels, entityNameToLinkableKeysMap)
implements UserTypes.IUserModuleService
{
@@ -41,9 +46,10 @@ export default class UserModuleService<TUser extends User = User>
protected baseRepository_: DAL.RepositoryService
protected readonly userService_: ModulesSdkTypes.InternalModuleService<TUser>
protected readonly inviteService_: ModulesSdkTypes.InternalModuleService<TInvite>
constructor(
{ userService, baseRepository }: InjectedDependencies,
{ userService, inviteService, baseRepository }: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
// @ts-ignore
@@ -51,22 +57,29 @@ export default class UserModuleService<TUser extends User = User>
this.baseRepository_ = baseRepository
this.userService_ = userService
this.inviteService_ = inviteService
}
create(data: CreateUserDTO[], sharedContext?: Context): Promise<UserDTO[]>
create(data: CreateUserDTO, sharedContext?: Context): Promise<UserDTO>
create(
data: UserTypes.CreateUserDTO[],
sharedContext?: Context
): Promise<UserTypes.UserDTO[]>
create(
data: UserTypes.CreateUserDTO,
sharedContext?: Context
): Promise<UserTypes.UserDTO>
@InjectManager("baseRepository_")
@InjectTransactionManager("baseRepository_")
async create(
data: CreateUserDTO[] | CreateUserDTO,
data: UserTypes.CreateUserDTO[] | UserTypes.CreateUserDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<UserTypes.UserDTO | UserTypes.UserDTO[]> {
const input = Array.isArray(data) ? data : [data]
const users = await this.create_(input, sharedContext)
const users = await this.userService_.create(input, sharedContext)
const serializedUsers = await this.baseRepository_.serialize<
UserTypes.UserDTO[]
UserTypes.UserDTO[] | UserTypes.UserDTO
>(users, {
populate: true,
})
@@ -74,25 +87,23 @@ export default class UserModuleService<TUser extends User = User>
return Array.isArray(data) ? serializedUsers : serializedUsers[0]
}
update(
data: UserTypes.UpdateUserDTO[],
sharedContext?: Context
): Promise<UserTypes.UserDTO[]>
update(
data: UserTypes.UpdateUserDTO,
sharedContext?: Context
): Promise<UserTypes.UserDTO>
@InjectTransactionManager("baseRepository_")
protected async create_(
data: CreateUserDTO[],
@MedusaContext() sharedContext: Context
): Promise<TUser[]> {
return await this.userService_.create(data, sharedContext)
}
update(data: UpdateUserDTO[], sharedContext?: Context): Promise<UserDTO[]>
update(data: UpdateUserDTO, sharedContext?: Context): Promise<UserDTO>
@InjectManager("baseRepository_")
async update(
data: UpdateUserDTO | UpdateUserDTO[],
data: UserTypes.UpdateUserDTO | UserTypes.UpdateUserDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<UserTypes.UserDTO | UserTypes.UserDTO[]> {
const input = Array.isArray(data) ? data : [data]
const updatedUsers = await this.update_(input, sharedContext)
const updatedUsers = await this.userService_.update(input, sharedContext)
const serializedUsers = await this.baseRepository_.serialize<
UserTypes.UserDTO[]
@@ -103,11 +114,60 @@ export default class UserModuleService<TUser extends User = User>
return Array.isArray(data) ? serializedUsers : serializedUsers[0]
}
createInvites(
data: UserTypes.CreateInviteDTO[],
sharedContext?: Context
): Promise<UserTypes.InviteDTO[]>
createInvites(
data: UserTypes.CreateInviteDTO,
sharedContext?: Context
): Promise<UserTypes.InviteDTO>
@InjectTransactionManager("baseRepository_")
protected async update_(
data: UpdateUserDTO[],
@MedusaContext() sharedContext: Context
): Promise<TUser[]> {
return await this.userService_.update(data, sharedContext)
async createInvites(
data: UserTypes.CreateInviteDTO[] | UserTypes.CreateInviteDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<UserTypes.InviteDTO | UserTypes.InviteDTO[]> {
const input = Array.isArray(data) ? data : [data]
const invites = await this.inviteService_.create(input, sharedContext)
const serializedInvites = await this.baseRepository_.serialize<
UserTypes.InviteDTO[] | UserTypes.InviteDTO
>(invites, {
populate: true,
})
return Array.isArray(data) ? serializedInvites : serializedInvites[0]
}
updateInvites(
data: UserTypes.UpdateInviteDTO[],
sharedContext?: Context
): Promise<UserTypes.InviteDTO[]>
updateInvites(
data: UserTypes.UpdateInviteDTO,
sharedContext?: Context
): Promise<UserTypes.InviteDTO>
@InjectTransactionManager("baseRepository_")
async updateInvites(
data: UserTypes.UpdateInviteDTO | UserTypes.UpdateInviteDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<UserTypes.InviteDTO | UserTypes.InviteDTO[]> {
const input = Array.isArray(data) ? data : [data]
const updatedInvites = await this.inviteService_.update(
input,
sharedContext
)
const serializedInvites = await this.baseRepository_.serialize<
UserTypes.InviteDTO[]
>(updatedInvites, {
populate: true,
})
return Array.isArray(data) ? serializedInvites : serializedInvites[0]
}
}