fix: Switch to zod for customer endpoints, fix inconsistencies (#7094)

This commit is contained in:
Stevche Radevski
2024-04-18 10:30:45 +02:00
committed by GitHub
parent 44829f296a
commit ccb50bb3da
25 changed files with 432 additions and 521 deletions
@@ -13,7 +13,7 @@ const adminHeaders = {
medusaIntegrationTestRunner({ medusaIntegrationTestRunner({
env, env,
testSuite: ({ dbConnection, getContainer, api }) => { testSuite: ({ dbConnection, getContainer, api }) => {
describe("POST /admin/customer-groups/:id/customers/batch/add", () => { describe("POST /admin/customer-groups/:id/customers/batch", () => {
let appContainer let appContainer
let customerModuleService: ICustomerModuleService let customerModuleService: ICustomerModuleService
@@ -48,9 +48,9 @@ medusaIntegrationTestRunner({
]) ])
const response = await api.post( const response = await api.post(
`/admin/customer-groups/${group.id}/customers/batch/add`, `/admin/customer-groups/${group.id}/customers/batch`,
{ {
customer_ids: customers.map((c) => ({ id: c.id })), create: customers.map((c) => c.id),
}, },
adminHeaders adminHeaders
) )
@@ -13,7 +13,7 @@ const adminHeaders = {
medusaIntegrationTestRunner({ medusaIntegrationTestRunner({
env, env,
testSuite: ({ dbConnection, getContainer, api }) => { testSuite: ({ dbConnection, getContainer, api }) => {
describe("POST /admin/customer-groups/:id/customers/batch/remove", () => { describe("POST /admin/customer-groups/:id/customers/batch", () => {
let appContainer let appContainer
let customerModuleService: ICustomerModuleService let customerModuleService: ICustomerModuleService
@@ -55,9 +55,9 @@ medusaIntegrationTestRunner({
) )
const response = await api.post( const response = await api.post(
`/admin/customer-groups/${group.id}/customers/batch/remove`, `/admin/customer-groups/${group.id}/customers/batch`,
{ {
customer_ids: customers.map((c) => ({ id: c.id })), delete: customers.map((c) => c.id),
}, },
adminHeaders adminHeaders
) )
@@ -13,7 +13,7 @@ const adminHeaders = {
medusaIntegrationTestRunner({ medusaIntegrationTestRunner({
env, env,
testSuite: ({ dbConnection, getContainer, api }) => { testSuite: ({ dbConnection, getContainer, api }) => {
describe("GET /admin/customer-groups/:id/customers", () => { describe("GET customer group customers", () => {
let appContainer let appContainer
let customerModuleService: ICustomerModuleService let customerModuleService: ICustomerModuleService
@@ -54,7 +54,7 @@ medusaIntegrationTestRunner({
) )
const response = await api.get( const response = await api.get(
`/admin/customer-groups/${group.id}/customers`, `/admin/customers?groups[]=${group.id}`,
adminHeaders adminHeaders
) )
@@ -35,7 +35,7 @@ medusaIntegrationTestRunner({
}) })
const response = await api.post( const response = await api.post(
`/admin/customers/${customer.id}/addresses`, `/admin/customers/${customer.id}/addresses?fields=*addresses`,
{ {
first_name: "John", first_name: "John",
last_name: "Doe", last_name: "Doe",
@@ -45,13 +45,15 @@ medusaIntegrationTestRunner({
) )
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.address).toEqual( expect(response.data.customer.addresses).toEqual(
expect.objectContaining({ expect.arrayContaining([
id: expect.any(String), expect.objectContaining({
first_name: "John", id: expect.any(String),
last_name: "Doe", first_name: "John",
address_1: "Test street 1", last_name: "Doe",
}) address_1: "Test street 1",
}),
])
) )
const customerWithAddresses = await customerModuleService.retrieve( const customerWithAddresses = await customerModuleService.retrieve(
@@ -42,7 +42,7 @@ medusaIntegrationTestRunner({
}) })
const response = await api.post( const response = await api.post(
`/admin/customers/${customer.id}/addresses/${address.id}`, `/admin/customers/${customer.id}/addresses/${address.id}?fields=*addresses`,
{ {
first_name: "Jane", first_name: "Jane",
}, },
@@ -50,12 +50,14 @@ medusaIntegrationTestRunner({
) )
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.address).toEqual( expect(response.data.customer.addresses).toEqual(
expect.objectContaining({ expect.arrayContaining([
id: expect.any(String), expect.objectContaining({
first_name: "Jane", id: expect.any(String),
last_name: "Doe", first_name: "Jane",
}) last_name: "Doe",
}),
])
) )
}) })
+9 -1
View File
@@ -1,11 +1,12 @@
import { Modules } from "@medusajs/modules-sdk" import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types" import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils" import { MapToConfig } from "@medusajs/utils"
import { Customer, CustomerGroup } from "@models" import { Address, Customer, CustomerGroup } from "@models"
export const LinkableKeys = { export const LinkableKeys = {
customer_id: Customer.name, customer_id: Customer.name,
customer_group_id: CustomerGroup.name, customer_group_id: CustomerGroup.name,
customer_address_id: Address.name,
} }
const entityLinkableKeysMap: MapToConfig = {} const entityLinkableKeysMap: MapToConfig = {}
@@ -37,5 +38,12 @@ export const joinerConfig: ModuleJoinerConfig = {
methodSuffix: "CustomerGroups", methodSuffix: "CustomerGroups",
}, },
}, },
{
name: ["customer_address", "customer_addresses"],
args: {
entity: Address.name,
methodSuffix: "Addresses",
},
},
], ],
} }
@@ -12,10 +12,8 @@ import {
import { import {
InjectManager, InjectManager,
InjectTransactionManager, InjectTransactionManager,
isDuplicateError,
isString, isString,
MedusaContext, MedusaContext,
MedusaError,
ModulesSdkUtils, ModulesSdkUtils,
} from "@medusajs/utils" } from "@medusajs/utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
@@ -26,10 +24,6 @@ import {
CustomerGroupCustomer, CustomerGroupCustomer,
} from "@models" } from "@models"
import { EntityManager } from "@mikro-orm/core" import { EntityManager } from "@mikro-orm/core"
import {
UNIQUE_CUSTOMER_BILLING_ADDRESS,
UNIQUE_CUSTOMER_SHIPPING_ADDRESS,
} from "../models/address"
type InjectedDependencies = { type InjectedDependencies = {
baseRepository: DAL.RepositoryService baseRepository: DAL.RepositoryService
@@ -39,7 +33,11 @@ type InjectedDependencies = {
customerGroupCustomerService: ModulesSdkTypes.InternalModuleService<any> customerGroupCustomerService: ModulesSdkTypes.InternalModuleService<any>
} }
const generateMethodForModels = [Address, CustomerGroup, CustomerGroupCustomer] const generateMethodForModels = [
{ model: Address, singular: "Address", plural: "Addresses" },
CustomerGroup,
CustomerGroupCustomer,
]
export default class CustomerModuleService< export default class CustomerModuleService<
TAddress extends Address = Address, TAddress extends Address = Address,
@@ -47,7 +45,6 @@ export default class CustomerModuleService<
TCustomerGroup extends CustomerGroup = CustomerGroup, TCustomerGroup extends CustomerGroup = CustomerGroup,
TCustomerGroupCustomer extends CustomerGroupCustomer = CustomerGroupCustomer TCustomerGroupCustomer extends CustomerGroupCustomer = CustomerGroupCustomer
> >
// TODO seb I let you manage that when you are moving forward
extends ModulesSdkUtils.abstractModuleServiceFactory< extends ModulesSdkUtils.abstractModuleServiceFactory<
InjectedDependencies, InjectedDependencies,
CustomerDTO, CustomerDTO,
@@ -1,48 +0,0 @@
import { createCustomerGroupCustomersWorkflow } from "@medusajs/core-flows"
import { AdminCustomerGroupResponse } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../../types/routing"
import { AdminPostCustomerGroupsGroupCustomersBatchReq } from "../../../../validators"
export const POST = async (
// eslint-disable-next-line max-len
req: AuthenticatedMedusaRequest<AdminPostCustomerGroupsGroupCustomersBatchReq>,
res: MedusaResponse<AdminCustomerGroupResponse>
) => {
const { id } = req.params
const { customer_ids } = req.validatedBody
const createCustomers = createCustomerGroupCustomersWorkflow(req.scope)
const { result, errors } = await createCustomers.run({
input: {
groupCustomers: customer_ids.map((c) => ({
customer_id: c.id,
customer_group_id: id,
})),
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "customer_group",
variables: { id },
fields: req.remoteQueryConfig.fields,
})
const [customer_group] = await remoteQuery(queryObject)
res.status(200).json({ customer_group })
}
@@ -1,48 +0,0 @@
import { deleteCustomerGroupCustomersWorkflow } from "@medusajs/core-flows"
import { AdminCustomerGroupResponse } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../../types/routing"
import { AdminPostCustomerGroupsGroupCustomersBatchReq } from "../../../../validators"
export const POST = async (
// eslint-disable-next-line max-len
req: AuthenticatedMedusaRequest<AdminPostCustomerGroupsGroupCustomersBatchReq>,
res: MedusaResponse<AdminCustomerGroupResponse>
) => {
const { id } = req.params
const { customer_ids } = req.validatedBody
const deleteCustomers = deleteCustomerGroupCustomersWorkflow(req.scope)
const { errors } = await deleteCustomers.run({
input: {
groupCustomers: customer_ids.map((c) => ({
customer_id: c.id,
customer_group_id: id,
})),
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "customer_group",
variables: { id },
fields: req.remoteQueryConfig.fields,
})
const [customer_group] = await remoteQuery(queryObject)
res.status(200).json({ customer_group })
}
@@ -0,0 +1,66 @@
import { AdminCustomerGroupResponse, BatchMethodRequest } from "@medusajs/types"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../types/routing"
import { AdminSetCustomersCustomerGroupType } from "../../../validators"
import {
createCustomerGroupCustomersWorkflow,
deleteCustomerGroupCustomersWorkflow,
} from "@medusajs/core-flows"
import { refetchCustomerGroup } from "../../../helpers"
export const POST = async (
req: AuthenticatedMedusaRequest<
BatchMethodRequest<
AdminSetCustomersCustomerGroupType,
AdminSetCustomersCustomerGroupType
>
>,
res: MedusaResponse<AdminCustomerGroupResponse>
) => {
const { id } = req.params
const { create, delete: toDelete } = req.validatedBody
if (!!create && create?.length > 0) {
const createCustomers = createCustomerGroupCustomersWorkflow(req.scope)
const { errors } = await createCustomers.run({
input: {
groupCustomers: create.map((c) => ({
customer_id: c,
customer_group_id: id,
})),
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
}
if (!!toDelete && toDelete?.length > 0) {
const deleteCustomers = deleteCustomerGroupCustomersWorkflow(req.scope)
const { errors } = await deleteCustomers.run({
input: {
groupCustomers: toDelete.map((c) => ({
customer_id: c,
customer_group_id: id,
})),
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
}
const customerGroup = await refetchCustomerGroup(
id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer_group: customerGroup })
}
@@ -1,32 +0,0 @@
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import { ICustomerModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const { id } = req.params
const service = req.scope.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const [customers, count] = await service.listAndCount(
{ ...req.filterableFields, groups: id },
req.listConfig
)
const { offset, limit } = req.validatedQuery
res.json({
count,
customers,
offset,
limit,
})
}
@@ -2,38 +2,29 @@ import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import {
CustomerGroupUpdatableFields,
ICustomerModuleService,
} from "@medusajs/types"
import { import {
deleteCustomerGroupsWorkflow, deleteCustomerGroupsWorkflow,
updateCustomerGroupsWorkflow, updateCustomerGroupsWorkflow,
} from "@medusajs/core-flows" } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { refetchCustomerGroup } from "../helpers"
import { AdminUpdateCustomerGroupType } from "../validators"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const customerModuleService = req.scope.resolve<ICustomerModuleService>( const customerGroup = await refetchCustomerGroup(
ModuleRegistrationName.CUSTOMER
)
const group = await customerModuleService.retrieveCustomerGroup(
req.params.id, req.params.id,
{ req.scope,
select: req.retrieveConfig.select, req.remoteQueryConfig.fields
relations: req.retrieveConfig.relations,
}
) )
res.status(200).json({ customer_group: group }) res.status(200).json({ customer_group: customerGroup })
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<CustomerGroupUpdatableFields>, req: AuthenticatedMedusaRequest<AdminUpdateCustomerGroupType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const updateGroups = updateCustomerGroupsWorkflow(req.scope) const updateGroups = updateCustomerGroupsWorkflow(req.scope)
@@ -49,7 +40,12 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
res.status(200).json({ customer_group: result[0] }) const customerGroup = await refetchCustomerGroup(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer_group: customerGroup })
} }
export const DELETE = async ( export const DELETE = async (
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchCustomerGroup = async (
customerGroupId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "customer_group",
variables: {
filters: { id: customerGroupId },
},
fields: fields,
})
const customerGroups = await remoteQuery(queryObject)
return customerGroups[0]
}
@@ -1,42 +1,39 @@
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { transformBody, transformQuery } from "../../../api/middlewares"
import {
AdminDeleteCustomerGroupsGroupCustomersBatchReq,
AdminGetCustomerGroupsGroupCustomersParams,
AdminGetCustomerGroupsGroupParams,
AdminGetCustomerGroupsParams,
AdminPostCustomerGroupsGroupCustomersBatchReq,
AdminPostCustomerGroupsGroupReq,
AdminPostCustomerGroupsReq,
} from "./validators"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { authenticate } from "../../../utils/authenticate-middleware" import { authenticate } from "../../../utils/authenticate-middleware"
import { listTransformQueryConfig as customersListTransformQueryConfig } from "../customers/query-config" import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateCustomerGroup,
AdminGetCustomerGroupParams,
AdminGetCustomerGroupsParams,
AdminSetCustomersCustomerGroup,
AdminUpdateCustomerGroup,
} from "./validators"
import { validateAndTransformBody } from "../../utils/validate-body"
import { createBatchBody } from "../../utils/validators"
export const adminCustomerGroupRoutesMiddlewares: MiddlewareRoute[] = [ export const adminCustomerGroupRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["ALL"],
matcher: "/admin/customer-groups*",
middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
},
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/customer-groups", matcher: "/admin/customer-groups",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetCustomerGroupsParams, AdminGetCustomerGroupsParams,
QueryConfig.listTransformQueryConfig QueryConfig.listTransformQueryConfig
), ),
], ],
}, },
{
method: ["ALL"],
matcher: "/admin/customer-groups*",
middlewares: [authenticate("admin", ["bearer", "session"])],
},
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/customer-groups/:id", matcher: "/admin/customer-groups/:id",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetCustomerGroupsGroupParams, AdminGetCustomerGroupParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -44,41 +41,37 @@ export const adminCustomerGroupRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/customer-groups", matcher: "/admin/customer-groups",
middlewares: [transformBody(AdminPostCustomerGroupsReq)],
},
{
method: ["POST"],
matcher: "/admin/customer-groups/:id",
middlewares: [transformBody(AdminPostCustomerGroupsGroupReq)],
},
{
method: ["GET"],
matcher: "/admin/customer-groups/:id/customers",
middlewares: [ middlewares: [
transformQuery( validateAndTransformBody(AdminCreateCustomerGroup),
AdminGetCustomerGroupsGroupCustomersParams, validateAndTransformQuery(
customersListTransformQueryConfig AdminGetCustomerGroupParams,
),
],
},
{
method: ["POST"],
matcher: "/admin/customer-groups/:id/customers/batch/add",
middlewares: [
transformBody(AdminPostCustomerGroupsGroupCustomersBatchReq),
transformQuery(
AdminGetCustomerGroupsGroupParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/customer-groups/:id/customers/batch/remove", matcher: "/admin/customer-groups/:id",
middlewares: [ middlewares: [
transformBody(AdminDeleteCustomerGroupsGroupCustomersBatchReq), validateAndTransformBody(AdminUpdateCustomerGroup),
transformQuery( validateAndTransformQuery(
AdminGetCustomerGroupsGroupParams, AdminGetCustomerGroupParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/customer-groups/:id/customers/batch",
middlewares: [
validateAndTransformBody(
createBatchBody(
AdminSetCustomersCustomerGroup,
AdminSetCustomersCustomerGroup
)
),
validateAndTransformQuery(
AdminGetCustomerGroupParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -1,17 +1,14 @@
export const defaultAdminCustomerGroupRelations = []
export const allowedAdminCustomerGroupRelations = ["customers"]
export const defaultAdminCustomerGroupFields = [ export const defaultAdminCustomerGroupFields = [
"id", "id",
"name", "name",
"created_by",
"created_at", "created_at",
"updated_at", "updated_at",
"deleted_at", "deleted_at",
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminCustomerGroupFields, defaults: defaultAdminCustomerGroupFields,
defaultRelations: defaultAdminCustomerGroupRelations,
allowedRelations: allowedAdminCustomerGroupRelations,
isList: false, isList: false,
} }
@@ -1,39 +1,42 @@
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../types/routing" } from "../../../types/routing"
import { CreateCustomerGroupDTO, ICustomerModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { createCustomerGroupsWorkflow } from "@medusajs/core-flows" import { createCustomerGroupsWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { AdminCreateCustomerGroupType } from "./validators"
import { refetchCustomerGroup } from "./helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const customerModuleService = req.scope.resolve<ICustomerModuleService>( const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
ModuleRegistrationName.CUSTOMER
)
const [groups, count] = const query = remoteQueryObjectFromString({
await customerModuleService.listAndCountCustomerGroups( entryPoint: "customer_group",
req.filterableFields, variables: {
req.listConfig filters: req.filterableFields,
) ...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
})
const { offset, limit } = req.validatedQuery const { rows: customer_groups, metadata } = await remoteQuery(query)
res.json({ res.json({
count, customer_groups,
customer_groups: groups, count: metadata.count,
offset, offset: metadata.skip,
limit, limit: metadata.take,
}) })
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<CreateCustomerGroupDTO>, req: AuthenticatedMedusaRequest<AdminCreateCustomerGroupType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const createGroups = createCustomerGroupsWorkflow(req.scope) const createGroups = createCustomerGroupsWorkflow(req.scope)
@@ -53,5 +56,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
res.status(200).json({ customer_group: result[0] }) const customerGroup = await refetchCustomerGroup(
result[0].id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer_group: customerGroup })
} }
@@ -1,183 +1,73 @@
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
import { import {
IsNotEmpty, createFindParams,
IsOptional, createOperatorMap,
IsString, createSelectParams,
ValidateNested, } from "../../utils/validators"
} from "class-validator" import { z } from "zod"
import { Transform, Type } from "class-transformer"
import { IsType } from "../../../utils" export type AdminGetCustomerGroupParamsType = z.infer<
import { OperatorMap } from "@medusajs/types" typeof AdminGetCustomerGroupParams
import { OperatorMapValidator } from "../../../types/validators/operator-map" >
export const AdminGetCustomerGroupParams = createSelectParams()
export class AdminGetCustomerGroupsGroupParams extends FindParams {} export const AdminCustomerInGroupFilters = z.object({
id: z.union([z.string(), z.array(z.string())]).optional(),
email: z
.union([z.string(), z.array(z.string()), createOperatorMap()])
.optional(),
default_billing_address_id: z
.union([z.string(), z.array(z.string())])
.optional(),
default_shipping_address_id: z
.union([z.string(), z.array(z.string())])
.optional(),
company_name: z.union([z.string(), z.array(z.string())]).optional(),
first_name: z.union([z.string(), z.array(z.string())]).optional(),
last_name: z.union([z.string(), z.array(z.string())]).optional(),
created_by: z.union([z.string(), z.array(z.string())]).optional(),
created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(),
})
class FilterableCustomerPropsValidator { export type AdminGetCustomerGroupsParamsType = z.infer<
@IsOptional() typeof AdminGetCustomerGroupsParams
@IsString({ each: true }) >
id?: string | string[] export const AdminGetCustomerGroupsParams = createFindParams({
limit: 50,
@IsOptional()
@ValidateNested({ each: true })
@Type(() => OperatorMapValidator)
email?: string | string[] | OperatorMap<string>
@IsOptional()
@IsString({ each: true })
default_billing_address_id?: string | string[] | null
@IsOptional()
@IsString({ each: true })
default_shipping_address_id?: string | string[] | null
@IsOptional()
@IsString({ each: true })
company_name?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
first_name?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
last_name?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
created_by?: string | string[] | null
@IsOptional()
@ValidateNested()
@Type(() => OperatorMapValidator)
created_at?: OperatorMap<string>
@IsOptional()
@ValidateNested()
@Type(() => OperatorMapValidator)
updated_at?: OperatorMap<string>
}
export class AdminGetCustomerGroupsParams extends extendedFindParamsMixin({
limit: 100,
offset: 0, offset: 0,
}) { }).merge(
@IsOptional() z.object({
@IsString() q: z.string().optional(),
q?: string id: z.union([z.string(), z.array(z.string())]).optional(),
name: z.union([z.string(), z.array(z.string())]).optional(),
customers: z
.union([z.string(), z.array(z.string()), AdminCustomerInGroupFilters])
.optional(),
created_by: z.union([z.string(), z.array(z.string())]).optional(),
created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(),
$and: z.lazy(() => AdminGetCustomerGroupsParams.array()).optional(),
$or: z.lazy(() => AdminGetCustomerGroupsParams.array()).optional(),
})
)
@IsOptional() export type AdminCreateCustomerGroupType = z.infer<
@IsString({ each: true }) typeof AdminCreateCustomerGroup
id?: string | string[] >
export const AdminCreateCustomerGroup = z.object({
name: z.string(),
})
@IsOptional() export type AdminUpdateCustomerGroupType = z.infer<
@ValidateNested({ each: true }) typeof AdminUpdateCustomerGroup
@Type(() => OperatorMapValidator) >
name?: string | OperatorMap<string> export const AdminUpdateCustomerGroup = z.object({
name: z.string(),
})
@IsOptional() export type AdminSetCustomersCustomerGroupType = z.infer<
@ValidateNested() typeof AdminSetCustomersCustomerGroup
@Type(() => FilterableCustomerPropsValidator) >
customers?: FilterableCustomerPropsValidator | string | string[] export const AdminSetCustomersCustomerGroup = z.string()
@IsOptional()
@IsString({ each: true })
created_by?: string | string[] | null
@IsOptional()
@ValidateNested()
@Type(() => OperatorMapValidator)
created_at?: OperatorMap<string>
@IsOptional()
@ValidateNested()
@Type(() => OperatorMapValidator)
updated_at?: OperatorMap<string>
// Additional filters from BaseFilterable
@IsOptional()
@ValidateNested({ each: true })
@Type(() => AdminGetCustomerGroupsParams)
$and?: AdminGetCustomerGroupsParams[]
@IsOptional()
@ValidateNested({ each: true })
@Type(() => AdminGetCustomerGroupsParams)
$or?: AdminGetCustomerGroupsParams[]
}
export class AdminPostCustomerGroupsReq {
@IsNotEmpty()
@IsString()
name: string
}
export class AdminPostCustomerGroupsGroupReq {
@IsNotEmpty()
@IsString()
@IsOptional()
name?: string
}
export class AdminGetCustomerGroupsGroupCustomersParams extends extendedFindParamsMixin(
{
limit: 100,
offset: 0,
}
) {
@IsOptional()
@IsString()
q?: string
@IsOptional()
@IsString({ each: true })
id?: string | string[]
@IsOptional()
@IsType([String, [String], OperatorMapValidator])
email?: string | string[] | OperatorMap<string>
@IsOptional()
@IsString({ each: true })
company_name?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
first_name?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsType([String, [String], OperatorMapValidator])
@Transform(({ value }) => (value === "null" ? null : value))
last_name?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
created_by?: string | string[] | null
@IsOptional()
@ValidateNested()
@Type(() => OperatorMapValidator)
created_at?: OperatorMap<string>
@IsOptional()
@ValidateNested()
@Type(() => OperatorMapValidator)
updated_at?: OperatorMap<string>
}
class CustomerGroupsBatchCustomer {
@IsString()
id: string
}
export class AdminDeleteCustomerGroupsGroupCustomersBatchReq {
@ValidateNested({ each: true })
@Type(() => CustomerGroupsBatchCustomer)
customer_ids: CustomerGroupsBatchCustomer[]
}
export class AdminPostCustomerGroupsGroupCustomersBatchReq {
@ValidateNested({ each: true })
@Type(() => CustomerGroupsBatchCustomer)
customer_ids: CustomerGroupsBatchCustomer[]
}
@@ -2,35 +2,38 @@ import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../../types/routing" } from "../../../../../../types/routing"
import { CustomerAddressDTO, ICustomerModuleService } from "@medusajs/types"
import { import {
deleteCustomerAddressesWorkflow, deleteCustomerAddressesWorkflow,
updateCustomerAddressesWorkflow, updateCustomerAddressesWorkflow,
} from "@medusajs/core-flows" } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { AdminCreateCustomerAddressType } from "../../../validators"
import { refetchCustomer } from "../../../helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const customerModuleService = req.scope.resolve<ICustomerModuleService>( const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
ModuleRegistrationName.CUSTOMER const queryObject = remoteQueryObjectFromString({
) entryPoint: "customer_address",
variables: {
filters: { id: req.params.address_id, customer_id: req.params.id },
},
fields: req.remoteQueryConfig.fields,
})
const [address] = await customerModuleService.listAddresses( const [address] = await remoteQuery(queryObject)
{ id: req.params.address_id, customer_id: req.params.id },
{
select: req.retrieveConfig.select,
relations: req.retrieveConfig.relations,
}
)
res.status(200).json({ address }) res.status(200).json({ address })
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<Partial<CustomerAddressDTO>>, req: AuthenticatedMedusaRequest<AdminCreateCustomerAddressType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const updateAddresses = updateCustomerAddressesWorkflow(req.scope) const updateAddresses = updateCustomerAddressesWorkflow(req.scope)
@@ -46,7 +49,13 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
res.status(200).json({ address: result[0] }) const customer = await refetchCustomer(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer })
} }
export const DELETE = async ( export const DELETE = async (
@@ -65,9 +74,16 @@ export const DELETE = async (
throw errors[0].error throw errors[0].error
} }
const customer = await refetchCustomer(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ res.status(200).json({
id, id,
object: "address", object: "customer_address",
deleted: true, deleted: true,
parent: customer,
}) })
} }
@@ -1,41 +1,43 @@
import { createCustomerAddressesWorkflow } from "@medusajs/core-flows" import { createCustomerAddressesWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CreateCustomerAddressDTO,
ICustomerModuleService,
} from "@medusajs/types"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../types/routing" } from "../../../../../types/routing"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { AdminCreateCustomerAddressType } from "../../validators"
import { refetchCustomer } from "../../helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const customerId = req.params.id const customerId = req.params.id
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const customerModuleService = req.scope.resolve<ICustomerModuleService>( const query = remoteQueryObjectFromString({
ModuleRegistrationName.CUSTOMER entryPoint: "customer_address",
) variables: {
filters: { ...req.filterableFields, customer_id: customerId },
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
})
const [addresses, count] = await customerModuleService.listAndCountAddresses( const { rows: addresses, metadata } = await remoteQuery(query)
{ ...req.filterableFields, customer_id: customerId },
req.listConfig
)
const { offset, limit } = req.validatedQuery
res.json({ res.json({
count,
addresses, addresses,
offset, count: metadata.count,
limit, offset: metadata.skip,
limit: metadata.take,
}) })
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<CreateCustomerAddressDTO>, req: AuthenticatedMedusaRequest<AdminCreateCustomerAddressType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const customerId = req.params.id const customerId = req.params.id
@@ -56,5 +58,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
res.status(200).json({ address: result[0] }) const customer = await refetchCustomer(
customerId,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer })
} }
@@ -2,37 +2,37 @@ import {
deleteCustomersWorkflow, deleteCustomersWorkflow,
updateCustomersWorkflow, updateCustomersWorkflow,
} from "@medusajs/core-flows" } from "@medusajs/core-flows"
import { AdminCustomerResponse, CustomerUpdatableFields } from "@medusajs/types" import { AdminCustomerResponse } from "@medusajs/types"
import { import { MedusaError } from "@medusajs/utils"
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { refetchCustomer } from "../helpers"
import { AdminUpdateCustomerType } from "../validators"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse<AdminCustomerResponse> res: MedusaResponse<AdminCustomerResponse>
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const customer = await refetchCustomer(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
const variables = { id: req.params.id } if (!customer) {
throw new MedusaError(
const queryObject = remoteQueryObjectFromString({ MedusaError.Types.NOT_FOUND,
entryPoint: "customer", `Customer with id: ${req.params.id} not found`
variables, )
fields: req.remoteQueryConfig.fields, }
})
const [customer] = await remoteQuery(queryObject)
res.status(200).json({ customer }) res.status(200).json({ customer })
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<CustomerUpdatableFields>, req: AuthenticatedMedusaRequest<AdminUpdateCustomerType>,
res: MedusaResponse<AdminCustomerResponse> res: MedusaResponse<AdminCustomerResponse>
) => { ) => {
const { errors } = await updateCustomersWorkflow(req.scope).run({ const { errors } = await updateCustomersWorkflow(req.scope).run({
@@ -47,18 +47,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const customer = await refetchCustomer(
req.params.id,
const queryObject = remoteQueryObjectFromString({ req.scope,
entryPoint: "customer", req.remoteQueryConfig.fields
variables: { )
filters: { id: req.params.id },
},
fields: req.remoteQueryConfig.fields,
})
const [customer] = await remoteQuery(queryObject)
res.status(200).json({ customer }) res.status(200).json({ customer })
} }
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchCustomer = async (
customerId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "customer",
variables: {
filters: { id: customerId },
},
fields: fields,
})
const customers = await remoteQuery(queryObject)
return customers[0]
}
@@ -19,7 +19,7 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: ["ALL"], method: ["ALL"],
matcher: "/admin/customers*", matcher: "/admin/customers*",
middlewares: [authenticate("admin", ["bearer", "session"])], middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
}, },
{ {
method: ["GET"], method: ["GET"],
@@ -38,7 +38,7 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [
validateAndTransformBody(AdminCreateCustomer), validateAndTransformBody(AdminCreateCustomer),
validateAndTransformQuery( validateAndTransformQuery(
AdminCustomerParams, AdminCustomerParams,
QueryConfig.listTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
}, },
@@ -48,7 +48,7 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [
middlewares: [ middlewares: [
validateAndTransformQuery( validateAndTransformQuery(
AdminCustomerParams, AdminCustomerParams,
QueryConfig.listTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
}, },
@@ -59,19 +59,41 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [
validateAndTransformBody(AdminUpdateCustomer), validateAndTransformBody(AdminUpdateCustomer),
validateAndTransformQuery( validateAndTransformQuery(
AdminCustomerParams, AdminCustomerParams,
QueryConfig.listTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/customers/:id/addresses", matcher: "/admin/customers/:id/addresses",
middlewares: [validateAndTransformBody(AdminCreateCustomerAddress)], middlewares: [
validateAndTransformBody(AdminCreateCustomerAddress),
validateAndTransformQuery(
AdminCustomerParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/customers/:id/addresses/:address_id", matcher: "/admin/customers/:id/addresses/:address_id",
middlewares: [validateAndTransformBody(AdminUpdateCustomerAddress)], middlewares: [
validateAndTransformBody(AdminUpdateCustomerAddress),
validateAndTransformQuery(
AdminCustomerParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["DELETE"],
matcher: "/admin/customers/:id/addresses/:address_id",
middlewares: [
validateAndTransformQuery(
AdminCustomerParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["GET"], method: ["GET"],
@@ -1,5 +1,3 @@
export const defaultAdminCustomerRelations = []
export const allowedAdminCustomerRelations = ["groups", "addresses"]
export const defaultAdminCustomerFields = [ export const defaultAdminCustomerFields = [
"id", "id",
"company_name", "company_name",
@@ -7,15 +5,14 @@ export const defaultAdminCustomerFields = [
"last_name", "last_name",
"email", "email",
"phone", "phone",
"created_by",
"created_at", "created_at",
"updated_at", "updated_at",
"deleted_at", "deleted_at",
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminCustomerFields, defaults: defaultAdminCustomerFields,
defaultRelations: defaultAdminCustomerRelations,
allowedRelations: allowedAdminCustomerRelations,
isList: false, isList: false,
} }
@@ -24,8 +21,6 @@ export const listTransformQueryConfig = {
isList: true, isList: true,
} }
export const defaultAdminCustomerAddressRelations = []
export const allowedAdminCustomerAddressRelations = ["customer"]
export const defaultAdminCustomerAddressFields = [ export const defaultAdminCustomerAddressFields = [
"id", "id",
"company", "company",
@@ -45,9 +40,7 @@ export const defaultAdminCustomerAddressFields = [
] ]
export const retrieveAddressTransformQueryConfig = { export const retrieveAddressTransformQueryConfig = {
defaultFields: defaultAdminCustomerAddressFields, defaults: defaultAdminCustomerAddressFields,
defaultRelations: defaultAdminCustomerAddressRelations,
allowedRelations: allowedAdminCustomerAddressRelations,
isList: false, isList: false,
} }
@@ -3,27 +3,29 @@ import {
AdminCustomerListResponse, AdminCustomerListResponse,
AdminCustomerResponse, AdminCustomerResponse,
} from "@medusajs/types" } from "@medusajs/types"
import { remoteQueryObjectFromString } from "@medusajs/utils" import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../types/routing" } from "../../../types/routing"
import { AdminCreateCustomerType } from "./validators" import { AdminCreateCustomerType } from "./validators"
import { refetchCustomer } from "./helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse<AdminCustomerListResponse> res: MedusaResponse<AdminCustomerListResponse>
) => { ) => {
const remoteQuery = req.scope.resolve("remoteQuery") const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const variables = {
filters: req.filterableFields,
...req.remoteQueryConfig.pagination,
}
const query = remoteQueryObjectFromString({ const query = remoteQueryObjectFromString({
entryPoint: "customers", entryPoint: "customers",
variables, variables: {
filters: req.filterableFields,
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields, fields: req.remoteQueryConfig.fields,
}) })
@@ -59,5 +61,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
res.status(200).json({ customer: result[0] as AdminCustomerResponse["customer"] }) const customer = await refetchCustomer(
result[0].id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer })
} }
@@ -6,7 +6,6 @@ import {
} from "../../utils/validators" } from "../../utils/validators"
export const AdminCustomerParams = createSelectParams() export const AdminCustomerParams = createSelectParams()
export const AdminCustomerGroupParams = createSelectParams()
export const AdminCustomerGroupInCustomerParams = z.object({ export const AdminCustomerGroupInCustomerParams = z.object({
id: z.union([z.string(), z.array(z.string())]).optional(), id: z.union([z.string(), z.array(z.string())]).optional(),
@@ -35,9 +34,9 @@ export const AdminCustomersParams = createFindParams({
first_name: z.union([z.string(), z.array(z.string())]).optional(), first_name: z.union([z.string(), z.array(z.string())]).optional(),
last_name: z.union([z.string(), z.array(z.string())]).optional(), last_name: z.union([z.string(), z.array(z.string())]).optional(),
created_by: z.union([z.string(), z.array(z.string())]).optional(), created_by: z.union([z.string(), z.array(z.string())]).optional(),
created_at: createOperatorMap().optional().optional(), created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional().optional(), updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional().optional(), deleted_at: createOperatorMap().optional(),
$and: z.lazy(() => AdminCustomersParams.array()).optional(), $and: z.lazy(() => AdminCustomersParams.array()).optional(),
$or: z.lazy(() => AdminCustomersParams.array()).optional(), $or: z.lazy(() => AdminCustomersParams.array()).optional(),
}) })
@@ -101,12 +100,6 @@ export const AdminCustomerAdressesParams = createFindParams({
) )
export type AdminCustomerParamsType = z.infer<typeof AdminCustomerParams> export type AdminCustomerParamsType = z.infer<typeof AdminCustomerParams>
export type AdminCustomerGroupParamsType = z.infer<
typeof AdminCustomerGroupParams
>
export type AdminCustomerGroupInCustomerParamsType = z.infer<
typeof AdminCustomerGroupInCustomerParams
>
export type AdminCustomersParamsType = z.infer<typeof AdminCustomersParams> export type AdminCustomersParamsType = z.infer<typeof AdminCustomersParams>
export type AdminCreateCustomerType = z.infer<typeof AdminCreateCustomer> export type AdminCreateCustomerType = z.infer<typeof AdminCreateCustomer>
export type AdminUpdateCustomerType = z.infer<typeof AdminUpdateCustomer> export type AdminUpdateCustomerType = z.infer<typeof AdminUpdateCustomer>