From 32c2a9d76b12b78c1498f6a780cf923808e92f81 Mon Sep 17 00:00:00 2001 From: Stevche Radevski Date: Mon, 29 Apr 2024 16:15:05 +0200 Subject: [PATCH] feat: Add missing endpoints and normalize customer and currency endpoints for storefront (#7160) --- .../store/create-customer-addresses.ts | 2 +- .../customer/store/list-customer-addresses.ts | 2 +- .../store/update-customer-address.spec.ts | 2 +- .../maybe-unset-default-billing-addresses.ts | 4 +- .../maybe-unset-default-shipping-addresses.ts | 4 +- .../src/customer/steps/update-addresses.ts | 4 +- .../steps/utils/unset-address-for-update.ts | 4 +- .../customer/workflows/update-addresses.ts | 3 +- .../api-v2/admin/currencies/[code]/route.ts | 8 + .../src/api-v2/admin/customers/validators.ts | 9 - .../api-v2/store/currencies/[code]/route.ts | 18 +- .../api-v2/store/currencies/middlewares.ts | 14 +- .../api-v2/store/currencies/query-config.ts | 11 +- .../src/api-v2/store/currencies/route.ts | 14 +- .../src/api-v2/store/currencies/validators.ts | 45 ++- .../src/api-v2/store/customers/helpers.ts | 23 ++ .../me/addresses/[address_id]/route.ts | 91 +++--- .../store/customers/me/addresses/route.ts | 54 ++-- .../src/api-v2/store/customers/me/route.ts | 52 +++- .../src/api-v2/store/customers/middlewares.ts | 104 +++++-- .../api-v2/store/customers/query-config.ts | 17 +- .../src/api-v2/store/customers/route.ts | 16 +- .../src/api-v2/store/customers/validators.ts | 288 ++++-------------- packages/types/src/customer/mutations.ts | 2 +- 24 files changed, 368 insertions(+), 423 deletions(-) create mode 100644 packages/medusa/src/api-v2/store/customers/helpers.ts diff --git a/integration-tests/modules/__tests__/customer/store/create-customer-addresses.ts b/integration-tests/modules/__tests__/customer/store/create-customer-addresses.ts index 5cb99635ec..6b467be733 100644 --- a/integration-tests/modules/__tests__/customer/store/create-customer-addresses.ts +++ b/integration-tests/modules/__tests__/customer/store/create-customer-addresses.ts @@ -37,7 +37,7 @@ medusaIntegrationTestRunner({ ) expect(response.status).toEqual(200) - expect(response.data.address).toEqual( + expect(response.data.customer.addresses[0]).toEqual( expect.objectContaining({ id: expect.any(String), first_name: "John", diff --git a/integration-tests/modules/__tests__/customer/store/list-customer-addresses.ts b/integration-tests/modules/__tests__/customer/store/list-customer-addresses.ts index 685cf19cb8..0848416083 100644 --- a/integration-tests/modules/__tests__/customer/store/list-customer-addresses.ts +++ b/integration-tests/modules/__tests__/customer/store/list-customer-addresses.ts @@ -1,7 +1,7 @@ import { ICustomerModuleService } from "@medusajs/types" import { ModuleRegistrationName } from "@medusajs/modules-sdk" 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 } diff --git a/integration-tests/modules/__tests__/customer/store/update-customer-address.spec.ts b/integration-tests/modules/__tests__/customer/store/update-customer-address.spec.ts index a7e0654495..91a70f9b65 100644 --- a/integration-tests/modules/__tests__/customer/store/update-customer-address.spec.ts +++ b/integration-tests/modules/__tests__/customer/store/update-customer-address.spec.ts @@ -42,7 +42,7 @@ medusaIntegrationTestRunner({ ) expect(response.status).toEqual(200) - expect(response.data.address).toEqual( + expect(response.data.customer.addresses[0]).toEqual( expect.objectContaining({ id: address.id, first_name: "Jane", diff --git a/packages/core-flows/src/customer/steps/maybe-unset-default-billing-addresses.ts b/packages/core-flows/src/customer/steps/maybe-unset-default-billing-addresses.ts index 70212f2c51..e036bd9b73 100644 --- a/packages/core-flows/src/customer/steps/maybe-unset-default-billing-addresses.ts +++ b/packages/core-flows/src/customer/steps/maybe-unset-default-billing-addresses.ts @@ -1,6 +1,6 @@ import { CreateCustomerAddressDTO, - CustomerAddressDTO, + UpdateCustomerAddressDTO, FilterableCustomerAddressProps, ICustomerModuleService, } from "@medusajs/types" @@ -13,7 +13,7 @@ type StepInput = { create?: CreateCustomerAddressDTO[] update?: { selector: FilterableCustomerAddressProps - update: Partial + update: UpdateCustomerAddressDTO } } diff --git a/packages/core-flows/src/customer/steps/maybe-unset-default-shipping-addresses.ts b/packages/core-flows/src/customer/steps/maybe-unset-default-shipping-addresses.ts index b484c31a5f..37dd60fe92 100644 --- a/packages/core-flows/src/customer/steps/maybe-unset-default-shipping-addresses.ts +++ b/packages/core-flows/src/customer/steps/maybe-unset-default-shipping-addresses.ts @@ -1,6 +1,6 @@ import { CreateCustomerAddressDTO, - CustomerAddressDTO, + UpdateCustomerAddressDTO, FilterableCustomerAddressProps, ICustomerModuleService, } from "@medusajs/types" @@ -13,7 +13,7 @@ type StepInput = { create?: CreateCustomerAddressDTO[] update?: { selector: FilterableCustomerAddressProps - update: Partial + update: UpdateCustomerAddressDTO } } diff --git a/packages/core-flows/src/customer/steps/update-addresses.ts b/packages/core-flows/src/customer/steps/update-addresses.ts index 49b793e36b..80c27f581a 100644 --- a/packages/core-flows/src/customer/steps/update-addresses.ts +++ b/packages/core-flows/src/customer/steps/update-addresses.ts @@ -1,6 +1,6 @@ import { ModuleRegistrationName } from "@medusajs/modules-sdk" import { - CustomerAddressDTO, + UpdateCustomerAddressDTO, FilterableCustomerAddressProps, ICustomerModuleService, } from "@medusajs/types" @@ -12,7 +12,7 @@ import { createStep, StepResponse } from "@medusajs/workflows-sdk" type UpdateCustomerAddresseStepInput = { selector: FilterableCustomerAddressProps - update: Partial + update: UpdateCustomerAddressDTO } export const updateCustomerAddresseStepId = "update-customer-addresses" diff --git a/packages/core-flows/src/customer/steps/utils/unset-address-for-update.ts b/packages/core-flows/src/customer/steps/utils/unset-address-for-update.ts index 404b550a9c..3fd072fb98 100644 --- a/packages/core-flows/src/customer/steps/utils/unset-address-for-update.ts +++ b/packages/core-flows/src/customer/steps/utils/unset-address-for-update.ts @@ -1,5 +1,5 @@ import { - CustomerAddressDTO, + UpdateCustomerAddressDTO, FilterableCustomerAddressProps, ICustomerModuleService, } from "@medusajs/types" @@ -8,7 +8,7 @@ import { StepResponse } from "@medusajs/workflows-sdk" export const unsetForUpdate = async ( data: { selector: FilterableCustomerAddressProps - update: Partial + update: UpdateCustomerAddressDTO }, customerService: ICustomerModuleService, field: "is_default_billing" | "is_default_shipping" diff --git a/packages/core-flows/src/customer/workflows/update-addresses.ts b/packages/core-flows/src/customer/workflows/update-addresses.ts index 3d9015cb01..ad2524d398 100644 --- a/packages/core-flows/src/customer/workflows/update-addresses.ts +++ b/packages/core-flows/src/customer/workflows/update-addresses.ts @@ -1,6 +1,7 @@ import { FilterableCustomerAddressProps, CustomerAddressDTO, + UpdateCustomerAddressDTO, } from "@medusajs/types" import { WorkflowData, @@ -16,7 +17,7 @@ import { type WorkflowInput = { selector: FilterableCustomerAddressProps - update: Partial + update: UpdateCustomerAddressDTO } export const updateCustomerAddressesWorkflowId = "update-customer-addresses" diff --git a/packages/medusa/src/api-v2/admin/currencies/[code]/route.ts b/packages/medusa/src/api-v2/admin/currencies/[code]/route.ts index b9fb4cccf6..f800e80dc2 100644 --- a/packages/medusa/src/api-v2/admin/currencies/[code]/route.ts +++ b/packages/medusa/src/api-v2/admin/currencies/[code]/route.ts @@ -1,5 +1,6 @@ import { ContainerRegistrationKeys, + MedusaError, remoteQueryObjectFromString, } from "@medusajs/utils" import { MedusaRequest, MedusaResponse } from "../../../../types/routing" @@ -16,5 +17,12 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { }) 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 }) } diff --git a/packages/medusa/src/api-v2/admin/customers/validators.ts b/packages/medusa/src/api-v2/admin/customers/validators.ts index 3d679a04df..84172a9335 100644 --- a/packages/medusa/src/api-v2/admin/customers/validators.ts +++ b/packages/medusa/src/api-v2/admin/customers/validators.ts @@ -83,20 +83,11 @@ export const AdminCustomerAdressesParams = createFindParams({ }).merge( z.object({ 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(), - 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(), country_code: 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(), - phone: z.union([z.string(), z.array(z.string())]).optional(), - metadata: z.record(z.unknown()).optional(), }) ) diff --git a/packages/medusa/src/api-v2/store/currencies/[code]/route.ts b/packages/medusa/src/api-v2/store/currencies/[code]/route.ts index d22ae866e2..f800e80dc2 100644 --- a/packages/medusa/src/api-v2/store/currencies/[code]/route.ts +++ b/packages/medusa/src/api-v2/store/currencies/[code]/route.ts @@ -1,18 +1,28 @@ -import { remoteQueryObjectFromString } from "@medusajs/utils" +import { + ContainerRegistrationKeys, + MedusaError, + remoteQueryObjectFromString, +} from "@medusajs/utils" import { MedusaRequest, MedusaResponse } from "../../../../types/routing" -import { defaultStoreCurrencyFields } from "../query-config" 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 queryObject = remoteQueryObjectFromString({ entryPoint: "currency", variables, - fields: defaultStoreCurrencyFields, + fields: req.remoteQueryConfig.fields, }) 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 }) } diff --git a/packages/medusa/src/api-v2/store/currencies/middlewares.ts b/packages/medusa/src/api-v2/store/currencies/middlewares.ts index 05e24a2f5f..9899e82dbf 100644 --- a/packages/medusa/src/api-v2/store/currencies/middlewares.ts +++ b/packages/medusa/src/api-v2/store/currencies/middlewares.ts @@ -1,18 +1,14 @@ -import { transformQuery } from "../../../api/middlewares" 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 { - StoreGetCurrenciesCurrencyParams, - StoreGetCurrenciesParams, -} from "./validators" +import { StoreGetCurrenciesParams, StoreGetCurrencyParams } from "./validators" export const storeCurrencyRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["GET"], matcher: "/store/currencies", middlewares: [ - transformQuery( + validateAndTransformQuery( StoreGetCurrenciesParams, QueryConfig.listTransformQueryConfig ), @@ -22,8 +18,8 @@ export const storeCurrencyRoutesMiddlewares: MiddlewareRoute[] = [ method: ["GET"], matcher: "/store/currencies/:code", middlewares: [ - transformQuery( - StoreGetCurrenciesCurrencyParams, + validateAndTransformQuery( + StoreGetCurrencyParams, QueryConfig.retrieveTransformQueryConfig ), ], diff --git a/packages/medusa/src/api-v2/store/currencies/query-config.ts b/packages/medusa/src/api-v2/store/currencies/query-config.ts index 27bb8a939e..2d7b959421 100644 --- a/packages/medusa/src/api-v2/store/currencies/query-config.ts +++ b/packages/medusa/src/api-v2/store/currencies/query-config.ts @@ -1,20 +1,19 @@ -export const defaultStoreCurrencyRelations = [] -export const allowedStoreCurrencyRelations = [] export const defaultStoreCurrencyFields = [ "code", "name", "symbol", "symbol_native", + "decimal_digits", + "rounding", ] export const retrieveTransformQueryConfig = { - defaultFields: defaultStoreCurrencyFields, - defaultRelations: defaultStoreCurrencyRelations, - allowedRelations: allowedStoreCurrencyRelations, + defaults: defaultStoreCurrencyFields, isList: false, } export const listTransformQueryConfig = { - defaultLimit: 20, + ...retrieveTransformQueryConfig, + defaultLimit: 50, isList: true, } diff --git a/packages/medusa/src/api-v2/store/currencies/route.ts b/packages/medusa/src/api-v2/store/currencies/route.ts index efb304da17..51947a58da 100644 --- a/packages/medusa/src/api-v2/store/currencies/route.ts +++ b/packages/medusa/src/api-v2/store/currencies/route.ts @@ -1,19 +1,19 @@ -import { remoteQueryObjectFromString } from "@medusajs/utils" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" import { MedusaRequest, MedusaResponse } from "../../../types/routing" -import { defaultStoreCurrencyFields } from "./query-config" 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({ entryPoint: "currency", variables: { filters: req.filterableFields, - order: req.listConfig.order, - skip: req.listConfig.skip, - take: req.listConfig.take, + ...req.remoteQueryConfig.pagination, }, - fields: defaultStoreCurrencyFields, + fields: req.remoteQueryConfig.fields, }) const { rows: currencies, metadata } = await remoteQuery(queryObject) diff --git a/packages/medusa/src/api-v2/store/currencies/validators.ts b/packages/medusa/src/api-v2/store/currencies/validators.ts index 17bb9df4a8..6bd2c1914b 100644 --- a/packages/medusa/src/api-v2/store/currencies/validators.ts +++ b/packages/medusa/src/api-v2/store/currencies/validators.ts @@ -1,30 +1,19 @@ -import { Type } from "class-transformer" -import { IsOptional, IsString, ValidateNested } from "class-validator" -import { FindParams, extendedFindParamsMixin } from "../../../types/common" +import { createFindParams, createSelectParams } from "../../utils/validators" +import { z } from "zod" -export class StoreGetCurrenciesCurrencyParams extends FindParams {} -/** - * Parameters used to filter and configure the pagination of the retrieved currencies. - */ -export class StoreGetCurrenciesParams extends extendedFindParamsMixin({ - limit: 50, +export const StoreGetCurrencyParams = createSelectParams() + +export type StoreGetCurrenciesParamsType = z.infer< + typeof StoreGetCurrenciesParams +> +export const StoreGetCurrenciesParams = createFindParams({ offset: 0, -}) { - /** - * Search parameter for currencies. - */ - @IsString({ each: true }) - @IsOptional() - code?: string | string[] - - // Additional filters from BaseFilterable - @IsOptional() - @ValidateNested({ each: true }) - @Type(() => StoreGetCurrenciesParams) - $and?: StoreGetCurrenciesParams[] - - @IsOptional() - @ValidateNested({ each: true }) - @Type(() => StoreGetCurrenciesParams) - $or?: StoreGetCurrenciesParams[] -} + limit: 50, +}).merge( + z.object({ + q: z.string().optional(), + code: z.union([z.string(), z.array(z.string())]).optional(), + $and: z.lazy(() => StoreGetCurrenciesParams.array()).optional(), + $or: z.lazy(() => StoreGetCurrenciesParams.array()).optional(), + }) +) diff --git a/packages/medusa/src/api-v2/store/customers/helpers.ts b/packages/medusa/src/api-v2/store/customers/helpers.ts new file mode 100644 index 0000000000..5aee77c888 --- /dev/null +++ b/packages/medusa/src/api-v2/store/customers/helpers.ts @@ -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] +} diff --git a/packages/medusa/src/api-v2/store/customers/me/addresses/[address_id]/route.ts b/packages/medusa/src/api-v2/store/customers/me/addresses/[address_id]/route.ts index f4fbced6d8..af0ea061c4 100644 --- a/packages/medusa/src/api-v2/store/customers/me/addresses/[address_id]/route.ts +++ b/packages/medusa/src/api-v2/store/customers/me/addresses/[address_id]/route.ts @@ -1,51 +1,59 @@ import { AuthenticatedMedusaRequest, - MedusaRequest, MedusaResponse, } from "../../../../../../types/routing" -import { CustomerAddressDTO, ICustomerModuleService } from "@medusajs/types" import { deleteCustomerAddressesWorkflow, updateCustomerAddressesWorkflow, } from "@medusajs/core-flows" -import { MedusaError } from "@medusajs/utils" -import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + 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 ( - req: AuthenticatedMedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { - const id = req.auth.actor_id + const customerId = req.auth.actor_id - const customerModuleService = req.scope.resolve( - 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: customerId }, + }, + fields: req.remoteQueryConfig.fields, + }) - const [address] = await customerModuleService.listAddresses( - { id: req.params.address_id, customer_id: id }, - { - select: req.retrieveConfig.select, - relations: req.retrieveConfig.relations, - } - ) + const [address] = await remoteQuery(queryObject) + if (!address) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Address with id: ${req.params.address_id} was not found` + ) + } res.status(200).json({ address }) } export const POST = async ( - req: AuthenticatedMedusaRequest>, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const id = req.auth.actor_id! - const service = req.scope.resolve( - ModuleRegistrationName.CUSTOMER - ) - - await validateCustomerAddress(service, id, req.params.address_id) + await validateCustomerAddress(req.scope, id, req.params.address_id) const updateAddresses = updateCustomerAddressesWorkflow(req.scope) - const { result, errors } = await updateAddresses.run({ + const { errors } = await updateAddresses.run({ input: { selector: { id: req.params.address_id, customer_id: req.params.id }, update: req.validatedBody, @@ -57,7 +65,13 @@ export const POST = async ( 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 ( @@ -65,14 +79,9 @@ export const DELETE = async ( res: MedusaResponse ) => { const id = req.auth.actor_id + await validateCustomerAddress(req.scope, id, req.params.address_id) - const service = req.scope.resolve( - ModuleRegistrationName.CUSTOMER - ) - - await validateCustomerAddress(service, id, req.params.address_id) const deleteAddress = deleteCustomerAddressesWorkflow(req.scope) - const { errors } = await deleteAddress.run({ input: { ids: [req.params.address_id] }, throwOnError: false, @@ -82,23 +91,35 @@ export const DELETE = async ( throw errors[0].error } + const customer = await refetchCustomer( + id, + req.scope, + req.remoteQueryConfig.fields + ) + res.status(200).json({ id, object: "address", deleted: true, + parent: customer, }) } const validateCustomerAddress = async ( - customerModuleService: ICustomerModuleService, + scope: MedusaContainer, customerId: string, addressId: string ) => { - const [address] = await customerModuleService.listAddresses( - { id: addressId, customer_id: customerId }, - { select: ["id"] } - ) + const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + const queryObject = remoteQueryObjectFromString({ + entryPoint: "customer_address", + variables: { + filters: { id: addressId, customer_id: customerId }, + }, + fields: ["id"], + }) + const [address] = await remoteQuery(queryObject) if (!address) { throw new MedusaError( MedusaError.Types.NOT_FOUND, diff --git a/packages/medusa/src/api-v2/store/customers/me/addresses/route.ts b/packages/medusa/src/api-v2/store/customers/me/addresses/route.ts index ef1eee18d4..89ee37caec 100644 --- a/packages/medusa/src/api-v2/store/customers/me/addresses/route.ts +++ b/packages/medusa/src/api-v2/store/customers/me/addresses/route.ts @@ -1,43 +1,47 @@ import { AuthenticatedMedusaRequest, - MedusaRequest, MedusaResponse, } from "../../../../../types/routing" -import { - CreateCustomerAddressDTO, - ICustomerModuleService, -} from "@medusajs/types" -import { ModuleRegistrationName } from "@medusajs/modules-sdk" 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 ( - req: AuthenticatedMedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const customerId = req.auth.actor_id - const customerModuleService = req.scope.resolve( - ModuleRegistrationName.CUSTOMER - ) + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + 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( - { ...req.filterableFields, customer_id: customerId }, - req.listConfig - ) - - const { offset, limit } = req.validatedQuery + const { rows: addresses, metadata } = await remoteQuery(queryObject) res.json({ - count, addresses, - offset, - limit, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, }) } export const POST = async ( - req: AuthenticatedMedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { 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 }, throwOnError: false, }) @@ -59,5 +63,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 }) } diff --git a/packages/medusa/src/api-v2/store/customers/me/route.ts b/packages/medusa/src/api-v2/store/customers/me/route.ts index 657979f3dc..5dee113e41 100644 --- a/packages/medusa/src/api-v2/store/customers/me/route.ts +++ b/packages/medusa/src/api-v2/store/customers/me/route.ts @@ -3,20 +3,56 @@ import { MedusaResponse, } 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 ( - req: AuthenticatedMedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { const id = req.auth.actor_id + const customer = await refetchCustomer( + id, + req.scope, + req.remoteQueryConfig.fields + ) - const customerModule = req.scope.resolve(ModuleRegistrationName.CUSTOMER) - - const customer = await customerModule.retrieve(id, { - select: req.retrieveConfig.select, - relations: req.retrieveConfig.relations, - }) + if (!customer) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Customer with id: ${id} was not found` + ) + } res.json({ customer }) } + +export const POST = async ( + req: AuthenticatedMedusaRequest, + 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 }) +} diff --git a/packages/medusa/src/api-v2/store/customers/middlewares.ts b/packages/medusa/src/api-v2/store/customers/middlewares.ts index 890dd43044..6910189c89 100644 --- a/packages/medusa/src/api-v2/store/customers/middlewares.ts +++ b/packages/medusa/src/api-v2/store/customers/middlewares.ts @@ -1,63 +1,109 @@ import * as QueryConfig from "./query-config" import { - StoreGetCustomersMeAddressesParams, - StoreGetCustomersMeParams, - StorePostCustomersMeAddressesAddressReq, - StorePostCustomersMeAddressesReq, - StorePostCustomersReq, + StoreCreateCustomer, + StoreCreateCustomerAddress, + StoreGetCustomerParams, + StoreGetCustomerAddressesParams, + StoreUpdateCustomer, + StoreUpdateCustomerAddress, + StoreGetCustomerAddressParams, } from "./validators" -import { transformBody, transformQuery } from "../../../api/middlewares" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { authenticate } from "../../../utils/authenticate-middleware" +import { validateAndTransformBody } from "../../utils/validate-body" +import { validateAndTransformQuery } from "../../utils/validate-query" export const storeCustomerRoutesMiddlewares: MiddlewareRoute[] = [ + { + method: ["POST"], + matcher: "/store/customers", + middlewares: [ + authenticate("store", ["session", "bearer"], { allowUnregistered: true }), + validateAndTransformBody(StoreCreateCustomer), + validateAndTransformQuery( + StoreGetCustomerParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], + }, { method: "ALL", matcher: "/store/customers/me*", 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"], matcher: "/store/customers/me", middlewares: [ - transformQuery( - StoreGetCustomersMeParams, + validateAndTransformQuery( + StoreGetCustomerParams, QueryConfig.retrieveTransformQueryConfig ), ], }, { method: ["POST"], - matcher: "/store/customers/me/addresses", - middlewares: [transformBody(StorePostCustomersMeAddressesReq)], - }, - { - method: ["POST"], - matcher: "/store/customers/me/addresses/:address_id", - middlewares: [transformBody(StorePostCustomersMeAddressesAddressReq)], + matcher: "/store/customers/me", + middlewares: [ + validateAndTransformBody(StoreUpdateCustomer), + validateAndTransformQuery( + StoreGetCustomerParams, + QueryConfig.retrieveTransformQueryConfig + ), + ], }, { method: ["GET"], matcher: "/store/customers/me/addresses", middlewares: [ - transformQuery( - StoreGetCustomersMeAddressesParams, + validateAndTransformQuery( + StoreGetCustomerAddressesParams, 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 + ), + ], + }, ] diff --git a/packages/medusa/src/api-v2/store/customers/query-config.ts b/packages/medusa/src/api-v2/store/customers/query-config.ts index 7a503843c6..783963ce79 100644 --- a/packages/medusa/src/api-v2/store/customers/query-config.ts +++ b/packages/medusa/src/api-v2/store/customers/query-config.ts @@ -1,8 +1,4 @@ -import { CustomerDTO } from "@medusajs/types" - -export const defaultStoreCustomersRelations = [] -export const allowedStoreCustomersRelations = ["addresses", "groups"] -export const defaultStoreCustomersFields: (keyof CustomerDTO)[] = [ +const defaultStoreCustomersFields = [ "id", "email", "company_name", @@ -14,17 +10,14 @@ export const defaultStoreCustomersFields: (keyof CustomerDTO)[] = [ "deleted_at", "created_at", "updated_at", + "*addresses", ] export const retrieveTransformQueryConfig = { - defaultFields: defaultStoreCustomersFields as string[], - defaultRelations: defaultStoreCustomersRelations, - allowedRelations: allowedStoreCustomersRelations, + defaults: defaultStoreCustomersFields, isList: false, } -export const defaultStoreCustomerAddressRelations = [] -export const allowedStoreCustomerAddressRelations = ["customer"] export const defaultStoreCustomerAddressFields = [ "id", "company", @@ -44,9 +37,7 @@ export const defaultStoreCustomerAddressFields = [ ] export const retrieveAddressTransformQueryConfig = { - defaultFields: defaultStoreCustomerAddressFields, - defaultRelations: defaultStoreCustomerAddressRelations, - allowedRelations: allowedStoreCustomerAddressRelations, + defaults: defaultStoreCustomerAddressFields, isList: false, } diff --git a/packages/medusa/src/api-v2/store/customers/route.ts b/packages/medusa/src/api-v2/store/customers/route.ts index a6250f4628..23ce4941be 100644 --- a/packages/medusa/src/api-v2/store/customers/route.ts +++ b/packages/medusa/src/api-v2/store/customers/route.ts @@ -7,11 +7,12 @@ import { remoteQueryObjectFromString, } from "@medusajs/utils" -import { CreateCustomerDTO } from "@medusajs/types" import { createCustomerAccountWorkflow } from "@medusajs/core-flows" +import { refetchCustomer } from "./helpers" +import { StoreCreateCustomerType } from "./validators" export const POST = async ( - req: AuthenticatedMedusaRequest, + req: AuthenticatedMedusaRequest, res: MedusaResponse ) => { if (req.auth.actor_id) { @@ -32,7 +33,7 @@ export const POST = async ( } const createCustomers = createCustomerAccountWorkflow(req.scope) - const customersData = req.validatedBody as CreateCustomerDTO + const customersData = req.validatedBody const { result } = await createCustomers.run({ input: { customersData, authUserId: req.auth.auth_user_id }, @@ -42,5 +43,12 @@ export const POST = async ( if (req.session.auth_user) { 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 }) } diff --git a/packages/medusa/src/api-v2/store/customers/validators.ts b/packages/medusa/src/api-v2/store/customers/validators.ts index 8bb0fc48d4..5486b02a70 100644 --- a/packages/medusa/src/api-v2/store/customers/validators.ts +++ b/packages/medusa/src/api-v2/store/customers/validators.ts @@ -1,244 +1,60 @@ -import { OperatorMap } from "@medusajs/types" -import { Type } from "class-transformer" -import { - IsBoolean, - IsEmail, - IsNotEmpty, - IsObject, - IsOptional, - IsString, - ValidateNested, -} from "class-validator" -import { extendedFindParamsMixin, FindParams } from "../../../types/common" -import { OperatorMapValidator } from "../../../types/validators/operator-map" +import { createFindParams, createSelectParams } from "../../utils/validators" +import { z } from "zod" +import { AddressPayload } from "../../utils/common-validators" -export class StoreGetCustomersMeParams extends FindParams {} +export const StoreGetCustomerParams = createSelectParams() -export class StorePostCustomersReq { - @IsString() - @IsOptional() - first_name: string +export const StoreCreateCustomer = z.object({ + email: z.string().email().optional(), + company_name: z.string().optional(), + first_name: z.string().optional(), + last_name: z.string().optional(), + phone: z.string().optional(), +}) - @IsString() - @IsOptional() - last_name: string +export const StoreUpdateCustomer = z.object({ + company_name: z.string().nullable().optional(), + first_name: z.string().nullable().optional(), + last_name: z.string().nullable().optional(), + phone: z.string().nullable().optional(), +}) - @IsEmail() - email: string +export const StoreGetCustomerAddressParams = createSelectParams() - @IsString() - @IsOptional() - phone?: string +export const StoreCreateCustomerAddress = AddressPayload.merge( + z.object({ + address_name: z.string().optional(), + is_default_shipping: z.boolean().optional(), + is_default_billing: z.boolean().optional(), + }) +) - @IsString() - @IsOptional() - company_name?: string +export const StoreUpdateCustomerAddress = StoreCreateCustomerAddress - @IsObject() - @IsOptional() - metadata?: Record -} +export const StoreGetCustomerAddressesParams = createFindParams({ + offset: 0, + limit: 50, +}).merge( + z.object({ + q: z.string().optional(), + 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(), + }) +) -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 -} - -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 -} - -export class StoreGetCustomersMeAddressesParams extends extendedFindParamsMixin( - { - limit: 100, - offset: 0, - } -) { - @IsOptional() - @IsString({ each: true }) - address_name?: string | string[] | OperatorMap - - @IsOptional() - @IsBoolean() - is_default_shipping?: boolean - - @IsOptional() - @IsBoolean() - is_default_billing?: boolean - - @IsOptional() - @IsString({ each: true }) - company?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - first_name?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - last_name?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - address_1?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - address_2?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - city?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - country_code?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - province?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - postal_code?: string | string[] | OperatorMap | null - - @IsOptional() - @IsString({ each: true }) - phone?: string | string[] | OperatorMap | null - - @IsOptional() - @ValidateNested() - @Type(() => OperatorMapValidator) - metadata?: OperatorMap> -} +export type StoreGetCustomerParamsType = z.infer +export type StoreCreateCustomerType = z.infer +export type StoreUpdateCustomerType = z.infer +export type StoreGetCustomerAddressParamsType = z.infer< + typeof StoreGetCustomerAddressParams +> +export type StoreGetCustomerAddressesParamsType = z.infer< + typeof StoreCreateCustomerAddress +> +export type StoreCreateCustomerAddressType = z.infer< + typeof StoreCreateCustomerAddress +> +export type StoreUpdateCustomerAddressType = z.infer< + typeof StoreUpdateCustomerAddress +> diff --git a/packages/types/src/customer/mutations.ts b/packages/types/src/customer/mutations.ts index 5da9937a6c..dab6de12df 100644 --- a/packages/types/src/customer/mutations.ts +++ b/packages/types/src/customer/mutations.ts @@ -75,7 +75,7 @@ export interface CreateCustomerAddressDTO { /** * Holds custom data in key-value pairs. */ - metadata?: Record + metadata?: Record | null } /**