From 691f68c3b8d910c9a8ee53f8c567e45680023219 Mon Sep 17 00:00:00 2001 From: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:19:07 +0100 Subject: [PATCH] feat(region): Region create, delete, update admin endpoints (#6332) **What** Add `POST /admin/regions` Add `POST /admin/regions/:id` Add `DELETE /admin/regions/:id` All are added for v2 using API Routes and workflows. In follow-up PRs, I will add support for passing countries in update and create. Update: First follow-up PR is #6372 --- .../regions/admin/get-region.spec.ts | 66 ------ .../regions/admin/list-regions.spec.ts | 66 ------ .../__tests__/regions/admin/regions.spec.ts | 219 ++++++++++++++++++ packages/core-flows/src/index.ts | 10 +- packages/core-flows/src/region/index.ts | 2 + .../src/region/steps/create-regions.ts | 31 +++ .../src/region/steps/delete-regions.ts | 28 +++ packages/core-flows/src/region/steps/index.ts | 3 + .../src/region/steps/update-regions.ts | 54 +++++ .../src/region/workflows/create-regions.ts | 13 ++ .../src/region/workflows/delete-regions.ts | 12 + .../core-flows/src/region/workflows/index.ts | 3 + .../src/region/workflows/update-regions.ts | 22 ++ .../src/api-v2/admin/regions/[id]/route.ts | 40 ++++ .../src/api-v2/admin/regions/middlewares.ts | 14 +- .../src/api-v2/admin/regions/query-config.ts | 5 + .../medusa/src/api-v2/admin/regions/route.ts | 40 +++- .../src/api-v2/admin/regions/validators.ts | 18 ++ packages/medusa/src/models/payment.ts | 2 +- packages/region/src/services/region-module.ts | 68 +++++- packages/types/src/region/mutations.ts | 20 +- packages/types/src/region/service.ts | 19 +- 22 files changed, 586 insertions(+), 169 deletions(-) delete mode 100644 integration-tests/plugins/__tests__/regions/admin/get-region.spec.ts delete mode 100644 integration-tests/plugins/__tests__/regions/admin/list-regions.spec.ts create mode 100644 integration-tests/plugins/__tests__/regions/admin/regions.spec.ts create mode 100644 packages/core-flows/src/region/index.ts create mode 100644 packages/core-flows/src/region/steps/create-regions.ts create mode 100644 packages/core-flows/src/region/steps/delete-regions.ts create mode 100644 packages/core-flows/src/region/steps/index.ts create mode 100644 packages/core-flows/src/region/steps/update-regions.ts create mode 100644 packages/core-flows/src/region/workflows/create-regions.ts create mode 100644 packages/core-flows/src/region/workflows/delete-regions.ts create mode 100644 packages/core-flows/src/region/workflows/index.ts create mode 100644 packages/core-flows/src/region/workflows/update-regions.ts diff --git a/integration-tests/plugins/__tests__/regions/admin/get-region.spec.ts b/integration-tests/plugins/__tests__/regions/admin/get-region.spec.ts deleted file mode 100644 index 5269654761..0000000000 --- a/integration-tests/plugins/__tests__/regions/admin/get-region.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { IRegionModuleService } from "@medusajs/types" -import path from "path" -import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app" -import { useApi } from "../../../../environment-helpers/use-api" -import { getContainer } from "../../../../environment-helpers/use-container" -import { initDb, useDb } from "../../../../environment-helpers/use-db" -import adminSeeder from "../../../../helpers/admin-seeder" - -jest.setTimeout(50000) - -const env = { MEDUSA_FF_MEDUSA_V2: true } -const adminHeaders = { - headers: { "x-medusa-access-token": "test_token" }, -} - -describe("GET /admin/regions/:id", () => { - let dbConnection - let appContainer - let shutdownServer - let regionModuleService: IRegionModuleService - - beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..", "..")) - dbConnection = await initDb({ cwd, env } as any) - shutdownServer = await startBootstrapApp({ cwd, env }) - appContainer = getContainer() - regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION) - }) - - afterAll(async () => { - const db = useDb() - await db.shutdown() - await shutdownServer() - }) - - beforeEach(async () => { - await adminSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("should get a region", async () => { - const [region] = await regionModuleService.create([ - { - name: "Test", - currency_code: "usd", - }, - ]) - - const api = useApi() as any - const response = await api.get(`/admin/regions/${region.id}`, adminHeaders) - - expect(response.status).toEqual(200) - expect(response.data.region).toEqual( - expect.objectContaining({ - id: region.id, - name: "Test", - currency_code: "usd", - }) - ) - }) -}) diff --git a/integration-tests/plugins/__tests__/regions/admin/list-regions.spec.ts b/integration-tests/plugins/__tests__/regions/admin/list-regions.spec.ts deleted file mode 100644 index 14683ce621..0000000000 --- a/integration-tests/plugins/__tests__/regions/admin/list-regions.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { IRegionModuleService } from "@medusajs/types" -import path from "path" -import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app" -import { useApi } from "../../../../environment-helpers/use-api" -import { getContainer } from "../../../../environment-helpers/use-container" -import { initDb, useDb } from "../../../../environment-helpers/use-db" -import adminSeeder from "../../../../helpers/admin-seeder" - -jest.setTimeout(50000) - -const env = { MEDUSA_FF_MEDUSA_V2: true } -const adminHeaders = { - headers: { "x-medusa-access-token": "test_token" }, -} - -describe("GET /admin/regions", () => { - let dbConnection - let appContainer - let shutdownServer - let regionModuleService: IRegionModuleService - - beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..", "..")) - dbConnection = await initDb({ cwd, env } as any) - shutdownServer = await startBootstrapApp({ cwd, env }) - appContainer = getContainer() - regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION) - }) - - afterAll(async () => { - const db = useDb() - await db.shutdown() - await shutdownServer() - }) - - beforeEach(async () => { - await adminSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("should get all regions and count", async () => { - await regionModuleService.create([ - { - name: "Test", - currency_code: "usd", - }, - ]) - - const api = useApi() as any - const response = await api.get(`/admin/regions`, adminHeaders) - - expect(response.status).toEqual(200) - expect(response.data.regions).toEqual([ - expect.objectContaining({ - id: expect.any(String), - name: "Test", - currency_code: "usd", - }), - ]) - }) -}) diff --git a/integration-tests/plugins/__tests__/regions/admin/regions.spec.ts b/integration-tests/plugins/__tests__/regions/admin/regions.spec.ts new file mode 100644 index 0000000000..f55d6f17ec --- /dev/null +++ b/integration-tests/plugins/__tests__/regions/admin/regions.spec.ts @@ -0,0 +1,219 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IRegionModuleService } from "@medusajs/types" +import path from "path" +import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app" +import { useApi } from "../../../../environment-helpers/use-api" +import { getContainer } from "../../../../environment-helpers/use-container" +import { initDb, useDb } from "../../../../environment-helpers/use-db" +import adminSeeder from "../../../../helpers/admin-seeder" + +jest.setTimeout(50000) + +const env = { MEDUSA_FF_MEDUSA_V2: true } +const adminHeaders = { + headers: { "x-medusa-access-token": "test_token" }, +} + +describe("Regions - Admin", () => { + let dbConnection + let appContainer + let shutdownServer + let service: IRegionModuleService + + beforeAll(async () => { + const cwd = path.resolve(path.join(__dirname, "..", "..", "..")) + dbConnection = await initDb({ cwd, env } as any) + shutdownServer = await startBootstrapApp({ cwd, env }) + appContainer = getContainer() + service = appContainer.resolve(ModuleRegistrationName.REGION) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + await shutdownServer() + }) + + beforeEach(async () => { + await adminSeeder(dbConnection) + + await service.createDefaultCountriesAndCurrencies() + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("should create, update, and delete a region", async () => { + const api = useApi() as any + const created = await api.post( + `/admin/regions`, + { + name: "Test Region", + currency_code: "usd", + }, + adminHeaders + ) + + expect(created.status).toEqual(200) + expect(created.data.region).toEqual( + expect.objectContaining({ + id: created.data.region.id, + name: "Test Region", + currency_code: "usd", + }) + ) + + const updated = await api.post( + `/admin/regions`, + { + name: "United States", + currency_code: "usd", + }, + adminHeaders + ) + + expect(updated.status).toEqual(200) + expect(updated.data.region).toEqual( + expect.objectContaining({ + id: updated.data.region.id, + currency_code: "usd", + }) + ) + + const deleted = await api.delete( + `/admin/regions/${updated.data.region.id}`, + adminHeaders + ) + + expect(deleted.status).toEqual(200) + expect(deleted.data).toEqual({ + id: updated.data.region.id, + object: "region", + deleted: true, + }) + + const deletedRegion = await service.retrieve(updated.data.region.id, { + withDeleted: true, + }) + + // @ts-ignore + expect(deletedRegion.deleted_at).toBeTruthy() + }) + + it("should throw on missing required properties in create", async () => { + const api = useApi() as any + const err = await api + .post(`/admin/regions`, {}, adminHeaders) + .catch((e) => e) + + expect(err.response.status).toEqual(400) + expect(err.response.data.message).toEqual( + "name must be a string, currency_code must be a string" + ) + }) + + it("should throw on unknown currency in create", async () => { + const api = useApi() as any + const error = await api + .post( + `/admin/regions`, + { + currency_code: "foo", + name: "Test Region", + }, + adminHeaders + ) + .catch((e) => e) + + expect(error.response.status).toEqual(400) + expect(error.response.data.message).toEqual( + "Currency with code: foo was not found" + ) + }) + + it("should throw on unknown properties in create", async () => { + const api = useApi() as any + const error = await api + .post( + `/admin/regions`, + { + foo: "bar", + currency_code: "usd", + name: "Test Region", + }, + adminHeaders + ) + .catch((e) => e) + + expect(error.response.status).toEqual(400) + expect(error.response.data.message).toEqual("property foo should not exist") + }) + + it("should throw on unknown properties in update", async () => { + const api = useApi() as any + + const created = await service.create({ + name: "Test Region", + currency_code: "usd", + }) + + const error = await api + .post( + `/admin/regions/${created.id}`, + { + foo: "bar", + currency_code: "usd", + name: "Test Region", + }, + adminHeaders + ) + .catch((e) => e) + + expect(error.response.status).toEqual(400) + expect(error.response.data.message).toEqual("property foo should not exist") + }) + + it("should get all regions and count", async () => { + await service.create([ + { + name: "Test", + currency_code: "usd", + }, + ]) + + const api = useApi() as any + const response = await api.get(`/admin/regions`, adminHeaders) + + expect(response.status).toEqual(200) + expect(response.data.regions).toEqual([ + expect.objectContaining({ + id: expect.any(String), + name: "Test", + currency_code: "usd", + }), + ]) + }) + + it("should get a region", async () => { + const [region] = await service.create([ + { + name: "Test", + currency_code: "usd", + }, + ]) + + const api = useApi() as any + const response = await api.get(`/admin/regions/${region.id}`, adminHeaders) + + expect(response.status).toEqual(200) + expect(response.data.region).toEqual( + expect.objectContaining({ + id: region.id, + name: "Test", + currency_code: "usd", + }) + ) + }) +}) diff --git a/packages/core-flows/src/index.ts b/packages/core-flows/src/index.ts index c1841bb2d2..2c8bb09c39 100644 --- a/packages/core-flows/src/index.ts +++ b/packages/core-flows/src/index.ts @@ -1,8 +1,10 @@ +export * from "./customer" +export * from "./customer-group" export * from "./definition" export * from "./definitions" export * as Handlers from "./handlers" -export * from "./promotion" -export * from "./customer" -export * from "./customer-group" -export * from "./user" export * from "./invite" +export * from "./promotion" +export * from "./region" +export * from "./user" + diff --git a/packages/core-flows/src/region/index.ts b/packages/core-flows/src/region/index.ts new file mode 100644 index 0000000000..68de82c9f9 --- /dev/null +++ b/packages/core-flows/src/region/index.ts @@ -0,0 +1,2 @@ +export * from "./steps" +export * from "./workflows" diff --git a/packages/core-flows/src/region/steps/create-regions.ts b/packages/core-flows/src/region/steps/create-regions.ts new file mode 100644 index 0000000000..d9fcfba354 --- /dev/null +++ b/packages/core-flows/src/region/steps/create-regions.ts @@ -0,0 +1,31 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { CreateRegionDTO, IRegionModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const createRegionsStepId = "create-regions" +export const createRegionsStep = createStep( + createRegionsStepId, + async (data: CreateRegionDTO[], { container }) => { + const service = container.resolve( + ModuleRegistrationName.REGION + ) + + const created = await service.create(data) + + return new StepResponse( + created, + created.map((region) => region.id) + ) + }, + async (createdIds, { container }) => { + if (!createdIds?.length) { + return + } + + const service = container.resolve( + ModuleRegistrationName.REGION + ) + + await service.delete(createdIds) + } +) diff --git a/packages/core-flows/src/region/steps/delete-regions.ts b/packages/core-flows/src/region/steps/delete-regions.ts new file mode 100644 index 0000000000..d7b11490d1 --- /dev/null +++ b/packages/core-flows/src/region/steps/delete-regions.ts @@ -0,0 +1,28 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { IRegionModuleService } from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +export const deleteRegionsStepId = "delete-regions" +export const deleteRegionsStep = createStep( + deleteRegionsStepId, + async (ids: string[], { container }) => { + const service = container.resolve( + ModuleRegistrationName.REGION + ) + + await service.softDelete(ids) + + return new StepResponse(void 0, ids) + }, + async (prevIds, { container }) => { + if (!prevIds?.length) { + return + } + + const service = container.resolve( + ModuleRegistrationName.REGION + ) + + await service.restore(prevIds) + } +) diff --git a/packages/core-flows/src/region/steps/index.ts b/packages/core-flows/src/region/steps/index.ts new file mode 100644 index 0000000000..e2a33dd84d --- /dev/null +++ b/packages/core-flows/src/region/steps/index.ts @@ -0,0 +1,3 @@ +export * from "./create-regions" +export * from "./delete-regions" +export * from "./update-regions" diff --git a/packages/core-flows/src/region/steps/update-regions.ts b/packages/core-flows/src/region/steps/update-regions.ts new file mode 100644 index 0000000000..1e30f1344c --- /dev/null +++ b/packages/core-flows/src/region/steps/update-regions.ts @@ -0,0 +1,54 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + FilterableRegionProps, + IRegionModuleService, + UpdatableRegionFields, +} from "@medusajs/types" +import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +type UpdateRegionsStepInput = { + selector: FilterableRegionProps + update: UpdatableRegionFields +} + +export const updateRegionsStepId = "update-region" +export const updateRegionsStep = createStep( + updateRegionsStepId, + async (data: UpdateRegionsStepInput, { container }) => { + const service = container.resolve( + ModuleRegistrationName.REGION + ) + + const { selects, relations } = getSelectsAndRelationsFromObjectArray([ + data.update, + ]) + + const prevData = await service.list(data.selector, { + select: selects, + relations, + }) + + const regions = await service.update(data.selector, data.update) + + return new StepResponse(regions, prevData) + }, + async (prevData, { container }) => { + if (!prevData?.length) { + return + } + + const service = container.resolve( + ModuleRegistrationName.REGION + ) + + await service.update( + prevData.map((r) => ({ + id: r.id, + name: r.name, + currency_code: r.currency_code, + metadata: r.metadata, + })) + ) + } +) diff --git a/packages/core-flows/src/region/workflows/create-regions.ts b/packages/core-flows/src/region/workflows/create-regions.ts new file mode 100644 index 0000000000..3be5b31882 --- /dev/null +++ b/packages/core-flows/src/region/workflows/create-regions.ts @@ -0,0 +1,13 @@ +import { CreateRegionDTO, RegionDTO } from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { createRegionsStep } from "../steps" + +type WorkflowInput = { regionsData: CreateRegionDTO[] } + +export const createRegionsWorkflowId = "create-regions" +export const createRegionsWorkflow = createWorkflow( + createRegionsWorkflowId, + (input: WorkflowData): WorkflowData => { + return createRegionsStep(input.regionsData) + } +) diff --git a/packages/core-flows/src/region/workflows/delete-regions.ts b/packages/core-flows/src/region/workflows/delete-regions.ts new file mode 100644 index 0000000000..cf0f58ea04 --- /dev/null +++ b/packages/core-flows/src/region/workflows/delete-regions.ts @@ -0,0 +1,12 @@ +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { deleteRegionsStep } from "../steps" + +type WorkflowInput = { ids: string[] } + +export const deleteRegionsWorkflowId = "delete-regions" +export const deleteRegionsWorkflow = createWorkflow( + deleteRegionsWorkflowId, + (input: WorkflowData): WorkflowData => { + return deleteRegionsStep(input.ids) + } +) diff --git a/packages/core-flows/src/region/workflows/index.ts b/packages/core-flows/src/region/workflows/index.ts new file mode 100644 index 0000000000..e2a33dd84d --- /dev/null +++ b/packages/core-flows/src/region/workflows/index.ts @@ -0,0 +1,3 @@ +export * from "./create-regions" +export * from "./delete-regions" +export * from "./update-regions" diff --git a/packages/core-flows/src/region/workflows/update-regions.ts b/packages/core-flows/src/region/workflows/update-regions.ts new file mode 100644 index 0000000000..601fdf7fb1 --- /dev/null +++ b/packages/core-flows/src/region/workflows/update-regions.ts @@ -0,0 +1,22 @@ +import { + FilterableRegionProps, + RegionDTO, + UpdatableRegionFields, +} from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { updateRegionsStep } from "../steps" + +type UpdateRegionsStepInput = { + selector: FilterableRegionProps + update: UpdatableRegionFields +} + +type WorkflowInput = UpdateRegionsStepInput + +export const updateRegionsWorkflowId = "update-regions" +export const updateRegionsWorkflow = createWorkflow( + updateRegionsWorkflowId, + (input: WorkflowData): WorkflowData => { + return updateRegionsStep(input) + } +) diff --git a/packages/medusa/src/api-v2/admin/regions/[id]/route.ts b/packages/medusa/src/api-v2/admin/regions/[id]/route.ts index e8886d1013..3167c36430 100644 --- a/packages/medusa/src/api-v2/admin/regions/[id]/route.ts +++ b/packages/medusa/src/api-v2/admin/regions/[id]/route.ts @@ -1,3 +1,8 @@ +import { + deleteRegionsWorkflow, + updateRegionsWorkflow, +} from "@medusajs/core-flows" +import { UpdatableRegionFields } from "@medusajs/types" import { remoteQueryObjectFromString } from "@medusajs/utils" import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { defaultAdminRegionFields } from "../query-config" @@ -17,3 +22,38 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => { res.status(200).json({ region }) } + +export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + const { result, errors } = await updateRegionsWorkflow(req.scope).run({ + input: { + selector: { id: req.params.id }, + update: req.validatedBody as UpdatableRegionFields, + }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ region: result[0] }) +} + +export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => { + const id = req.params.id + + const { errors } = await deleteRegionsWorkflow(req.scope).run({ + input: { ids: [id] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ + id, + object: "region", + deleted: true, + }) +} diff --git a/packages/medusa/src/api-v2/admin/regions/middlewares.ts b/packages/medusa/src/api-v2/admin/regions/middlewares.ts index df15958d80..5aef768c9f 100644 --- a/packages/medusa/src/api-v2/admin/regions/middlewares.ts +++ b/packages/medusa/src/api-v2/admin/regions/middlewares.ts @@ -1,9 +1,11 @@ -import { transformQuery } from "../../../api/middlewares" +import { transformBody, transformQuery } from "../../../api/middlewares" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import * as QueryConfig from "./query-config" import { AdminGetRegionsParams, AdminGetRegionsRegionParams, + AdminPostRegionsRegionReq, + AdminPostRegionsReq, } from "./validators" export const adminRegionRoutesMiddlewares: MiddlewareRoute[] = [ @@ -27,4 +29,14 @@ export const adminRegionRoutesMiddlewares: MiddlewareRoute[] = [ ), ], }, + { + method: ["POST"], + matcher: "/admin/regions", + middlewares: [transformBody(AdminPostRegionsReq)], + }, + { + method: ["POST"], + matcher: "/admin/regions/:id", + middlewares: [transformBody(AdminPostRegionsRegionReq)], + }, ] diff --git a/packages/medusa/src/api-v2/admin/regions/query-config.ts b/packages/medusa/src/api-v2/admin/regions/query-config.ts index e0256ffe51..ffbd9f98c8 100644 --- a/packages/medusa/src/api-v2/admin/regions/query-config.ts +++ b/packages/medusa/src/api-v2/admin/regions/query-config.ts @@ -1,3 +1,5 @@ +export const defaultAdminRegionRelations = ["countries", "currency"] +export const allowedAdminRegionRelations = ["countries", "currency"] export const defaultAdminRegionFields = [ "id", "name", @@ -18,6 +20,9 @@ export const defaultAdminRegionFields = [ ] export const retrieveTransformQueryConfig = { + defaultFields: defaultAdminRegionFields, + defaultRelations: defaultAdminRegionRelations, + allowedRelations: allowedAdminRegionRelations, isList: false, } diff --git a/packages/medusa/src/api-v2/admin/regions/route.ts b/packages/medusa/src/api-v2/admin/regions/route.ts index bf2a9d4e45..89cfeeaecf 100644 --- a/packages/medusa/src/api-v2/admin/regions/route.ts +++ b/packages/medusa/src/api-v2/admin/regions/route.ts @@ -1,3 +1,5 @@ +import { createRegionsWorkflow } from "@medusajs/core-flows" +import { CreateRegionDTO } from "@medusajs/types" import { remoteQueryObjectFromString } from "@medusajs/utils" import { MedusaRequest, MedusaResponse } from "../../../types/routing" import { defaultAdminRegionFields } from "./query-config" @@ -5,16 +7,42 @@ import { defaultAdminRegionFields } from "./query-config" export const GET = async (req: MedusaRequest, res: MedusaResponse) => { const remoteQuery = req.scope.resolve("remoteQuery") - const variables = { filters: req.filterableFields } - const queryObject = remoteQueryObjectFromString({ entryPoint: "region", - variables, + variables: { + filters: req.filterableFields, + order: req.listConfig.order, + skip: req.listConfig.skip, + take: req.listConfig.take, + }, fields: defaultAdminRegionFields, }) - // TODO: Add count, offset, limit - const regions = await remoteQuery(queryObject) + const { rows: regions, metadata } = await remoteQuery(queryObject) - res.json({ regions }) + res.json({ + regions, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} + +export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + const input = [ + { + ...(req.validatedBody as CreateRegionDTO), + }, + ] + + const { result, errors } = await createRegionsWorkflow(req.scope).run({ + input: { regionsData: input }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + res.status(200).json({ region: result[0] }) } diff --git a/packages/medusa/src/api-v2/admin/regions/validators.ts b/packages/medusa/src/api-v2/admin/regions/validators.ts index 31f75f9679..96ef914928 100644 --- a/packages/medusa/src/api-v2/admin/regions/validators.ts +++ b/packages/medusa/src/api-v2/admin/regions/validators.ts @@ -69,3 +69,21 @@ export class AdminGetRegionsParams extends extendedFindParamsMixin({ @Type(() => AdminGetRegionsParams) $or?: AdminGetRegionsParams[] } + +export class AdminPostRegionsReq { + @IsString() + name: string + + @IsString() + currency_code: string +} + +export class AdminPostRegionsRegionReq { + @IsString() + @IsOptional() + name?: string + + @IsString() + @IsOptional() + currency_code?: string +} diff --git a/packages/medusa/src/models/payment.ts b/packages/medusa/src/models/payment.ts index 1f3e8258b6..dabad809e9 100644 --- a/packages/medusa/src/models/payment.ts +++ b/packages/medusa/src/models/payment.ts @@ -10,11 +10,11 @@ import { import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column" import { BaseEntity } from "../interfaces/models/base-entity" +import { generateEntityId } from "../utils/generate-entity-id" import { Cart } from "./cart" import { Currency } from "./currency" import { Order } from "./order" import { Swap } from "./swap" -import { generateEntityId } from "../utils/generate-entity-id" @Index(["cart_id"], { where: "canceled_at IS NOT NULL" }) @Index("UniquePaymentActive", ["cart_id"], { diff --git a/packages/region/src/services/region-module.ts b/packages/region/src/services/region-module.ts index 0089b06c54..485565ddb9 100644 --- a/packages/region/src/services/region-module.ts +++ b/packages/region/src/services/region-module.ts @@ -2,6 +2,7 @@ import { Context, CreateRegionDTO, DAL, + FilterableRegionProps, InternalModuleDeclaration, IRegionModuleService, ModuleJoinerConfig, @@ -9,11 +10,14 @@ import { RegionCountryDTO, RegionCurrencyDTO, RegionDTO, + UpdatableRegionFields, UpdateRegionDTO, } from "@medusajs/types" import { InjectManager, InjectTransactionManager, + isObject, + isString, MedusaContext, MedusaError, ModulesSdkUtils, @@ -188,26 +192,68 @@ export default class RegionModuleService< } async update( - data: UpdateRegionDTO[], + selector: FilterableRegionProps, + data: UpdatableRegionFields, sharedContext?: Context ): Promise async update( - data: UpdateRegionDTO, + regionId: string, + data: UpdatableRegionFields, sharedContext?: Context ): Promise - @InjectTransactionManager("baseRepository_") + async update(data: UpdateRegionDTO[]): Promise + @InjectManager("baseRepository_") async update( - data: UpdateRegionDTO | UpdateRegionDTO[], + idOrSelectorOrData: string | FilterableRegionProps | UpdateRegionDTO[], + data?: UpdatableRegionFields, @MedusaContext() sharedContext: Context = {} ): Promise { - const result = await this.regionService_.update(data, sharedContext) + const result = await this.update_(idOrSelectorOrData, data, sharedContext) - return await this.baseRepository_.serialize( - Array.isArray(data) ? result : result[0], - { - populate: true, - } - ) + const regions = await this.baseRepository_.serialize< + RegionDTO[] | RegionDTO + >(result) + + return isString(idOrSelectorOrData) ? regions[0] : regions + } + + @InjectTransactionManager("baseRepository_") + protected async update_( + idOrSelectorOrData: string | FilterableRegionProps | UpdateRegionDTO[], + data?: UpdatableRegionFields, + @MedusaContext() sharedContext: Context = {} + ): Promise { + let result: Region[] = [] + if (isString(idOrSelectorOrData)) { + result = await this.regionService_.update( + [{ id: idOrSelectorOrData, ...data }], + sharedContext + ) + } + + if (Array.isArray(idOrSelectorOrData)) { + result = await this.regionService_.update( + idOrSelectorOrData, + sharedContext + ) + } + + if (isObject(idOrSelectorOrData)) { + let toUpdate: Partial[] = [] + const regions = await this.regionService_.list( + { ...idOrSelectorOrData }, + {}, + sharedContext + ) + + regions.forEach((region) => { + toUpdate.push({ id: region.id, ...data }) + }) + + result = await this.regionService_.update(toUpdate, sharedContext) + } + + return result } @InjectManager("baseRepository_") diff --git a/packages/types/src/region/mutations.ts b/packages/types/src/region/mutations.ts index 4b429dfcbc..88ad80be69 100644 --- a/packages/types/src/region/mutations.ts +++ b/packages/types/src/region/mutations.ts @@ -1,24 +1,22 @@ -import { RegionCurrencyDTO } from "./common" - export interface CreateRegionDTO { name: string currency_code: string - currency?: RegionCurrencyDTO countries?: string[] - tax_code?: string - tax_rate?: number - tax_provider_id?: string + metadata?: Record } export interface UpdateRegionDTO { id: string currency_code?: string - currency?: RegionCurrencyDTO - countries: string[] + countries?: string[] name?: string - tax_code?: string - tax_rate?: number - tax_provider_id?: string + metadata?: Record +} + +export interface UpdatableRegionFields { + currency_code?: string + name?: string + metadata?: Record } export interface AddCountryToRegionDTO { diff --git a/packages/types/src/region/service.ts b/packages/types/src/region/service.ts index 2dcc8655b6..8889d98f35 100644 --- a/packages/types/src/region/service.ts +++ b/packages/types/src/region/service.ts @@ -10,14 +10,27 @@ import { RegionCurrencyDTO, RegionDTO, } from "./common" -import { CreateRegionDTO, UpdateRegionDTO } from "./mutations" +import { + CreateRegionDTO, + UpdatableRegionFields, + UpdateRegionDTO, +} from "./mutations" export interface IRegionModuleService extends IModuleService { create(data: CreateRegionDTO[], sharedContext?: Context): Promise create(data: CreateRegionDTO, sharedContext?: Context): Promise - update(data: UpdateRegionDTO[], sharedContext?: Context): Promise - update(data: UpdateRegionDTO, sharedContext?: Context): Promise + update(data: UpdateRegionDTO[]): Promise + update( + selector: FilterableRegionProps, + data: UpdatableRegionFields, + sharedContext?: Context + ): Promise + update( + regionId: string, + data: UpdatableRegionFields, + sharedContext?: Context + ): Promise delete(ids: string[], sharedContext?: Context): Promise delete(id: string, sharedContext?: Context): Promise