feat: Add missing endpoints and normalize customer and currency endpoints for storefront (#7160)

This commit is contained in:
Stevche Radevski authored and GitHub committed 2024-04-29 16:15:05 +02:00
1 parent 08a9297f76
commit 32c2a9d76b
24 files changed
+366 -421

No files matched your search

@@ -37,7 +37,7 @@ medusaIntegrationTestRunner({
) )
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.address).toEqual( expect(response.data.customer.addresses[0]).toEqual(
expect.objectContaining({ expect.objectContaining({
id: expect.any(String), id: expect.any(String),
first_name: "John", first_name: "John",
@@ -1,7 +1,7 @@
import { ICustomerModuleService } from "@medusajs/types" import { ICustomerModuleService } from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { createAuthenticatedCustomer } from "../../../helpers/create-authenticated-customer" import { createAuthenticatedCustomer } from "../../../helpers/create-authenticated-customer"
import {medusaIntegrationTestRunner} from "medusa-test-utils"; import { medusaIntegrationTestRunner } from "medusa-test-utils"
const env = { MEDUSA_FF_MEDUSA_V2: true } const env = { MEDUSA_FF_MEDUSA_V2: true }
@@ -42,7 +42,7 @@ medusaIntegrationTestRunner({
) )
expect(response.status).toEqual(200) expect(response.status).toEqual(200)
expect(response.data.address).toEqual( expect(response.data.customer.addresses[0]).toEqual(
expect.objectContaining({ expect.objectContaining({
id: address.id, id: address.id,
first_name: "Jane", first_name: "Jane",
@@ -1,6 +1,6 @@
import { import {
CreateCustomerAddressDTO, CreateCustomerAddressDTO,
CustomerAddressDTO, UpdateCustomerAddressDTO,
FilterableCustomerAddressProps, FilterableCustomerAddressProps,
ICustomerModuleService, ICustomerModuleService,
} from "@medusajs/types" } from "@medusajs/types"
@@ -13,7 +13,7 @@ type StepInput = {
create?: CreateCustomerAddressDTO[] create?: CreateCustomerAddressDTO[]
update?: { update?: {
selector: FilterableCustomerAddressProps selector: FilterableCustomerAddressProps
update: Partial<CustomerAddressDTO> update: UpdateCustomerAddressDTO
} }
} }
@@ -1,6 +1,6 @@
import { import {
CreateCustomerAddressDTO, CreateCustomerAddressDTO,
CustomerAddressDTO, UpdateCustomerAddressDTO,
FilterableCustomerAddressProps, FilterableCustomerAddressProps,
ICustomerModuleService, ICustomerModuleService,
} from "@medusajs/types" } from "@medusajs/types"
@@ -13,7 +13,7 @@ type StepInput = {
create?: CreateCustomerAddressDTO[] create?: CreateCustomerAddressDTO[]
update?: { update?: {
selector: FilterableCustomerAddressProps selector: FilterableCustomerAddressProps
update: Partial<CustomerAddressDTO> update: UpdateCustomerAddressDTO
} }
} }
@@ -1,6 +1,6 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { import {
CustomerAddressDTO, UpdateCustomerAddressDTO,
FilterableCustomerAddressProps, FilterableCustomerAddressProps,
ICustomerModuleService, ICustomerModuleService,
} from "@medusajs/types" } from "@medusajs/types"
@@ -12,7 +12,7 @@ import { createStep, StepResponse } from "@medusajs/workflows-sdk"
type UpdateCustomerAddresseStepInput = { type UpdateCustomerAddresseStepInput = {
selector: FilterableCustomerAddressProps selector: FilterableCustomerAddressProps
update: Partial<CustomerAddressDTO> update: UpdateCustomerAddressDTO
} }
export const updateCustomerAddresseStepId = "update-customer-addresses" export const updateCustomerAddresseStepId = "update-customer-addresses"
@@ -1,5 +1,5 @@
import { import {
CustomerAddressDTO, UpdateCustomerAddressDTO,
FilterableCustomerAddressProps, FilterableCustomerAddressProps,
ICustomerModuleService, ICustomerModuleService,
} from "@medusajs/types" } from "@medusajs/types"
@@ -8,7 +8,7 @@ import { StepResponse } from "@medusajs/workflows-sdk"
export const unsetForUpdate = async ( export const unsetForUpdate = async (
data: { data: {
selector: FilterableCustomerAddressProps selector: FilterableCustomerAddressProps
update: Partial<CustomerAddressDTO> update: UpdateCustomerAddressDTO
}, },
customerService: ICustomerModuleService, customerService: ICustomerModuleService,
field: "is_default_billing" | "is_default_shipping" field: "is_default_billing" | "is_default_shipping"
@@ -1,6 +1,7 @@
import { import {
FilterableCustomerAddressProps, FilterableCustomerAddressProps,
CustomerAddressDTO, CustomerAddressDTO,
UpdateCustomerAddressDTO,
} from "@medusajs/types" } from "@medusajs/types"
import { import {
WorkflowData, WorkflowData,
@@ -16,7 +17,7 @@ import {
type WorkflowInput = { type WorkflowInput = {
selector: FilterableCustomerAddressProps selector: FilterableCustomerAddressProps
update: Partial<CustomerAddressDTO> update: UpdateCustomerAddressDTO
} }
export const updateCustomerAddressesWorkflowId = "update-customer-addresses" export const updateCustomerAddressesWorkflowId = "update-customer-addresses"
@@ -1,5 +1,6 @@
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString, remoteQueryObjectFromString,
} from "@medusajs/utils" } from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
@@ -16,5 +17,12 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
}) })
const [currency] = await remoteQuery(queryObject) const [currency] = await remoteQuery(queryObject)
if (!currency) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Currency with code: ${req.params.code} was not found`
)
}
res.status(200).json({ currency }) res.status(200).json({ currency })
} }
@@ -83,20 +83,11 @@ export const AdminCustomerAdressesParams = createFindParams({
}).merge( }).merge(
z.object({ z.object({
q: z.string().optional(), q: z.string().optional(),
address_name: z.union([z.string(), z.array(z.string())]).optional(),
is_default_shipping: z.boolean().optional(),
is_default_billing: z.boolean().optional(),
company: z.union([z.string(), z.array(z.string())]).optional(), company: 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(),
address_1: z.union([z.string(), z.array(z.string())]).optional(),
address_2: z.union([z.string(), z.array(z.string())]).optional(),
city: z.union([z.string(), z.array(z.string())]).optional(), city: z.union([z.string(), z.array(z.string())]).optional(),
country_code: z.union([z.string(), z.array(z.string())]).optional(), country_code: z.union([z.string(), z.array(z.string())]).optional(),
province: z.union([z.string(), z.array(z.string())]).optional(), province: z.union([z.string(), z.array(z.string())]).optional(),
postal_code: z.union([z.string(), z.array(z.string())]).optional(), postal_code: z.union([z.string(), z.array(z.string())]).optional(),
phone: z.union([z.string(), z.array(z.string())]).optional(),
metadata: z.record(z.unknown()).optional(),
}) })
) )
@@ -1,18 +1,28 @@
import { remoteQueryObjectFromString } from "@medusajs/utils" import {
ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
import { defaultStoreCurrencyFields } from "../query-config"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => { export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const remoteQuery = req.scope.resolve("remoteQuery") const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const variables = { code: req.params.code } const variables = { code: req.params.code }
const queryObject = remoteQueryObjectFromString({ const queryObject = remoteQueryObjectFromString({
entryPoint: "currency", entryPoint: "currency",
variables, variables,
fields: defaultStoreCurrencyFields, fields: req.remoteQueryConfig.fields,
}) })
const [currency] = await remoteQuery(queryObject) const [currency] = await remoteQuery(queryObject)
if (!currency) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Currency with code: ${req.params.code} was not found`
)
}
res.status(200).json({ currency }) res.status(200).json({ currency })
} }
@@ -1,18 +1,14 @@
import { transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { authenticate } from "../../../utils/authenticate-middleware" import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { import { StoreGetCurrenciesParams, StoreGetCurrencyParams } from "./validators"
StoreGetCurrenciesCurrencyParams,
StoreGetCurrenciesParams,
} from "./validators"
export const storeCurrencyRoutesMiddlewares: MiddlewareRoute[] = [ export const storeCurrencyRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: ["GET"], method: ["GET"],
matcher: "/store/currencies", matcher: "/store/currencies",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
StoreGetCurrenciesParams, StoreGetCurrenciesParams,
QueryConfig.listTransformQueryConfig QueryConfig.listTransformQueryConfig
), ),
@@ -22,8 +18,8 @@ export const storeCurrencyRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/store/currencies/:code", matcher: "/store/currencies/:code",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
StoreGetCurrenciesCurrencyParams, StoreGetCurrencyParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -1,20 +1,19 @@
export const defaultStoreCurrencyRelations = []
export const allowedStoreCurrencyRelations = []
export const defaultStoreCurrencyFields = [ export const defaultStoreCurrencyFields = [
"code", "code",
"name", "name",
"symbol", "symbol",
"symbol_native", "symbol_native",
"decimal_digits",
"rounding",
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultStoreCurrencyFields, defaults: defaultStoreCurrencyFields,
defaultRelations: defaultStoreCurrencyRelations,
allowedRelations: allowedStoreCurrencyRelations,
isList: false, isList: false,
} }
export const listTransformQueryConfig = { export const listTransformQueryConfig = {
defaultLimit: 20, ...retrieveTransformQueryConfig,
defaultLimit: 50,
isList: true, isList: true,
} }
@@ -1,19 +1,19 @@
import { remoteQueryObjectFromString } from "@medusajs/utils" import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../types/routing"
import { defaultStoreCurrencyFields } from "./query-config"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => { export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const remoteQuery = req.scope.resolve("remoteQuery") const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({ const queryObject = remoteQueryObjectFromString({
entryPoint: "currency", entryPoint: "currency",
variables: { variables: {
filters: req.filterableFields, filters: req.filterableFields,
order: req.listConfig.order, ...req.remoteQueryConfig.pagination,
skip: req.listConfig.skip,
take: req.listConfig.take,
}, },
fields: defaultStoreCurrencyFields, fields: req.remoteQueryConfig.fields,
}) })
const { rows: currencies, metadata } = await remoteQuery(queryObject) const { rows: currencies, metadata } = await remoteQuery(queryObject)
@@ -1,30 +1,19 @@
import { Type } from "class-transformer" import { createFindParams, createSelectParams } from "../../utils/validators"
import { IsOptional, IsString, ValidateNested } from "class-validator" import { z } from "zod"
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
export class StoreGetCurrenciesCurrencyParams extends FindParams {} export const StoreGetCurrencyParams = createSelectParams()
/**
* Parameters used to filter and configure the pagination of the retrieved currencies. export type StoreGetCurrenciesParamsType = z.infer<
*/ typeof StoreGetCurrenciesParams
export class StoreGetCurrenciesParams extends extendedFindParamsMixin({ >
limit: 50, export const StoreGetCurrenciesParams = createFindParams({
offset: 0, offset: 0,
}) { limit: 50,
/** }).merge(
* Search parameter for currencies. z.object({
*/ q: z.string().optional(),
@IsString({ each: true }) code: z.union([z.string(), z.array(z.string())]).optional(),
@IsOptional() $and: z.lazy(() => StoreGetCurrenciesParams.array()).optional(),
code?: string | string[] $or: z.lazy(() => StoreGetCurrenciesParams.array()).optional(),
})
// Additional filters from BaseFilterable )
@IsOptional()
@ValidateNested({ each: true })
@Type(() => StoreGetCurrenciesParams)
$and?: StoreGetCurrenciesParams[]
@IsOptional()
@ValidateNested({ each: true })
@Type(() => StoreGetCurrenciesParams)
$or?: StoreGetCurrenciesParams[]
}
@@ -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]
}
@@ -1,51 +1,59 @@
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaRequest,
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 { MedusaError } from "@medusajs/utils" import {
import { ModuleRegistrationName } from "@medusajs/modules-sdk" ContainerRegistrationKeys,
MedusaError,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaContainer } from "@medusajs/modules-sdk"
import {
StoreGetCustomerAddressParamsType,
StoreUpdateCustomerAddressType,
} from "../../../validators"
import { refetchCustomer } from "../../../helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<StoreGetCustomerAddressParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.auth.actor_id const customerId = req.auth.actor_id
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: customerId },
},
fields: req.remoteQueryConfig.fields,
})
const [address] = await remoteQuery(queryObject)
if (!address) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Address with id: ${req.params.address_id} was not found`
) )
const [address] = await customerModuleService.listAddresses(
{ id: req.params.address_id, customer_id: 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<StoreUpdateCustomerAddressType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.auth.actor_id! const id = req.auth.actor_id!
const service = req.scope.resolve<ICustomerModuleService>( await validateCustomerAddress(req.scope, id, req.params.address_id)
ModuleRegistrationName.CUSTOMER
)
await validateCustomerAddress(service, id, req.params.address_id)
const updateAddresses = updateCustomerAddressesWorkflow(req.scope) const updateAddresses = updateCustomerAddressesWorkflow(req.scope)
const { result, errors } = await updateAddresses.run({ const { errors } = await updateAddresses.run({
input: { input: {
selector: { id: req.params.address_id, customer_id: req.params.id }, selector: { id: req.params.address_id, customer_id: req.params.id },
update: req.validatedBody, update: req.validatedBody,
@@ -57,7 +65,13 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
res.status(200).json({ address: result[0] }) const customer = await refetchCustomer(
id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer })
} }
export const DELETE = async ( export const DELETE = async (
@@ -65,14 +79,9 @@ export const DELETE = async (
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.auth.actor_id const id = req.auth.actor_id
await validateCustomerAddress(req.scope, id, req.params.address_id)
const service = req.scope.resolve<ICustomerModuleService>(
ModuleRegistrationName.CUSTOMER
)
await validateCustomerAddress(service, id, req.params.address_id)
const deleteAddress = deleteCustomerAddressesWorkflow(req.scope) const deleteAddress = deleteCustomerAddressesWorkflow(req.scope)
const { errors } = await deleteAddress.run({ const { errors } = await deleteAddress.run({
input: { ids: [req.params.address_id] }, input: { ids: [req.params.address_id] },
throwOnError: false, throwOnError: false,
@@ -82,23 +91,35 @@ export const DELETE = async (
throw errors[0].error throw errors[0].error
} }
const customer = await refetchCustomer(
id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ res.status(200).json({
id, id,
object: "address", object: "address",
deleted: true, deleted: true,
parent: customer,
}) })
} }
const validateCustomerAddress = async ( const validateCustomerAddress = async (
customerModuleService: ICustomerModuleService, scope: MedusaContainer,
customerId: string, customerId: string,
addressId: string addressId: string
) => { ) => {
const [address] = await customerModuleService.listAddresses( const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
{ id: addressId, customer_id: customerId }, const queryObject = remoteQueryObjectFromString({
{ select: ["id"] } entryPoint: "customer_address",
) variables: {
filters: { id: addressId, customer_id: customerId },
},
fields: ["id"],
})
const [address] = await remoteQuery(queryObject)
if (!address) { if (!address) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
@@ -1,43 +1,47 @@
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../types/routing" } from "../../../../../types/routing"
import {
CreateCustomerAddressDTO,
ICustomerModuleService,
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { createCustomerAddressesWorkflow } from "@medusajs/core-flows" import { createCustomerAddressesWorkflow } from "@medusajs/core-flows"
import {
StoreCreateCustomerAddressType,
StoreGetCustomerAddressesParamsType,
} from "../../validators"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { refetchCustomer } from "../../helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<StoreGetCustomerAddressesParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const customerId = req.auth.actor_id const customerId = req.auth.actor_id
const customerModuleService = req.scope.resolve<ICustomerModuleService>( const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
ModuleRegistrationName.CUSTOMER const queryObject = remoteQueryObjectFromString({
) 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(queryObject)
{ ...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<StoreCreateCustomerAddressType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const customerId = req.auth.actor_id const customerId = req.auth.actor_id
@@ -50,7 +54,7 @@ export const POST = async (
}, },
] ]
const { result, errors } = await createAddresses.run({ const { errors } = await createAddresses.run({
input: { addresses }, input: { addresses },
throwOnError: false, throwOnError: false,
}) })
@@ -59,5 +63,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 })
} }
@@ -3,20 +3,56 @@ import {
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { ModuleRegistrationName } from "@medusajs/modules-sdk" import {
StoreGetCustomerParamsType,
StoreUpdateCustomerType,
} from "../validators"
import { refetchCustomer } from "../helpers"
import { MedusaError } from "@medusajs/utils"
import { updateCustomersWorkflow } from "@medusajs/core-flows"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<StoreGetCustomerParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.auth.actor_id const id = req.auth.actor_id
const customer = await refetchCustomer(
id,
req.scope,
req.remoteQueryConfig.fields
)
const customerModule = req.scope.resolve(ModuleRegistrationName.CUSTOMER) if (!customer) {
throw new MedusaError(
const customer = await customerModule.retrieve(id, { MedusaError.Types.NOT_FOUND,
select: req.retrieveConfig.select, `Customer with id: ${id} was not found`
relations: req.retrieveConfig.relations, )
}) }
res.json({ customer }) res.json({ customer })
} }
export const POST = async (
req: AuthenticatedMedusaRequest<StoreUpdateCustomerType>,
res: MedusaResponse
) => {
const customerId = req.auth.actor_id
const { errors } = await updateCustomersWorkflow(req.scope).run({
input: {
selector: { id: customerId },
update: req.validatedBody,
},
throwOnError: false,
})
if (Array.isArray(errors) && errors[0]) {
throw errors[0].error
}
const customer = await refetchCustomer(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer })
}
@@ -1,63 +1,109 @@
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { import {
StoreGetCustomersMeAddressesParams, StoreCreateCustomer,
StoreGetCustomersMeParams, StoreCreateCustomerAddress,
StorePostCustomersMeAddressesAddressReq, StoreGetCustomerParams,
StorePostCustomersMeAddressesReq, StoreGetCustomerAddressesParams,
StorePostCustomersReq, StoreUpdateCustomer,
StoreUpdateCustomerAddress,
StoreGetCustomerAddressParams,
} from "./validators" } from "./validators"
import { transformBody, transformQuery } from "../../../api/middlewares"
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 { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
export const storeCustomerRoutesMiddlewares: MiddlewareRoute[] = [ export const storeCustomerRoutesMiddlewares: MiddlewareRoute[] = [
{
method: ["POST"],
matcher: "/store/customers",
middlewares: [
authenticate("store", ["session", "bearer"], { allowUnregistered: true }),
validateAndTransformBody(StoreCreateCustomer),
validateAndTransformQuery(
StoreGetCustomerParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{ {
method: "ALL", method: "ALL",
matcher: "/store/customers/me*", matcher: "/store/customers/me*",
middlewares: [authenticate("store", ["session", "bearer"])], middlewares: [authenticate("store", ["session", "bearer"])],
}, },
{
method: "POST",
matcher: "/store/customers",
middlewares: [
authenticate("store", ["session", "bearer"], { allowUnregistered: true }),
],
},
{
method: ["POST"],
matcher: "/store/customers",
middlewares: [transformBody(StorePostCustomersReq)],
},
{ {
method: ["GET"], method: ["GET"],
matcher: "/store/customers/me", matcher: "/store/customers/me",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
StoreGetCustomersMeParams, StoreGetCustomerParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/store/customers/me/addresses", matcher: "/store/customers/me",
middlewares: [transformBody(StorePostCustomersMeAddressesReq)], middlewares: [
}, validateAndTransformBody(StoreUpdateCustomer),
{ validateAndTransformQuery(
method: ["POST"], StoreGetCustomerParams,
matcher: "/store/customers/me/addresses/:address_id", QueryConfig.retrieveTransformQueryConfig
middlewares: [transformBody(StorePostCustomersMeAddressesAddressReq)], ),
],
}, },
{ {
method: ["GET"], method: ["GET"],
matcher: "/store/customers/me/addresses", matcher: "/store/customers/me/addresses",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
StoreGetCustomersMeAddressesParams, StoreGetCustomerAddressesParams,
QueryConfig.listAddressesTransformQueryConfig QueryConfig.listAddressesTransformQueryConfig
), ),
], ],
}, },
{
method: ["POST"],
matcher: "/store/customers/me/addresses",
middlewares: [
validateAndTransformBody(StoreCreateCustomerAddress),
validateAndTransformQuery(
StoreGetCustomerParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["GET"],
matcher: "/store/customers/me/addresses/:address_id",
middlewares: [
validateAndTransformQuery(
StoreGetCustomerAddressParams,
QueryConfig.retrieveAddressTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/store/customers/me/addresses/:address_id",
middlewares: [
validateAndTransformBody(StoreUpdateCustomerAddress),
validateAndTransformQuery(
StoreGetCustomerParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["DELETE"],
matcher: "/store/customers/me/addresses/:address_id",
middlewares: [
validateAndTransformQuery(
StoreGetCustomerAddressParams,
QueryConfig.retrieveAddressTransformQueryConfig
),
],
},
] ]
@@ -1,8 +1,4 @@
import { CustomerDTO } from "@medusajs/types" const defaultStoreCustomersFields = [
export const defaultStoreCustomersRelations = []
export const allowedStoreCustomersRelations = ["addresses", "groups"]
export const defaultStoreCustomersFields: (keyof CustomerDTO)[] = [
"id", "id",
"email", "email",
"company_name", "company_name",
@@ -14,17 +10,14 @@ export const defaultStoreCustomersFields: (keyof CustomerDTO)[] = [
"deleted_at", "deleted_at",
"created_at", "created_at",
"updated_at", "updated_at",
"*addresses",
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultStoreCustomersFields as string[], defaults: defaultStoreCustomersFields,
defaultRelations: defaultStoreCustomersRelations,
allowedRelations: allowedStoreCustomersRelations,
isList: false, isList: false,
} }
export const defaultStoreCustomerAddressRelations = []
export const allowedStoreCustomerAddressRelations = ["customer"]
export const defaultStoreCustomerAddressFields = [ export const defaultStoreCustomerAddressFields = [
"id", "id",
"company", "company",
@@ -44,9 +37,7 @@ export const defaultStoreCustomerAddressFields = [
] ]
export const retrieveAddressTransformQueryConfig = { export const retrieveAddressTransformQueryConfig = {
defaultFields: defaultStoreCustomerAddressFields, defaults: defaultStoreCustomerAddressFields,
defaultRelations: defaultStoreCustomerAddressRelations,
allowedRelations: allowedStoreCustomerAddressRelations,
isList: false, isList: false,
} }
@@ -7,11 +7,12 @@ import {
remoteQueryObjectFromString, remoteQueryObjectFromString,
} from "@medusajs/utils" } from "@medusajs/utils"
import { CreateCustomerDTO } from "@medusajs/types"
import { createCustomerAccountWorkflow } from "@medusajs/core-flows" import { createCustomerAccountWorkflow } from "@medusajs/core-flows"
import { refetchCustomer } from "./helpers"
import { StoreCreateCustomerType } from "./validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<StoreCreateCustomerType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
if (req.auth.actor_id) { if (req.auth.actor_id) {
@@ -32,7 +33,7 @@ export const POST = async (
} }
const createCustomers = createCustomerAccountWorkflow(req.scope) const createCustomers = createCustomerAccountWorkflow(req.scope)
const customersData = req.validatedBody as CreateCustomerDTO const customersData = req.validatedBody
const { result } = await createCustomers.run({ const { result } = await createCustomers.run({
input: { customersData, authUserId: req.auth.auth_user_id }, input: { customersData, authUserId: req.auth.auth_user_id },
@@ -42,5 +43,12 @@ export const POST = async (
if (req.session.auth_user) { if (req.session.auth_user) {
req.session.auth_user.app_metadata.customer_id = result.id req.session.auth_user.app_metadata.customer_id = result.id
} }
res.status(200).json({ customer: result })
const customer = await refetchCustomer(
result.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ customer })
} }
@@ -1,244 +1,60 @@
import { OperatorMap } from "@medusajs/types" import { createFindParams, createSelectParams } from "../../utils/validators"
import { Type } from "class-transformer" import { z } from "zod"
import { import { AddressPayload } from "../../utils/common-validators"
IsBoolean,
IsEmail,
IsNotEmpty,
IsObject,
IsOptional,
IsString,
ValidateNested,
} from "class-validator"
import { extendedFindParamsMixin, FindParams } from "../../../types/common"
import { OperatorMapValidator } from "../../../types/validators/operator-map"
export class StoreGetCustomersMeParams extends FindParams {} export const StoreGetCustomerParams = createSelectParams()
export class StorePostCustomersReq { export const StoreCreateCustomer = z.object({
@IsString() email: z.string().email().optional(),
@IsOptional() company_name: z.string().optional(),
first_name: string first_name: z.string().optional(),
last_name: z.string().optional(),
phone: z.string().optional(),
})
@IsString() export const StoreUpdateCustomer = z.object({
@IsOptional() company_name: z.string().nullable().optional(),
last_name: string first_name: z.string().nullable().optional(),
last_name: z.string().nullable().optional(),
phone: z.string().nullable().optional(),
})
@IsEmail() export const StoreGetCustomerAddressParams = createSelectParams()
email: string
@IsString() export const StoreCreateCustomerAddress = AddressPayload.merge(
@IsOptional() z.object({
phone?: string address_name: z.string().optional(),
is_default_shipping: z.boolean().optional(),
is_default_billing: z.boolean().optional(),
})
)
@IsString() export const StoreUpdateCustomerAddress = StoreCreateCustomerAddress
@IsOptional()
company_name?: string
@IsObject() export const StoreGetCustomerAddressesParams = createFindParams({
@IsOptional()
metadata?: Record<string, unknown>
}
export class StorePostCustomersMeAddressesReq {
@IsNotEmpty()
@IsString()
@IsOptional()
address_name?: string
@IsBoolean()
@IsOptional()
is_default_shipping?: boolean
@IsBoolean()
@IsOptional()
is_default_billing?: boolean
@IsNotEmpty()
@IsString()
@IsOptional()
company?: string
@IsNotEmpty()
@IsString()
@IsOptional()
first_name?: string
@IsNotEmpty()
@IsString()
@IsOptional()
last_name?: string
@IsNotEmpty()
@IsString()
@IsOptional()
address_1?: string
@IsNotEmpty()
@IsString()
@IsOptional()
address_2?: string
@IsNotEmpty()
@IsString()
@IsOptional()
city?: string
@IsNotEmpty()
@IsString()
@IsOptional()
country_code?: string
@IsNotEmpty()
@IsString()
@IsOptional()
province?: string
@IsNotEmpty()
@IsString()
@IsOptional()
postal_code?: string
@IsNotEmpty()
@IsString()
@IsOptional()
phone?: string
@IsNotEmpty()
@IsString()
@IsOptional()
metadata?: Record<string, unknown>
}
export class StorePostCustomersMeAddressesAddressReq {
@IsNotEmpty()
@IsString()
@IsOptional()
address_name?: string
@IsBoolean()
@IsOptional()
is_default_shipping?: boolean
@IsBoolean()
@IsOptional()
is_default_billing?: boolean
@IsNotEmpty()
@IsString()
@IsOptional()
company?: string
@IsNotEmpty()
@IsString()
@IsOptional()
first_name?: string
@IsNotEmpty()
@IsString()
@IsOptional()
last_name?: string
@IsNotEmpty()
@IsString()
@IsOptional()
address_1?: string
@IsNotEmpty()
@IsString()
@IsOptional()
address_2?: string
@IsNotEmpty()
@IsString()
@IsOptional()
city?: string
@IsNotEmpty()
@IsString()
@IsOptional()
country_code?: string
@IsNotEmpty()
@IsString()
@IsOptional()
province?: string
@IsNotEmpty()
@IsString()
@IsOptional()
postal_code?: string
@IsNotEmpty()
@IsString()
@IsOptional()
phone?: string
@IsNotEmpty()
@IsString()
@IsOptional()
metadata?: Record<string, unknown>
}
export class StoreGetCustomersMeAddressesParams extends extendedFindParamsMixin(
{
limit: 100,
offset: 0, offset: 0,
} limit: 50,
) { }).merge(
@IsOptional() z.object({
@IsString({ each: true }) q: z.string().optional(),
address_name?: string | string[] | OperatorMap<string> city: z.union([z.string(), z.array(z.string())]).optional(),
country_code: z.union([z.string(), z.array(z.string())]).optional(),
postal_code: z.union([z.string(), z.array(z.string())]).optional(),
})
)
@IsOptional() export type StoreGetCustomerParamsType = z.infer<typeof StoreGetCustomerParams>
@IsBoolean() export type StoreCreateCustomerType = z.infer<typeof StoreCreateCustomer>
is_default_shipping?: boolean export type StoreUpdateCustomerType = z.infer<typeof StoreUpdateCustomer>
export type StoreGetCustomerAddressParamsType = z.infer<
@IsOptional() typeof StoreGetCustomerAddressParams
@IsBoolean() >
is_default_billing?: boolean export type StoreGetCustomerAddressesParamsType = z.infer<
typeof StoreCreateCustomerAddress
@IsOptional() >
@IsString({ each: true }) export type StoreCreateCustomerAddressType = z.infer<
company?: string | string[] | OperatorMap<string> | null typeof StoreCreateCustomerAddress
>
@IsOptional() export type StoreUpdateCustomerAddressType = z.infer<
@IsString({ each: true }) typeof StoreUpdateCustomerAddress
first_name?: string | string[] | OperatorMap<string> | null >
@IsOptional()
@IsString({ each: true })
last_name?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
address_1?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
address_2?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
city?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
country_code?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
province?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
postal_code?: string | string[] | OperatorMap<string> | null
@IsOptional()
@IsString({ each: true })
phone?: string | string[] | OperatorMap<string> | null
@IsOptional()
@ValidateNested()
@Type(() => OperatorMapValidator)
metadata?: OperatorMap<Record<string, unknown>>
}
+1 -1
View File
@@ -75,7 +75,7 @@ export interface CreateCustomerAddressDTO {
/** /**
* Holds custom data in key-value pairs. * Holds custom data in key-value pairs.
*/ */
metadata?: Record<string, unknown> metadata?: Record<string, unknown> | null
} }
/** /**