feat(user, types): add invite and user properties + migration (#6327)
**What** - Add invite model - Populate user model
This commit is contained in:
7
.changeset/tame-kings-battle.md
Normal file
7
.changeset/tame-kings-battle.md
Normal 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
|
||||
@@ -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),
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
72
integration-tests/plugins/__tests__/users/list-users.spec.ts
Normal file
72
integration-tests/plugins/__tests__/users/list-users.spec.ts
Normal 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,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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" })
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -4,3 +4,4 @@ export * as Handlers from "./handlers"
|
||||
export * from "./promotion"
|
||||
export * from "./customer"
|
||||
export * from "./customer-group"
|
||||
export * from "./user"
|
||||
|
||||
2
packages/core-flows/src/user/index.ts
Normal file
2
packages/core-flows/src/user/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./steps"
|
||||
export * from "./workflows"
|
||||
22
packages/core-flows/src/user/steps/create-users.ts
Normal file
22
packages/core-flows/src/user/steps/create-users.ts
Normal 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)
|
||||
}
|
||||
)
|
||||
28
packages/core-flows/src/user/steps/delete-users.ts
Normal file
28
packages/core-flows/src/user/steps/delete-users.ts
Normal 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)
|
||||
}
|
||||
)
|
||||
3
packages/core-flows/src/user/steps/index.ts
Normal file
3
packages/core-flows/src/user/steps/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./delete-users"
|
||||
export * from "./create-users"
|
||||
export * from "./update-users"
|
||||
42
packages/core-flows/src/user/steps/update-users.ts
Normal file
42
packages/core-flows/src/user/steps/update-users.ts
Normal 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,
|
||||
}))
|
||||
)
|
||||
}
|
||||
)
|
||||
14
packages/core-flows/src/user/workflows/create-users.ts
Normal file
14
packages/core-flows/src/user/workflows/create-users.ts
Normal 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)
|
||||
}
|
||||
)
|
||||
13
packages/core-flows/src/user/workflows/delete-users.ts
Normal file
13
packages/core-flows/src/user/workflows/delete-users.ts
Normal 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)
|
||||
}
|
||||
)
|
||||
3
packages/core-flows/src/user/workflows/index.ts
Normal file
3
packages/core-flows/src/user/workflows/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./delete-users"
|
||||
export * from "./create-users"
|
||||
export * from "./update-users"
|
||||
14
packages/core-flows/src/user/workflows/update-users.ts
Normal file
14
packages/core-flows/src/user/workflows/update-users.ts
Normal 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)
|
||||
}
|
||||
)
|
||||
62
packages/medusa/src/api-v2/admin/users/[id]/route.ts
Normal file
62
packages/medusa/src/api-v2/admin/users/[id]/route.ts
Normal 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,
|
||||
})
|
||||
}
|
||||
39
packages/medusa/src/api-v2/admin/users/middlewares.ts
Normal file
39
packages/medusa/src/api-v2/admin/users/middlewares.ts
Normal 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)],
|
||||
},
|
||||
]
|
||||
25
packages/medusa/src/api-v2/admin/users/query-config.ts
Normal file
25
packages/medusa/src/api-v2/admin/users/query-config.ts
Normal 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,
|
||||
}
|
||||
48
packages/medusa/src/api-v2/admin/users/route.ts
Normal file
48
packages/medusa/src/api-v2/admin/users/route.ts
Normal 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 })
|
||||
}
|
||||
112
packages/medusa/src/api-v2/admin/users/validators.ts
Normal file
112
packages/medusa/src/api-v2/admin/users/validators.ts
Normal 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
|
||||
}
|
||||
@@ -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,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
5
packages/types/src/workflow/user/create-user.ts
Normal file
5
packages/types/src/workflow/user/create-user.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { CreateUserDTO } from "../../user"
|
||||
|
||||
export interface CreateUsersWorkflowInputDTO {
|
||||
users: CreateUserDTO[]
|
||||
}
|
||||
3
packages/types/src/workflow/user/delete-user.ts
Normal file
3
packages/types/src/workflow/user/delete-user.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface DeleteUserWorkflowInput {
|
||||
ids: string[]
|
||||
}
|
||||
3
packages/types/src/workflow/user/index.ts
Normal file
3
packages/types/src/workflow/user/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./create-user"
|
||||
export * from "./update-user"
|
||||
export * from "./delete-user"
|
||||
5
packages/types/src/workflow/user/update-user.ts
Normal file
5
packages/types/src/workflow/user/update-user.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { UpdateUserDTO } from "../../user"
|
||||
|
||||
export interface UpdateUsersWorkflowInputDTO {
|
||||
updates: UpdateUserDTO[]
|
||||
}
|
||||
17
packages/user/integration-tests/__fixtures__/invite.ts
Normal file
17
packages/user/integration-tests/__fixtures__/invite.ts
Normal 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)
|
||||
}
|
||||
@@ -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[] = []
|
||||
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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;');
|
||||
}
|
||||
|
||||
}
|
||||
53
packages/user/src/migrations/Migration20240214033943.ts
Normal file
53
packages/user/src/migrations/Migration20240214033943.ts
Normal 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;')
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { default as User } from "./user"
|
||||
export { default as Invite } from "./invite"
|
||||
|
||||
110
packages/user/src/models/invite.ts
Normal file
110
packages/user/src/models/invite.ts
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: "*"
|
||||
|
||||
Reference in New Issue
Block a user