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

View File

@@ -0,0 +1,7 @@
---
"@medusajs/core-flows": patch
"@medusajs/medusa": patch
"@medusajs/types": patch
---
feat(core-flows, types, utils, medusa): add user endpoints to api-v2

View File

@@ -0,0 +1,58 @@
import { initDb, useDb } from "../../../environment-helpers/use-db"
import { IUserModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { getContainer } from "../../../environment-helpers/use-container"
import path from "path"
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../environment-helpers/use-api"
import adminSeeder from "../../../helpers/admin-seeder"
import { AxiosInstance } from "axios"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}
describe("POST /admin/users", () => {
let dbConnection
let shutdownServer
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
})
beforeEach(async () => {
await adminSeeder(dbConnection)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("create a user", async () => {
const api = useApi()! as AxiosInstance
const body = {
email: "test_member@test.com",
}
const response = await api.post(`/admin/users`, body, adminHeaders)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
user: expect.objectContaining(body),
})
})
})

View File

@@ -0,0 +1,71 @@
import { initDb, useDb } from "../../../environment-helpers/use-db"
import { IUserModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { getContainer } from "../../../environment-helpers/use-container"
import path from "path"
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../environment-helpers/use-api"
import adminSeeder from "../../../helpers/admin-seeder"
import { AxiosInstance } from "axios"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}
describe("DELETE /admin/users/:id", () => {
let dbConnection
let appContainer
let shutdownServer
let userModuleService: IUserModuleService
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
userModuleService = appContainer.resolve(ModuleRegistrationName.USER)
})
beforeEach(async () => {
await adminSeeder(dbConnection)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should delete a single user", async () => {
const user = await userModuleService.create({
email: "member@test.com",
})
const api = useApi()! as AxiosInstance
const response = await api.delete(`/admin/users/${user.id}`, adminHeaders)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
id: user.id,
object: "user",
deleted: true,
})
const { response: deletedResponse } = await api
.get(`/admin/users/${user.id}`, adminHeaders)
.catch((e) => e)
expect(deletedResponse.status).toEqual(404)
expect(deletedResponse.data.type).toEqual("not_found")
})
})

View File

@@ -0,0 +1,72 @@
import { initDb, useDb } from "../../../environment-helpers/use-db"
import { IUserModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { getContainer } from "../../../environment-helpers/use-container"
import path from "path"
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../environment-helpers/use-api"
import adminSeeder from "../../../helpers/admin-seeder"
import { AxiosInstance } from "axios"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}
describe("GET /admin/users", () => {
let dbConnection
let appContainer
let shutdownServer
let userModuleService: IUserModuleService
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
userModuleService = appContainer.resolve(ModuleRegistrationName.USER)
})
beforeEach(async () => {
await adminSeeder(dbConnection)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should list users", async () => {
await userModuleService.create([
{
email: "member@test.com",
},
])
const api = useApi()! as AxiosInstance
const response = await api.get(`/admin/users`, adminHeaders)
expect(response.status).toEqual(200)
expect(response.data).toEqual({
users: expect.arrayContaining([
expect.objectContaining({
email: "admin@medusa.js",
}),
expect.objectContaining({ email: "member@test.com" }),
]),
count: 2,
offset: 0,
limit: 50,
})
})
})

View File

@@ -0,0 +1,62 @@
import { initDb, useDb } from "../../../environment-helpers/use-db"
import { IUserModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { getContainer } from "../../../environment-helpers/use-container"
import path from "path"
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../environment-helpers/use-api"
import adminSeeder from "../../../helpers/admin-seeder"
import { AxiosInstance } from "axios"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}
describe("GET /admin/users/:id", () => {
let dbConnection
let appContainer
let shutdownServer
let userModuleService: IUserModuleService
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
userModuleService = appContainer.resolve(ModuleRegistrationName.USER)
})
beforeEach(async () => {
await adminSeeder(dbConnection)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should retrieve a single user", async () => {
const user = await userModuleService.create({
email: "member@test.com",
})
const api = useApi()! as AxiosInstance
const response = await api.get(`/admin/users/${user.id}`, adminHeaders)
expect(response.status).toEqual(200)
expect(response.data.user).toEqual(
expect.objectContaining({ email: "member@test.com" })
)
})
})

View File

@@ -0,0 +1,68 @@
import { initDb, useDb } from "../../../environment-helpers/use-db"
import { IUserModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { getContainer } from "../../../environment-helpers/use-container"
import path from "path"
import { startBootstrapApp } from "../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../environment-helpers/use-api"
import adminSeeder from "../../../helpers/admin-seeder"
import { AxiosInstance } from "axios"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}
describe("POST /admin/users/:id", () => {
let dbConnection
let appContainer
let shutdownServer
let userModuleService: IUserModuleService
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
userModuleService = appContainer.resolve(ModuleRegistrationName.USER)
})
beforeEach(async () => {
await adminSeeder(dbConnection)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should update a single user", async () => {
const user = await userModuleService.create({
email: "member@test.com",
})
const api = useApi()! as AxiosInstance
const body = {
first_name: "John",
last_name: "Doe",
}
const response = await api.post(
`/admin/users/${user.id}`,
body,
adminHeaders
)
expect(response.status).toEqual(200)
expect(response.data.user).toEqual(expect.objectContaining(body))
})
})

View File

@@ -47,6 +47,11 @@ module.exports = {
resources: "shared",
resolve: "@medusajs/auth",
},
[Modules.USER]: {
scope: "internal",
resources: "shared",
resolve: "@medusajs/user",
},
[Modules.STOCK_LOCATION]: {
scope: "internal",
resources: "shared",

View File

@@ -20,6 +20,7 @@
"@medusajs/product": "workspace:^",
"@medusajs/promotion": "workspace:^",
"@medusajs/region": "workspace:^",
"@medusajs/user": "workspace:^",
"@medusajs/utils": "workspace:^",
"@medusajs/workflow-engine-inmemory": "workspace:*",
"faker": "^5.5.3",

View File

@@ -4,3 +4,4 @@ export * as Handlers from "./handlers"
export * from "./promotion"
export * from "./customer"
export * from "./customer-group"
export * from "./user"

View File

@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"

View File

@@ -0,0 +1,22 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreateUserDTO, IUserModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const createUsersStepId = "create-users-step"
export const createUsersStep = createStep(
createUsersStepId,
async (input: CreateUserDTO[], { container }) => {
const service: IUserModuleService = container.resolve(
ModuleRegistrationName.USER
)
const users = await service.create(input)
return new StepResponse(users)
},
async (createdUsers, { container }) => {
if (!createdUsers?.length) {
return
}
const service = container.resolve(ModuleRegistrationName.USER)
await service.delete(createdUsers)
}
)

View File

@@ -0,0 +1,28 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IUserModuleService } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const deleteUsersStepId = "delete-users-step"
export const deleteUsersStep = createStep(
deleteUsersStepId,
async (input: string[], { container }) => {
const service: IUserModuleService = container.resolve(
ModuleRegistrationName.USER
)
await service.softDelete(input)
return new StepResponse(void 0, input)
},
async (prevUserIds, { container }) => {
if (!prevUserIds?.length) {
return
}
const service: IUserModuleService = container.resolve(
ModuleRegistrationName.USER
)
await service.restore(prevUserIds)
}
)

View File

@@ -0,0 +1,3 @@
export * from "./delete-users"
export * from "./create-users"
export * from "./update-users"

View File

@@ -0,0 +1,42 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IUserModuleService, UpdateUserDTO } from "@medusajs/types"
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
export const updateUsersStepId = "update-users-step"
export const updateUsersStep = createStep(
updateUsersStepId,
async (input: UpdateUserDTO[], { container }) => {
const service: IUserModuleService = container.resolve(
ModuleRegistrationName.USER
)
if (!input.length) {
return new StepResponse([], [])
}
const originalUsers = await service.list({
id: input.map((u) => u.id),
})
const users = await service.update(input)
return new StepResponse(users, originalUsers)
},
async (originalUsers, { container }) => {
if (!originalUsers?.length) {
return
}
const service = container.resolve(ModuleRegistrationName.USER)
await service.update(
originalUsers.map((u) => ({
id: u.id,
first_name: u.first_name,
last_name: u.last_name,
email: u.email,
avatar_url: u.avatar_url,
metadata: u.metadata,
}))
)
}
)

View File

@@ -0,0 +1,14 @@
import { CreateUserDTO, UserDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createUsersStep } from "../steps"
import { UserWorkflow } from "@medusajs/types"
export const createUsersWorkflowId = "create-users-workflow"
export const createUsersWorkflow = createWorkflow(
createUsersWorkflowId,
(
input: WorkflowData<UserWorkflow.CreateUsersWorkflowInputDTO>
): WorkflowData<UserDTO[]> => {
return createUsersStep(input.users)
}
)

View File

@@ -0,0 +1,13 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteUsersStep } from "../steps"
import { UserWorkflow } from "@medusajs/types"
export const deleteUsersWorkflowId = "delete-user"
export const deleteUsersWorkflow = createWorkflow(
deleteUsersWorkflowId,
(
input: WorkflowData<UserWorkflow.DeleteUserWorkflowInput>
): WorkflowData<void> => {
return deleteUsersStep(input.ids)
}
)

View File

@@ -0,0 +1,3 @@
export * from "./delete-users"
export * from "./create-users"
export * from "./update-users"

View File

@@ -0,0 +1,14 @@
import { UserDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updateUsersStep } from "../steps"
import { UserWorkflow } from "@medusajs/types"
export const updateUsersWorkflowId = "update-users-workflow"
export const updateUsersWorkflow = createWorkflow(
updateUsersWorkflowId,
(
input: WorkflowData<UserWorkflow.UpdateUsersWorkflowInputDTO>
): WorkflowData<UserDTO[]> => {
return updateUsersStep(input.updates)
}
)

View File

@@ -0,0 +1,62 @@
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
import { deleteUsersWorkflow, updateUsersWorkflow } from "@medusajs/core-flows"
import { IUserModuleService, UpdateUserDTO } from "@medusajs/types"
import { ModuleRegistrationName } from "../../../../../../modules-sdk/dist"
import { AdminUpdateUserRequest } from "../validators"
// Get user
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const { id } = req.params
const moduleService: IUserModuleService = req.scope.resolve(
ModuleRegistrationName.USER
)
const user = await moduleService.retrieve(id, req.retrieveConfig)
res.status(200).json({ user })
}
// update user
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
const workflow = updateUsersWorkflow(req.scope)
const input = {
updates: [
{
id: req.params.id,
...(req.validatedBody as AdminUpdateUserRequest),
} as UpdateUserDTO,
],
}
const { result } = await workflow.run({ input })
const [user] = result
res.status(200).json({ user })
}
// delete user
export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
const { id } = req.params
const workflow = deleteUsersWorkflow(req.scope)
const { errors } = await workflow.run({
input: { ids: [id] },
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
res.status(200).json({
id,
object: "user",
deleted: true,
})
}

View File

@@ -0,0 +1,39 @@
import { transformBody, transformQuery } from "../../../api/middlewares"
import {
AdminCreateUserRequest,
AdminGetUsersParams,
AdminGetUsersUserParams,
AdminUpdateUserRequest,
} from "./validators"
import * as QueryConfig from "./query-config"
import { MiddlewareRoute } from "../../../types/middlewares"
export const adminUserRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["GET"],
matcher: "/admin/users",
middlewares: [
transformQuery(AdminGetUsersParams, QueryConfig.listTransformQueryConfig),
],
},
{
method: ["POST"],
matcher: "/admin/users",
middlewares: [transformBody(AdminCreateUserRequest)],
},
{
method: ["GET"],
matcher: "/admin/users/:id",
middlewares: [
transformQuery(
AdminGetUsersUserParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/users/:id",
middlewares: [transformBody(AdminUpdateUserRequest)],
},
]

View File

@@ -0,0 +1,25 @@
export const defaultAdminUserRelations = []
export const allowedAdminUserRelations = []
export const defaultAdminUserFields = [
"id",
"first_name",
"last_name",
"email",
"avatar_url",
"metadata",
"created_at",
"updated_at",
"deleted_at",
]
export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminUserFields,
defaultRelations: defaultAdminUserRelations,
allowedRelations: allowedAdminUserRelations,
isList: false,
}
export const listTransformQueryConfig = {
...retrieveTransformQueryConfig,
isList: true,
}

View File

@@ -0,0 +1,48 @@
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
import { createUsersWorkflow } from "@medusajs/core-flows"
import { CreateUserDTO } from "@medusajs/types"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = remoteQueryObjectFromString({
entryPoint: "user",
variables: {
filters: req.filterableFields,
order: req.listConfig.order,
skip: req.listConfig.skip,
take: req.listConfig.take,
},
fields: req.listConfig.select as string[],
})
const { rows: users, metadata } = await remoteQuery({
...query,
})
res.status(200).json({
users,
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
})
}
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
const workflow = createUsersWorkflow(req.scope)
const input = {
input: {
users: [req.validatedBody as CreateUserDTO],
},
}
const { result } = await workflow.run(input)
const [user] = result
res.status(200).json({ user })
}

View File

@@ -0,0 +1,112 @@
import { Type } from "class-transformer"
import { IsEmail, IsOptional, IsString, ValidateNested } from "class-validator"
import {
DateComparisonOperator,
FindParams,
extendedFindParamsMixin,
} from "../../../types/common"
import { IsType } from "../../../utils"
export class AdminGetUsersUserParams extends FindParams {}
export class AdminGetUsersParams extends extendedFindParamsMixin({
limit: 50,
offset: 0,
}) {
/**
* IDs to filter users by.
*/
@IsOptional()
@IsType([String, [String]])
id?: string | string[]
/**
* The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`.
*/
@IsString()
@IsOptional()
order?: string
/**
* Date filters to apply on the users' `update_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
updated_at?: DateComparisonOperator
/**
* Date filters to apply on the customer users' `created_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
created_at?: DateComparisonOperator
/**
* Date filters to apply on the users' `deleted_at` date.
*/
@IsOptional()
@ValidateNested()
@Type(() => DateComparisonOperator)
deleted_at?: DateComparisonOperator
/**
* Filter to apply on the users' `email` field.
*/
@IsOptional()
@IsString()
email?: string
/**
* Filter to apply on the users' `first_name` field.
*/
@IsOptional()
@IsString()
first_name?: string
/**
* Filter to apply on the users' `last_name` field.
*/
@IsOptional()
@IsString()
last_name?: string
/**
* Comma-separated fields that should be included in the returned users.
*/
@IsOptional()
@IsString()
fields?: string
}
export class AdminCreateUserRequest {
@IsEmail()
email: string
@IsOptional()
@IsString()
first_name?: string
@IsOptional()
@IsString()
last_name?: string
@IsString()
@IsOptional()
avatar_url: string
}
export class AdminUpdateUserRequest {
@IsString()
@IsOptional()
first_name?: string
@IsString()
@IsOptional()
last_name?: string
@IsString()
@IsOptional()
avatar_url: string
}

View File

@@ -8,6 +8,7 @@ import { adminWorkflowsExecutionsMiddlewares } from "./admin/workflows-execution
import { authRoutesMiddlewares } from "./auth/middlewares"
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
import { adminUserRoutesMiddlewares } from "./admin/users/middlewares"
import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares"
export const config: MiddlewaresConfig = {
@@ -22,5 +23,6 @@ export const config: MiddlewaresConfig = {
...adminWorkflowsExecutionsMiddlewares,
...storeRegionRoutesMiddlewares,
...adminRegionRoutesMiddlewares,
...adminUserRoutesMiddlewares,
],
}

View File

@@ -1,5 +1,41 @@
export type UserDTO = {
import { DateComparisonOperator } from "../common"
import { BaseFilterable } from "../dal"
export interface UserDTO {
id: string
email: string
first_name: string | null
last_name: string | null
avatar_url: string | null
metadata: Record<string, unknown> | null
created_at: Date
updated_at: Date
deleted_at: Date | null
}
export type FilterableUserProps = {}
export interface FilterableUserProps
extends BaseFilterable<FilterableUserProps> {
id?: string | string[]
email?: string | string[]
first_name?: string | string[]
last_name?: string | string[]
}
export interface InviteDTO {
id: string
email: string
accepted: boolean
token: string
expires_at: Date
metadata: Record<string, unknown> | null
created_at: Date
updated_at: Date
deleted_at: Date | null
}
export interface FilterableInviteProps
extends BaseFilterable<FilterableInviteProps> {
id?: string | string[]
email?: string | string[]
expires_at?: DateComparisonOperator
}

View File

@@ -1,6 +1,22 @@
export interface CreateUserDTO {
id?: string
email: string
first_name?: string | null
last_name?: string | null
avatar_url?: string | null
metadata?: Record<string, unknown> | null
}
export interface UpdateUserDTO {
export interface UpdateUserDTO extends Partial<Omit<CreateUserDTO, "email">> {
id: string
}
export interface CreateInviteDTO {
email: string
accepted?: boolean
token: string
expires_at: Date
metadata?: Record<string, unknown> | null
}
export interface UpdateInviteDTO extends Partial<CreateInviteDTO> {
id: string
}

View File

@@ -1,9 +1,15 @@
import { CreateUserDTO, UpdateUserDTO } from "./mutations"
import { FilterableUserProps, UserDTO } from "./common"
import {
CreateInviteDTO,
CreateUserDTO,
UpdateInviteDTO,
UpdateUserDTO,
} from "./mutations"
import { FilterableUserProps, InviteDTO, UserDTO } from "./common"
import { Context } from "../shared-context"
import { FindConfig } from "../common"
import { IModuleService } from "../modules-sdk"
import { RestoreReturn, SoftDeleteReturn } from "../dal"
export interface IUserModuleService extends IModuleService {
retrieve(
@@ -33,4 +39,68 @@ export interface IUserModuleService extends IModuleService {
update(data: UpdateUserDTO, sharedContext?: Context): Promise<UserDTO>
delete(ids: string[], sharedContext?: Context): Promise<void>
softDelete<TReturnableLinkableKeys extends string = string>(
userIds: string[],
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<TReturnableLinkableKeys, string[]> | void>
restore<TReturnableLinkableKeys extends string = string>(
userIds: string[],
config?: RestoreReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<TReturnableLinkableKeys, string[]> | void>
retrieveInvite(
id: string,
config?: FindConfig<InviteDTO>,
sharedContext?: Context
): Promise<InviteDTO>
listInvites(
filters?: FilterableUserProps,
config?: FindConfig<InviteDTO>,
sharedContext?: Context
): Promise<InviteDTO[]>
listAndCountInvites(
filters?: FilterableUserProps,
config?: FindConfig<InviteDTO>,
sharedContext?: Context
): Promise<[InviteDTO[], number]>
createInvites(
data: CreateInviteDTO[],
sharedContext?: Context
): Promise<InviteDTO[]>
createInvites(
data: CreateInviteDTO,
sharedContext?: Context
): Promise<InviteDTO>
updateInvites(
data: UpdateInviteDTO[],
sharedContext?: Context
): Promise<InviteDTO[]>
updateInvites(
data: UpdateInviteDTO,
sharedContext?: Context
): Promise<InviteDTO>
deleteInvites(ids: string[], sharedContext?: Context): Promise<void>
softDeleteInvites<TReturnableLinkableKeys extends string = string>(
inviteIds: string[],
config?: SoftDeleteReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<TReturnableLinkableKeys, string[]> | void>
restoreInvites<TReturnableLinkableKeys extends string = string>(
inviteIds: string[],
config?: RestoreReturn<TReturnableLinkableKeys>,
sharedContext?: Context
): Promise<Record<TReturnableLinkableKeys, string[]> | void>
}

View File

@@ -3,3 +3,4 @@ export * as CommonWorkflow from "./common"
export * as ProductWorkflow from "./product"
export * as InventoryWorkflow from "./inventory"
export * as PriceListWorkflow from "./price-list"
export * as UserWorkflow from "./user"

View File

@@ -0,0 +1,5 @@
import { CreateUserDTO } from "../../user"
export interface CreateUsersWorkflowInputDTO {
users: CreateUserDTO[]
}

View File

@@ -0,0 +1,3 @@
export interface DeleteUserWorkflowInput {
ids: string[]
}

View File

@@ -0,0 +1,3 @@
export * from "./create-user"
export * from "./update-user"
export * from "./delete-user"

View File

@@ -0,0 +1,5 @@
import { UpdateUserDTO } from "../../user"
export interface UpdateUsersWorkflowInputDTO {
updates: UpdateUserDTO[]
}

View File

@@ -0,0 +1,17 @@
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { Invite } from "@models"
import { CreateInviteDTO } from "../../../types/dist"
export const createInvites = async (
manager: SqlEntityManager,
inviteData: (CreateInviteDTO & { id?: string })[]
) => {
const invites: Invite[] = []
for (const invite of inviteData) {
const inv = manager.create(Invite, invite)
invites.push(inv)
}
await manager.persistAndFlush(invites)
}

View File

@@ -1,9 +1,10 @@
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { User } from "@models"
import { CreateUserDTO } from "../../../types/dist"
export const createUsers = async (
manager: SqlEntityManager,
userData = [{ id: "1" }]
userData: (CreateUserDTO & { id?: string })[]
) => {
const users: User[] = []

View File

@@ -0,0 +1,192 @@
import { IUserModuleService } from "@medusajs/types/dist/user"
import { MikroOrmWrapper } from "../../../utils"
import { Modules } from "@medusajs/modules-sdk"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { createInvites } from "../../../__fixtures__/invite"
import { getInitModuleConfig } from "../../../utils/get-init-module-config"
import { initModules } from "medusa-test-utils"
jest.setTimeout(30000)
const today = new Date()
const expireDate = new Date(today.setDate(today.getDate() + 10))
const defaultInviteData = [
{
id: "1",
email: "user_1@test.com",
token: "test",
expires_at: expireDate,
},
{
id: "2",
email: "user_2@test.com",
token: "test",
expires_at: expireDate,
},
]
describe("UserModuleService - Invite", () => {
let service: IUserModuleService
let testManager: SqlEntityManager
let shutdownFunc: () => Promise<void>
beforeAll(async () => {
const initModulesConfig = getInitModuleConfig()
const { medusaApp, shutdown } = await initModules(initModulesConfig)
service = medusaApp.modules[Modules.USER]
shutdownFunc = shutdown
})
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
testManager = MikroOrmWrapper.forkManager()
})
afterEach(async () => {
await MikroOrmWrapper.clearDatabase()
})
afterAll(async () => {
await shutdownFunc()
})
describe("listInvites", () => {
it("should list invites", async () => {
await createInvites(testManager, defaultInviteData)
const invites = await service.listInvites()
expect(invites).toEqual([
expect.objectContaining({
id: "1",
}),
expect.objectContaining({
id: "2",
}),
])
})
it("should list invites by id", async () => {
await createInvites(testManager, defaultInviteData)
const invites = await service.listInvites({
id: ["1"],
})
expect(invites).toEqual([
expect.objectContaining({
id: "1",
}),
])
})
})
describe("listAndCountInvites", () => {
it("should list and count invites", async () => {
await createInvites(testManager, defaultInviteData)
const [invites, count] = await service.listAndCountInvites()
expect(count).toEqual(2)
expect(invites).toEqual([
expect.objectContaining({
id: "1",
}),
expect.objectContaining({
id: "2",
}),
])
})
it("should listAndCount invites by id", async () => {
await createInvites(testManager, defaultInviteData)
const [invites, count] = await service.listAndCountInvites({
id: "1",
})
expect(count).toEqual(1)
expect(invites).toEqual([
expect.objectContaining({
id: "1",
}),
])
})
})
describe("retrieveInvite", () => {
const id = "1"
it("should return an invite for the given id", async () => {
await createInvites(testManager, defaultInviteData)
const invite = await service.retrieveInvite(id)
expect(invite).toEqual(
expect.objectContaining({
id,
})
)
})
it("should throw an error when an invite with the given id does not exist", async () => {
const error = await service
.retrieveInvite("does-not-exist")
.catch((e) => e)
expect(error.message).toEqual(
"Invite with id: does-not-exist was not found"
)
})
it("should throw an error when inviteId is not provided", async () => {
const error = await service
.retrieveInvite(undefined as unknown as string)
.catch((e) => e)
expect(error.message).toEqual("invite - id must be defined")
})
it("should return invite based on config select param", async () => {
await createInvites(testManager, defaultInviteData)
const invite = await service.retrieveInvite(id, {
select: ["id"],
})
expect(invite).toEqual({
id,
})
})
})
describe("updateInvite", () => {
it("should throw an error when an id does not exist", async () => {
const error = await service
.updateInvites([
{
id: "does-not-exist",
},
])
.catch((e) => e)
expect(error.message).toEqual('Invite with id "does-not-exist" not found')
})
})
describe("createInvitie", () => {
it("should create an invite successfully", async () => {
await service.createInvites(defaultInviteData)
const [invites, count] = await service.listAndCountInvites({
id: ["1"],
})
expect(count).toEqual(1)
expect(invites[0]).toEqual(
expect.objectContaining({
id: "1",
})
)
})
})
})

View File

@@ -8,6 +8,17 @@ import { initModules } from "medusa-test-utils"
jest.setTimeout(30000)
const defaultUserData = [
{
id: "1",
email: "user_1@test.com",
},
{
id: "2",
email: "user_2@test.com",
},
]
describe("UserModuleService - User", () => {
let service: IUserModuleService
let testManager: SqlEntityManager
@@ -26,8 +37,6 @@ describe("UserModuleService - User", () => {
beforeEach(async () => {
await MikroOrmWrapper.setupDatabase()
testManager = MikroOrmWrapper.forkManager()
await createUsers(testManager)
})
afterEach(async () => {
@@ -38,19 +47,24 @@ describe("UserModuleService - User", () => {
await shutdownFunc()
})
describe("listUsers", () => {
describe("list", () => {
it("should list users", async () => {
const users = await service.list()
const serialized = JSON.parse(JSON.stringify(users))
await createUsers(testManager, defaultUserData)
expect(serialized).toEqual([
const users = await service.list()
expect(users).toEqual([
expect.objectContaining({
id: "1",
}),
expect.objectContaining({
id: "2",
}),
])
})
it("should list users by id", async () => {
await createUsers(testManager, defaultUserData)
const users = await service.list({
id: ["1"],
})
@@ -63,20 +77,24 @@ describe("UserModuleService - User", () => {
})
})
describe("listAndCountUsers", () => {
describe("listAndCount", () => {
it("should list and count users", async () => {
await createUsers(testManager, defaultUserData)
const [users, count] = await service.listAndCount()
const serialized = JSON.parse(JSON.stringify(users))
expect(count).toEqual(1)
expect(serialized).toEqual([
expect(count).toEqual(2)
expect(users).toEqual([
expect.objectContaining({
id: "1",
}),
expect.objectContaining({
id: "2",
}),
])
})
it("should listAndCount Users by id", async () => {
it("should list and count users by id", async () => {
await createUsers(testManager, defaultUserData)
const [Users, count] = await service.listAndCount({
id: "1",
})
@@ -90,10 +108,12 @@ describe("UserModuleService - User", () => {
})
})
describe("retrieveUser", () => {
describe("retrieve", () => {
const id = "1"
it("should return an user for the given id", async () => {
await createUsers(testManager, defaultUserData)
const user = await service.retrieve(id)
expect(user).toEqual(
@@ -104,13 +124,7 @@ describe("UserModuleService - User", () => {
})
it("should throw an error when an user with the given id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
const error = await service.retrieve("does-not-exist").catch((e) => e)
expect(error.message).toEqual(
"User with id: does-not-exist was not found"
@@ -118,18 +132,16 @@ describe("UserModuleService - User", () => {
})
it("should throw an error when a userId is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
const error = await service
.retrieve(undefined as unknown as string)
.catch((e) => e)
expect(error.message).toEqual("user - id must be defined")
})
it("should return user based on config select param", async () => {
await createUsers(testManager, defaultUserData)
const User = await service.retrieve(id, {
select: ["id"],
})
@@ -142,10 +154,12 @@ describe("UserModuleService - User", () => {
})
})
describe("deleteUser", () => {
describe("delete", () => {
const id = "1"
it("should delete the Users given an id successfully", async () => {
it("should delete the users given an id successfully", async () => {
await createUsers(testManager, defaultUserData)
await service.delete([id])
const users = await service.list({
@@ -156,40 +170,32 @@ describe("UserModuleService - User", () => {
})
})
describe("updateUser", () => {
describe("update", () => {
it("should throw an error when a id does not exist", async () => {
let error
try {
await service.update([
const error = await service
.update([
{
id: "does-not-exist",
},
])
} catch (e) {
error = e
}
.catch((e) => e)
expect(error.message).toEqual('User with id "does-not-exist" not found')
})
})
describe("createUser", () => {
it("should create a User successfully", async () => {
await service.create([
{
id: "2",
},
])
describe("create", () => {
it("should create a user successfully", async () => {
await service.create(defaultUserData)
const [User, count] = await service.listAndCount({
id: ["2"],
id: ["1"],
})
expect(count).toEqual(1)
expect(User[0]).toEqual(
expect.objectContaining({
id: "2",
id: "1",
})
)
})

View File

@@ -1,8 +1,12 @@
import * as entities from "./src/models"
import { TSMigrationGenerator } from "@medusajs/utils"
module.exports = {
entities: Object.values(entities),
schema: "public",
clientUrl: "postgres://postgres@localhost/medusa-user",
type: "postgresql",
migrations: {
generator: TSMigrationGenerator,
},
}

View File

@@ -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": [

View File

@@ -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;');
}
}

View File

@@ -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;')
}
}

View File

@@ -1 +1,2 @@
export { default as User } from "./user"
export { default as Invite } from "./invite"

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")
}
}

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")

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]
}
}

View File

@@ -8806,7 +8806,7 @@ __metadata:
languageName: unknown
linkType: soft
"@medusajs/user@workspace:packages/user":
"@medusajs/user@workspace:^, @medusajs/user@workspace:packages/user":
version: 0.0.0-use.local
resolution: "@medusajs/user@workspace:packages/user"
dependencies:
@@ -31576,6 +31576,7 @@ __metadata:
"@medusajs/promotion": "workspace:^"
"@medusajs/region": "workspace:^"
"@medusajs/types": "workspace:^"
"@medusajs/user": "workspace:^"
"@medusajs/utils": "workspace:^"
"@medusajs/workflow-engine-inmemory": "workspace:*"
babel-preset-medusa-package: "*"