Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(customer): admin CRUD endpoints #6232

Merged
merged 13 commits into from
Jan 30, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICustomerModuleService } from "@medusajs/types"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"

const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}

describe("POST /admin/customers", () => {
let dbConnection
let appContainer
let shutdownServer
let customerModuleService: ICustomerModuleService

beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
customerModuleService = appContainer.resolve(
ModuleRegistrationName.CUSTOMER
)
})

afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})

beforeEach(async () => {
await adminSeeder(dbConnection)
})

afterEach(async () => {
const db = useDb()
await db.teardown()
})

it("should create a customer", async () => {
const api = useApi() as any
const response = await api.post(
`/admin/customers`,
{
first_name: "John",
last_name: "Doe",
},
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data.customer).toEqual(
expect.objectContaining({
id: expect.any(String),
first_name: "John",
last_name: "Doe",
created_by: "admin_user",
})
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICustomerModuleService } from "@medusajs/types"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"

const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}

describe("DELETE /admin/customers/:id", () => {
let dbConnection
let appContainer
let shutdownServer
let customerModuleService: ICustomerModuleService

beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
customerModuleService = appContainer.resolve(
ModuleRegistrationName.CUSTOMER
)
})

afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})

beforeEach(async () => {
await adminSeeder(dbConnection)
})

afterEach(async () => {
const db = useDb()
await db.teardown()
})

it("should delete a customer", async () => {
const customer = await customerModuleService.create({
first_name: "John",
last_name: "Doe",
})

const api = useApi() as any
const response = await api.delete(
`/admin/customers/${customer.id}`,
adminHeaders
)

expect(response.status).toEqual(200)

const deletedCustomer = await customerModuleService.retrieve(customer.id, {
withDeleted: true,
})
expect(deletedCustomer.deleted_at).toBeTruthy()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICustomerModuleService } from "@medusajs/types"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"

const env = { MEDUSA_FF_MEDUSA_V2: true }
const adminHeaders = {
headers: { "x-medusa-access-token": "test_token" },
}

describe("POST /admin/customers/:id", () => {
let dbConnection
let appContainer
let shutdownServer
let customerModuleService: ICustomerModuleService

beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
customerModuleService = appContainer.resolve(
ModuleRegistrationName.CUSTOMER
)
})

afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})

beforeEach(async () => {
await adminSeeder(dbConnection)
})

afterEach(async () => {
const db = useDb()
await db.teardown()
})

it("should update a customer", async () => {
const customer = await customerModuleService.create({
first_name: "John",
last_name: "Doe",
})

const api = useApi() as any
const response = await api.post(
`/admin/customers/${customer.id}`,
{
first_name: "Jane",
},
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data.customer).toEqual(
expect.objectContaining({
id: expect.any(String),
first_name: "Jane",
last_name: "Doe",
})
)
})
})
2 changes: 2 additions & 0 deletions packages/core-flows/src/customer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./steps"
export * from "./workflows"
31 changes: 31 additions & 0 deletions packages/core-flows/src/customer/steps/create-customers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
import { CreateCustomerDTO, ICustomerModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"

export const createCustomersStepId = "create-customers"
export const createCustomersStep = createStep(
createCustomersStepId,
async (data: CreateCustomerDTO[], { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)

const createdCustomers = await service.create(data)

return new StepResponse(
createdCustomers,
createdCustomers.map((createdCustomers) => createdCustomers.id)
)
},
async (createdCustomerIds, { container }) => {
if (!createdCustomerIds?.length) {
return
}

const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)

await service.delete(createdCustomerIds)
}
)
30 changes: 30 additions & 0 deletions packages/core-flows/src/customer/steps/delete-customers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ICustomerModuleService } from "@medusajs/types"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"

type DeleteCustomerStepInput = string[]

export const deleteCustomerStepId = "delete-customer"
export const deleteCustomerStep = createStep(
deleteCustomerStepId,
Comment on lines +7 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Consistent with how other workflows and steps are named

Suggested change
export const deleteCustomerStepId = "delete-customer"
export const deleteCustomerStep = createStep(
deleteCustomerStepId,
export const deleteCustomersStepId = "delete-customer"
export const deleteCustomersStep = createStep(
deleteCustomerStepId,

async (ids: DeleteCustomerStepInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)

await service.softDelete(ids)

return new StepResponse(void 0, ids)
},
async (prevCustomerIds, { container }) => {
if (!prevCustomerIds?.length) {
return
}

const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)

await service.restore(prevCustomerIds)
}
)
3 changes: 3 additions & 0 deletions packages/core-flows/src/customer/steps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./create-customers"
export * from "./update-customers"
export * from "./delete-customers"
59 changes: 59 additions & 0 deletions packages/core-flows/src/customer/steps/update-customers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
FilterableCustomerProps,
ICustomerModuleService,
CustomerUpdatableFields,
} from "@medusajs/types"
import {
getSelectsAndRelationsFromObjectArray,
promiseAll,
} from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"

type UpdateCustomersStepInput = {
selector: FilterableCustomerProps
update: CustomerUpdatableFields
}

export const updateCustomersStepId = "update-customer"
export const updateCustomersStep = createStep(
updateCustomersStepId,
async (data: UpdateCustomersStepInput, { container }) => {
const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)

const { selects, relations } = getSelectsAndRelationsFromObjectArray([
data.update,
])
const prevCustomers = await service.list(data.selector, {
select: selects,
relations,
})

const customers = await service.update(data.selector, data.update)

return new StepResponse(customers, prevCustomers)
},
async (prevCustomers, { container }) => {
if (!prevCustomers?.length) {
return
}

const service = container.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)

await promiseAll(
prevCustomers.map((c) =>
service.update(c.id, {
first_name: c.first_name,
last_name: c.last_name,
email: c.email,
phone: c.phone,
metadata: c.metadata,
})
Comment on lines +48 to +55
Copy link
Contributor

Choose a reason for hiding this comment

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

This might backfire if any of these attributes are not present in the data object itself. Nothing to do here for now, just FYI I'm working on a util to sanitize the data to the needed selects and relationships when reverting an update.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nice!

)
)
}
)
13 changes: 13 additions & 0 deletions packages/core-flows/src/customer/workflows/create-customers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CustomerDTO, CreateCustomerDTO } from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { createCustomersStep } from "../steps"

type WorkflowInput = { customersData: CreateCustomerDTO[] }

export const createCustomersWorkflowId = "create-customers"
export const createCustomersWorkflow = createWorkflow(
createCustomersWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerDTO[]> => {
return createCustomersStep(input.customersData)
}
)
12 changes: 12 additions & 0 deletions packages/core-flows/src/customer/workflows/delete-customers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { deleteCustomerStep } from "../steps"

type WorkflowInput = { ids: string[] }

export const deleteCustomersWorkflowId = "delete-customers"
export const deleteCustomersWorkflow = createWorkflow(
deleteCustomersWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
return deleteCustomerStep(input.ids)
}
)
3 changes: 3 additions & 0 deletions packages/core-flows/src/customer/workflows/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./create-customers"
export * from "./update-customers"
export * from "./delete-customers"
22 changes: 22 additions & 0 deletions packages/core-flows/src/customer/workflows/update-customers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
CustomerDTO,
CustomerUpdatableFields,
FilterableCustomerProps,
} from "@medusajs/types"
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
import { updateCustomersStep } from "../steps"

type UpdateCustomersStepInput = {
selector: FilterableCustomerProps
update: CustomerUpdatableFields
}

type WorkflowInput = UpdateCustomersStepInput

export const updateCustomersWorkflowId = "update-customers"
export const updateCustomersWorkflow = createWorkflow(
updateCustomersWorkflowId,
(input: WorkflowData<WorkflowInput>): WorkflowData<CustomerDTO[]> => {
return updateCustomersStep(input)
}
)
1 change: 1 addition & 0 deletions packages/core-flows/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./definition"
export * from "./definitions"
export * as Handlers from "./handlers"
export * from "./promotion"
export * from "./customer"
Loading
Loading