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

View File

@@ -13,7 +13,7 @@ const adminHeaders = {
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe("POST /admin/customer-groups/:id/customers/batch/add", () => {
describe("POST /admin/customer-groups/:id/customers/batch", () => {
let appContainer
let customerModuleService: ICustomerModuleService
@@ -48,9 +48,9 @@ medusaIntegrationTestRunner({
])
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
)

View File

@@ -13,7 +13,7 @@ const adminHeaders = {
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe("POST /admin/customer-groups/:id/customers/batch/remove", () => {
describe("POST /admin/customer-groups/:id/customers/batch", () => {
let appContainer
let customerModuleService: ICustomerModuleService
@@ -55,9 +55,9 @@ medusaIntegrationTestRunner({
)
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
)

View File

@@ -13,7 +13,7 @@ const adminHeaders = {
medusaIntegrationTestRunner({
env,
testSuite: ({ dbConnection, getContainer, api }) => {
describe("GET /admin/customer-groups/:id/customers", () => {
describe("GET customer group customers", () => {
let appContainer
let customerModuleService: ICustomerModuleService
@@ -54,7 +54,7 @@ medusaIntegrationTestRunner({
)
const response = await api.get(
`/admin/customer-groups/${group.id}/customers`,
`/admin/customers?groups[]=${group.id}`,
adminHeaders
)

View File

@@ -35,7 +35,7 @@ medusaIntegrationTestRunner({
})
const response = await api.post(
`/admin/customers/${customer.id}/addresses`,
`/admin/customers/${customer.id}/addresses?fields=*addresses`,
{
first_name: "John",
last_name: "Doe",
@@ -45,13 +45,15 @@ medusaIntegrationTestRunner({
)
expect(response.status).toEqual(200)
expect(response.data.address).toEqual(
expect.objectContaining({
id: expect.any(String),
first_name: "John",
last_name: "Doe",
address_1: "Test street 1",
})
expect(response.data.customer.addresses).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
first_name: "John",
last_name: "Doe",
address_1: "Test street 1",
}),
])
)
const customerWithAddresses = await customerModuleService.retrieve(

View File

@@ -42,7 +42,7 @@ medusaIntegrationTestRunner({
})
const response = await api.post(
`/admin/customers/${customer.id}/addresses/${address.id}`,
`/admin/customers/${customer.id}/addresses/${address.id}?fields=*addresses`,
{
first_name: "Jane",
},
@@ -50,12 +50,14 @@ medusaIntegrationTestRunner({
)
expect(response.status).toEqual(200)
expect(response.data.address).toEqual(
expect.objectContaining({
id: expect.any(String),
first_name: "Jane",
last_name: "Doe",
})
expect(response.data.customer.addresses).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
first_name: "Jane",
last_name: "Doe",
}),
])
)
})

View File

@@ -1,11 +1,12 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import { Customer, CustomerGroup } from "@models"
import { Address, Customer, CustomerGroup } from "@models"
export const LinkableKeys = {
customer_id: Customer.name,
customer_group_id: CustomerGroup.name,
customer_address_id: Address.name,
}
const entityLinkableKeysMap: MapToConfig = {}
@@ -37,5 +38,12 @@ export const joinerConfig: ModuleJoinerConfig = {
methodSuffix: "CustomerGroups",
},
},
{
name: ["customer_address", "customer_addresses"],
args: {
entity: Address.name,
methodSuffix: "Addresses",
},
},
],
}

View File

@@ -12,10 +12,8 @@ import {
import {
InjectManager,
InjectTransactionManager,
isDuplicateError,
isString,
MedusaContext,
MedusaError,
ModulesSdkUtils,
} from "@medusajs/utils"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
@@ -26,10 +24,6 @@ import {
CustomerGroupCustomer,
} from "@models"
import { EntityManager } from "@mikro-orm/core"
import {
UNIQUE_CUSTOMER_BILLING_ADDRESS,
UNIQUE_CUSTOMER_SHIPPING_ADDRESS,
} from "../models/address"
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
@@ -39,7 +33,11 @@ type InjectedDependencies = {
customerGroupCustomerService: ModulesSdkTypes.InternalModuleService<any>
}
const generateMethodForModels = [Address, CustomerGroup, CustomerGroupCustomer]
const generateMethodForModels = [
{ model: Address, singular: "Address", plural: "Addresses" },
CustomerGroup,
CustomerGroupCustomer,
]
export default class CustomerModuleService<
TAddress extends Address = Address,
@@ -47,7 +45,6 @@ export default class CustomerModuleService<
TCustomerGroup extends CustomerGroup = CustomerGroup,
TCustomerGroupCustomer extends CustomerGroupCustomer = CustomerGroupCustomer
>
// TODO seb I let you manage that when you are moving forward
extends ModulesSdkUtils.abstractModuleServiceFactory<
InjectedDependencies,
CustomerDTO,

View File

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

View File

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

View File

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

View File

@@ -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,
})
}

View File

@@ -2,38 +2,29 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import {
CustomerGroupUpdatableFields,
ICustomerModuleService,
} from "@medusajs/types"
import {
deleteCustomerGroupsWorkflow,
updateCustomerGroupsWorkflow,
} from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { refetchCustomerGroup } from "../helpers"
import { AdminUpdateCustomerGroupType } from "../validators"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const customerModuleService = req.scope.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const group = await customerModuleService.retrieveCustomerGroup(
const customerGroup = await refetchCustomerGroup(
req.params.id,
{
select: req.retrieveConfig.select,
relations: req.retrieveConfig.relations,
}
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer_group: group })
res.status(200).json({ customer_group: customerGroup })
}
export const POST = async (
req: AuthenticatedMedusaRequest<CustomerGroupUpdatableFields>,
req: AuthenticatedMedusaRequest<AdminUpdateCustomerGroupType>,
res: MedusaResponse
) => {
const updateGroups = updateCustomerGroupsWorkflow(req.scope)
@@ -49,7 +40,12 @@ export const POST = async (
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 (

View File

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

View File

@@ -1,42 +1,39 @@
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 { 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[] = [
{
method: ["ALL"],
matcher: "/admin/customer-groups*",
middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
},
{
method: ["GET"],
matcher: "/admin/customer-groups",
middlewares: [
transformQuery(
validateAndTransformQuery(
AdminGetCustomerGroupsParams,
QueryConfig.listTransformQueryConfig
),
],
},
{
method: ["ALL"],
matcher: "/admin/customer-groups*",
middlewares: [authenticate("admin", ["bearer", "session"])],
},
{
method: ["GET"],
matcher: "/admin/customer-groups/:id",
middlewares: [
transformQuery(
AdminGetCustomerGroupsGroupParams,
validateAndTransformQuery(
AdminGetCustomerGroupParams,
QueryConfig.retrieveTransformQueryConfig
),
],
@@ -44,41 +41,37 @@ export const adminCustomerGroupRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
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: [
transformQuery(
AdminGetCustomerGroupsGroupCustomersParams,
customersListTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/customer-groups/:id/customers/batch/add",
middlewares: [
transformBody(AdminPostCustomerGroupsGroupCustomersBatchReq),
transformQuery(
AdminGetCustomerGroupsGroupParams,
validateAndTransformBody(AdminCreateCustomerGroup),
validateAndTransformQuery(
AdminGetCustomerGroupParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/customer-groups/:id/customers/batch/remove",
matcher: "/admin/customer-groups/:id",
middlewares: [
transformBody(AdminDeleteCustomerGroupsGroupCustomersBatchReq),
transformQuery(
AdminGetCustomerGroupsGroupParams,
validateAndTransformBody(AdminUpdateCustomerGroup),
validateAndTransformQuery(
AdminGetCustomerGroupParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/customer-groups/:id/customers/batch",
middlewares: [
validateAndTransformBody(
createBatchBody(
AdminSetCustomersCustomerGroup,
AdminSetCustomersCustomerGroup
)
),
validateAndTransformQuery(
AdminGetCustomerGroupParams,
QueryConfig.retrieveTransformQueryConfig
),
],

View File

@@ -1,17 +1,14 @@
export const defaultAdminCustomerGroupRelations = []
export const allowedAdminCustomerGroupRelations = ["customers"]
export const defaultAdminCustomerGroupFields = [
"id",
"name",
"created_by",
"created_at",
"updated_at",
"deleted_at",
]
export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminCustomerGroupFields,
defaultRelations: defaultAdminCustomerGroupRelations,
allowedRelations: allowedAdminCustomerGroupRelations,
defaults: defaultAdminCustomerGroupFields,
isList: false,
}

View File

@@ -1,39 +1,42 @@
import {
AuthenticatedMedusaRequest,
MedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { CreateCustomerGroupDTO, ICustomerModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { createCustomerGroupsWorkflow } from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { AdminCreateCustomerGroupType } from "./validators"
import { refetchCustomerGroup } from "./helpers"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const customerModuleService = req.scope.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const [groups, count] =
await customerModuleService.listAndCountCustomerGroups(
req.filterableFields,
req.listConfig
)
const query = remoteQueryObjectFromString({
entryPoint: "customer_group",
variables: {
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({
count,
customer_groups: groups,
offset,
limit,
customer_groups,
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
})
}
export const POST = async (
req: AuthenticatedMedusaRequest<CreateCustomerGroupDTO>,
req: AuthenticatedMedusaRequest<AdminCreateCustomerGroupType>,
res: MedusaResponse
) => {
const createGroups = createCustomerGroupsWorkflow(req.scope)
@@ -53,5 +56,11 @@ export const POST = async (
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 })
}

View File

@@ -1,183 +1,73 @@
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
import {
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
} from "class-validator"
import { Transform, Type } from "class-transformer"
createFindParams,
createOperatorMap,
createSelectParams,
} from "../../utils/validators"
import { z } from "zod"
import { IsType } from "../../../utils"
import { OperatorMap } from "@medusajs/types"
import { OperatorMapValidator } from "../../../types/validators/operator-map"
export type AdminGetCustomerGroupParamsType = z.infer<
typeof AdminGetCustomerGroupParams
>
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 {
@IsOptional()
@IsString({ each: true })
id?: string | string[]
@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,
export type AdminGetCustomerGroupsParamsType = z.infer<
typeof AdminGetCustomerGroupsParams
>
export const AdminGetCustomerGroupsParams = createFindParams({
limit: 50,
offset: 0,
}) {
@IsOptional()
@IsString()
q?: string
}).merge(
z.object({
q: z.string().optional(),
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()
@IsString({ each: true })
id?: string | string[]
export type AdminCreateCustomerGroupType = z.infer<
typeof AdminCreateCustomerGroup
>
export const AdminCreateCustomerGroup = z.object({
name: z.string(),
})
@IsOptional()
@ValidateNested({ each: true })
@Type(() => OperatorMapValidator)
name?: string | OperatorMap<string>
export type AdminUpdateCustomerGroupType = z.infer<
typeof AdminUpdateCustomerGroup
>
export const AdminUpdateCustomerGroup = z.object({
name: z.string(),
})
@IsOptional()
@ValidateNested()
@Type(() => FilterableCustomerPropsValidator)
customers?: FilterableCustomerPropsValidator | string | 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[]
}
export type AdminSetCustomersCustomerGroupType = z.infer<
typeof AdminSetCustomersCustomerGroup
>
export const AdminSetCustomersCustomerGroup = z.string()

View File

@@ -2,35 +2,38 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../types/routing"
import { CustomerAddressDTO, ICustomerModuleService } from "@medusajs/types"
import {
deleteCustomerAddressesWorkflow,
updateCustomerAddressesWorkflow,
} 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 (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const customerModuleService = req.scope.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
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(
{ id: req.params.address_id, customer_id: req.params.id },
{
select: req.retrieveConfig.select,
relations: req.retrieveConfig.relations,
}
)
const [address] = await remoteQuery(queryObject)
res.status(200).json({ address })
}
export const POST = async (
req: AuthenticatedMedusaRequest<Partial<CustomerAddressDTO>>,
req: AuthenticatedMedusaRequest<AdminCreateCustomerAddressType>,
res: MedusaResponse
) => {
const updateAddresses = updateCustomerAddressesWorkflow(req.scope)
@@ -46,7 +49,13 @@ export const POST = async (
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 (
@@ -65,9 +74,16 @@ export const DELETE = async (
throw errors[0].error
}
const customer = await refetchCustomer(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({
id,
object: "address",
object: "customer_address",
deleted: true,
parent: customer,
})
}

View File

@@ -1,41 +1,43 @@
import { createCustomerAddressesWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
CreateCustomerAddressDTO,
ICustomerModuleService,
} from "@medusajs/types"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { AdminCreateCustomerAddressType } from "../../validators"
import { refetchCustomer } from "../../helpers"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const customerId = req.params.id
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const customerModuleService = req.scope.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
const query = remoteQueryObjectFromString({
entryPoint: "customer_address",
variables: {
filters: { ...req.filterableFields, customer_id: customerId },
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
})
const [addresses, count] = await customerModuleService.listAndCountAddresses(
{ ...req.filterableFields, customer_id: customerId },
req.listConfig
)
const { offset, limit } = req.validatedQuery
const { rows: addresses, metadata } = await remoteQuery(query)
res.json({
count,
addresses,
offset,
limit,
count: metadata.count,
offset: metadata.skip,
limit: metadata.take,
})
}
export const POST = async (
req: AuthenticatedMedusaRequest<CreateCustomerAddressDTO>,
req: AuthenticatedMedusaRequest<AdminCreateCustomerAddressType>,
res: MedusaResponse
) => {
const customerId = req.params.id
@@ -56,5 +58,11 @@ export const POST = async (
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 })
}

View File

@@ -2,37 +2,37 @@ import {
deleteCustomersWorkflow,
updateCustomersWorkflow,
} from "@medusajs/core-flows"
import { AdminCustomerResponse, CustomerUpdatableFields } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { AdminCustomerResponse } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { refetchCustomer } from "../helpers"
import { AdminUpdateCustomerType } from "../validators"
export const GET = async (
req: AuthenticatedMedusaRequest,
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 }
const queryObject = remoteQueryObjectFromString({
entryPoint: "customer",
variables,
fields: req.remoteQueryConfig.fields,
})
const [customer] = await remoteQuery(queryObject)
if (!customer) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Customer with id: ${req.params.id} not found`
)
}
res.status(200).json({ customer })
}
export const POST = async (
req: AuthenticatedMedusaRequest<CustomerUpdatableFields>,
req: AuthenticatedMedusaRequest<AdminUpdateCustomerType>,
res: MedusaResponse<AdminCustomerResponse>
) => {
const { errors } = await updateCustomersWorkflow(req.scope).run({
@@ -47,18 +47,11 @@ export const POST = async (
throw errors[0].error
}
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "customer",
variables: {
filters: { id: req.params.id },
},
fields: req.remoteQueryConfig.fields,
})
const [customer] = await remoteQuery(queryObject)
const customer = await refetchCustomer(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer })
}

View File

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

View File

@@ -19,7 +19,7 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["ALL"],
matcher: "/admin/customers*",
middlewares: [authenticate("admin", ["bearer", "session"])],
middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
},
{
method: ["GET"],
@@ -38,7 +38,7 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [
validateAndTransformBody(AdminCreateCustomer),
validateAndTransformQuery(
AdminCustomerParams,
QueryConfig.listTransformQueryConfig
QueryConfig.retrieveTransformQueryConfig
),
],
},
@@ -48,7 +48,7 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [
middlewares: [
validateAndTransformQuery(
AdminCustomerParams,
QueryConfig.listTransformQueryConfig
QueryConfig.retrieveTransformQueryConfig
),
],
},
@@ -59,19 +59,41 @@ export const adminCustomerRoutesMiddlewares: MiddlewareRoute[] = [
validateAndTransformBody(AdminUpdateCustomer),
validateAndTransformQuery(
AdminCustomerParams,
QueryConfig.listTransformQueryConfig
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/customers/:id/addresses",
middlewares: [validateAndTransformBody(AdminCreateCustomerAddress)],
middlewares: [
validateAndTransformBody(AdminCreateCustomerAddress),
validateAndTransformQuery(
AdminCustomerParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
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"],

View File

@@ -1,5 +1,3 @@
export const defaultAdminCustomerRelations = []
export const allowedAdminCustomerRelations = ["groups", "addresses"]
export const defaultAdminCustomerFields = [
"id",
"company_name",
@@ -7,15 +5,14 @@ export const defaultAdminCustomerFields = [
"last_name",
"email",
"phone",
"created_by",
"created_at",
"updated_at",
"deleted_at",
]
export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminCustomerFields,
defaultRelations: defaultAdminCustomerRelations,
allowedRelations: allowedAdminCustomerRelations,
defaults: defaultAdminCustomerFields,
isList: false,
}
@@ -24,8 +21,6 @@ export const listTransformQueryConfig = {
isList: true,
}
export const defaultAdminCustomerAddressRelations = []
export const allowedAdminCustomerAddressRelations = ["customer"]
export const defaultAdminCustomerAddressFields = [
"id",
"company",
@@ -45,9 +40,7 @@ export const defaultAdminCustomerAddressFields = [
]
export const retrieveAddressTransformQueryConfig = {
defaultFields: defaultAdminCustomerAddressFields,
defaultRelations: defaultAdminCustomerAddressRelations,
allowedRelations: allowedAdminCustomerAddressRelations,
defaults: defaultAdminCustomerAddressFields,
isList: false,
}

View File

@@ -3,27 +3,29 @@ import {
AdminCustomerListResponse,
AdminCustomerResponse,
} from "@medusajs/types"
import { remoteQueryObjectFromString } from "@medusajs/utils"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { AdminCreateCustomerType } from "./validators"
import { refetchCustomer } from "./helpers"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse<AdminCustomerListResponse>
) => {
const remoteQuery = req.scope.resolve("remoteQuery")
const variables = {
filters: req.filterableFields,
...req.remoteQueryConfig.pagination,
}
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const query = remoteQueryObjectFromString({
entryPoint: "customers",
variables,
variables: {
filters: req.filterableFields,
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
})
@@ -59,5 +61,11 @@ export const POST = async (
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 })
}

View File

@@ -6,7 +6,6 @@ import {
} from "../../utils/validators"
export const AdminCustomerParams = createSelectParams()
export const AdminCustomerGroupParams = createSelectParams()
export const AdminCustomerGroupInCustomerParams = z.object({
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(),
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().optional(),
updated_at: createOperatorMap().optional().optional(),
deleted_at: createOperatorMap().optional().optional(),
created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(),
$and: 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 AdminCustomerGroupParamsType = z.infer<
typeof AdminCustomerGroupParams
>
export type AdminCustomerGroupInCustomerParamsType = z.infer<
typeof AdminCustomerGroupInCustomerParams
>
export type AdminCustomersParamsType = z.infer<typeof AdminCustomersParams>
export type AdminCreateCustomerType = z.infer<typeof AdminCreateCustomer>
export type AdminUpdateCustomerType = z.infer<typeof AdminUpdateCustomer>