From dbddfc12ed524e3923569fe1699e3cc558ed4f57 Mon Sep 17 00:00:00 2001 From: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:03:37 +0200 Subject: [PATCH] feat: Admin Shipping Profiles API (#7019) --- .../api/__tests__/admin/shipping-profile.js | 346 ------------------ .../__tests__/admin/shipping-profile.spec.ts | 228 ++++++++++++ .../steps/create-shipping-profiles.ts | 36 ++ .../core-flows/src/fulfillment/steps/index.ts | 1 + .../workflows/create-shipping-profiles.ts | 16 + .../src/fulfillment/workflows/index.ts | 3 +- .../[id]/service-zones/[zone_id]/route.ts | 2 +- .../admin/shipping-profiles/[id]/route.ts | 26 ++ .../admin/shipping-profiles/middlewares.ts | 51 +++ .../admin/shipping-profiles/query-config.ts | 18 + .../api-v2/admin/shipping-profiles/route.ts | 71 ++++ .../admin/shipping-profiles/validators.ts | 25 ++ packages/medusa/src/api-v2/middlewares.ts | 2 + packages/medusa/src/index.js | 1 - packages/medusa/src/joiner-config.ts | 5 - packages/medusa/src/joiner-configs/index.ts | 1 - .../shipping-profile-service.ts | 29 -- packages/medusa/src/loaders/medusa-app.ts | 24 +- .../fulfillment/admin/shipping-profile.ts | 12 +- .../types/src/workflow/fulfillment/index.ts | 3 +- .../workflow/fulfillment/shipping-profiles.ts | 20 + 21 files changed, 520 insertions(+), 400 deletions(-) delete mode 100644 integration-tests/api/__tests__/admin/shipping-profile.js create mode 100644 integration-tests/api/__tests__/admin/shipping-profile.spec.ts create mode 100644 packages/core-flows/src/fulfillment/steps/create-shipping-profiles.ts create mode 100644 packages/core-flows/src/fulfillment/workflows/create-shipping-profiles.ts create mode 100644 packages/medusa/src/api-v2/admin/shipping-profiles/[id]/route.ts create mode 100644 packages/medusa/src/api-v2/admin/shipping-profiles/middlewares.ts create mode 100644 packages/medusa/src/api-v2/admin/shipping-profiles/query-config.ts create mode 100644 packages/medusa/src/api-v2/admin/shipping-profiles/route.ts create mode 100644 packages/medusa/src/api-v2/admin/shipping-profiles/validators.ts delete mode 100644 packages/medusa/src/joiner-config.ts delete mode 100644 packages/medusa/src/joiner-configs/index.ts delete mode 100644 packages/medusa/src/joiner-configs/shipping-profile-service.ts create mode 100644 packages/types/src/workflow/fulfillment/shipping-profiles.ts diff --git a/integration-tests/api/__tests__/admin/shipping-profile.js b/integration-tests/api/__tests__/admin/shipping-profile.js deleted file mode 100644 index 8c4ab955de..0000000000 --- a/integration-tests/api/__tests__/admin/shipping-profile.js +++ /dev/null @@ -1,346 +0,0 @@ -const path = require("path") - -const setupServer = require("../../../environment-helpers/setup-server") -const { useApi } = require("../../../environment-helpers/use-api") -const { initDb, useDb } = require("../../../environment-helpers/use-db") -const { - simpleProductFactory, - simpleShippingOptionFactory, - simpleShippingProfileFactory, -} = require("../../../factories") -const adminSeeder = require("../../../helpers/admin-seeder") - -const adminReqConfig = { - headers: { - "x-medusa-access-token": "test_token", - }, -} - -jest.setTimeout(30000) - -describe("/admin/shipping-profiles", () => { - let medusaProcess - let dbConnection - - beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")) - dbConnection = await initDb({ cwd }) - medusaProcess = await setupServer({ cwd }) - }) - - afterAll(async () => { - const db = useDb() - await db.shutdown() - - medusaProcess.kill() - }) - - describe("GET /admin/shipping-profiles", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("lists shipping profiles", async () => { - const api = useApi() - - const { - data: { shipping_profiles }, - status, - } = await api.get("/admin/shipping-profiles", adminReqConfig) - - expect(status).toEqual(200) - - // Should contain default and gift_card profiles - expect(shipping_profiles.length).toEqual(2) - }) - - it("gets a shipping profile by id", async () => { - const api = useApi() - - const profile = await simpleShippingProfileFactory(dbConnection) - - const { - data: { shipping_profile }, - status, - } = await api.get( - `/admin/shipping-profiles/${profile.id}`, - adminReqConfig - ) - - expect(status).toEqual(200) - expect(shipping_profile).toEqual( - expect.objectContaining({ - ...profile, - updated_at: expect.any(String), - created_at: expect.any(String), - }) - ) - }) - }) - - describe("POST /admin/shipping-profiles", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("creates a custom shipping profile", async () => { - const api = useApi() - - const payload = { - name: "test-profile-2023", - type: "custom", - } - - const { - data: { shipping_profile }, - status, - } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) - - expect(status).toEqual(200) - expect(shipping_profile).toEqual( - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - ...payload, - }) - ) - }) - - it("creates a default shipping profile", async () => { - const api = useApi() - - const payload = { - name: "test-profile-2023", - type: "default", - } - - const { - data: { shipping_profile }, - status, - } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) - - expect(status).toEqual(200) - expect(shipping_profile).toEqual( - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - ...payload, - }) - ) - }) - - it("creates a gift_card shipping profile", async () => { - const api = useApi() - - const payload = { - name: "test-profile-2023", - type: "gift_card", - } - - const { - data: { shipping_profile }, - status, - } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) - - expect(status).toEqual(200) - expect(shipping_profile).toEqual( - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - ...payload, - }) - ) - }) - - it("creates a shipping profile with metadata", async () => { - const api = useApi() - - const payload = { - name: "test-profile-2023", - type: "default", - metadata: { - custom_key: "custom_value", - }, - } - - const { - data: { shipping_profile }, - status, - } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) - - expect(status).toEqual(200) - expect(shipping_profile).toEqual( - expect.objectContaining({ - id: expect.any(String), - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - ...payload, - }) - ) - }) - - it("fails to create a shipping profile with invalid type", async () => { - const api = useApi() - expect.assertions(2) - - const payload = { - name: "test-profile-2023", - type: "invalid", - } - - await api - .post("/admin/shipping-profiles", payload, adminReqConfig) - .catch((err) => { - expect(err.response.status).toEqual(400) - expect(err.response.data.message).toEqual( - "type must be one of 'default', 'custom', 'gift_card'" - ) - }) - }) - - it("updates a shipping profile", async () => { - const api = useApi() - - const testProducts = await Promise.all( - [...Array(5).keys()].map(async () => { - return await simpleProductFactory(dbConnection) - }) - ) - - const testShippingOptions = await Promise.all( - [...Array(5).keys()].map(async () => { - return await simpleShippingOptionFactory(dbConnection) - }) - ) - - const payload = { - name: "test-profile-2023", - type: "custom", - metadata: { - my_key: "my_value", - }, - } - - const { - data: { shipping_profile: created }, - } = await api.post("/admin/shipping-profiles", payload, adminReqConfig) - - const updatePayload = { - name: "test-profile-2023-updated", - products: testProducts.map((p) => p.id), - shipping_options: testShippingOptions.map((o) => o.id), - metadata: { - my_key: "", - my_new_key: "my_new_value", - }, - } - - const { - data: { shipping_profile }, - status, - } = await api.post( - `/admin/shipping-profiles/${created.id}`, - updatePayload, - adminReqConfig - ) - - expect(status).toEqual(200) - expect(shipping_profile).toEqual( - expect.objectContaining({ - name: "test-profile-2023-updated", - created_at: expect.any(String), - updated_at: expect.any(String), - metadata: { - my_new_key: "my_new_value", - }, - deleted_at: null, - type: "custom", - }) - ) - - const { - data: { products }, - } = await api.get(`/admin/products`, adminReqConfig) - - expect(products.length).toEqual(5) - expect(products).toEqual( - expect.arrayContaining( - testProducts.map((p) => { - return expect.objectContaining({ - id: p.id, - profile_id: shipping_profile.id, - }) - }) - ) - ) - - const { - data: { shipping_options }, - } = await api.get(`/admin/shipping-options`, adminReqConfig) - - const numberOfShippingOptionsWithProfile = shipping_options.filter( - (so) => so.profile_id === shipping_profile.id - ).length - - expect(numberOfShippingOptionsWithProfile).toEqual(5) - expect(shipping_options).toEqual( - expect.arrayContaining( - testShippingOptions.map((o) => { - return expect.objectContaining({ - id: o.id, - profile_id: shipping_profile.id, - }) - }) - ) - ) - }) - }) - - describe("DELETE /admin/shipping-profiles", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("deletes a shipping profile", async () => { - expect.assertions(2) - - const api = useApi() - - const profile = await simpleShippingProfileFactory(dbConnection) - - const { status } = await api.delete( - `/admin/shipping-profiles/${profile.id}`, - adminReqConfig - ) - - expect(status).toEqual(200) - await api - .get(`/admin/shipping-profiles/${profile.id}`, adminReqConfig) - .catch((err) => { - expect(err.response.status).toEqual(404) - }) - }) - }) -}) diff --git a/integration-tests/api/__tests__/admin/shipping-profile.spec.ts b/integration-tests/api/__tests__/admin/shipping-profile.spec.ts new file mode 100644 index 0000000000..1ab488c67e --- /dev/null +++ b/integration-tests/api/__tests__/admin/shipping-profile.spec.ts @@ -0,0 +1,228 @@ +import { medusaIntegrationTestRunner } from "medusa-test-utils" +import { breaking } from "../../../helpers/breaking" +import { + adminHeaders, + createAdminUser, +} from "../../../helpers/create-admin-user" + +let { + simpleProductFactory, + simpleShippingOptionFactory, + simpleShippingProfileFactory, +} = {} + +jest.setTimeout(30000) + +medusaIntegrationTestRunner({ + env: { + // MEDUSA_FF_MEDUSA_V2: true, + }, + testSuite: ({ dbConnection, getContainer, api }) => { + let appContainer + + beforeAll(() => { + ;({ + simpleProductFactory, + simpleShippingOptionFactory, + simpleShippingProfileFactory, + } = require("../../../factories")) + }) + + beforeEach(async () => { + appContainer = getContainer() + + await createAdminUser(dbConnection, adminHeaders, appContainer) + }) + + describe("Admin - Shipping Profiles", () => { + // TODO: Missing update and delete tests + it("should test the entire lifecycle of a shipping profile", async () => { + const payload = { + name: "test-profile-2023", + type: "custom", + } + + const { + data: { shipping_profile }, + status, + } = await api.post("/admin/shipping-profiles", payload, adminHeaders) + + expect(status).toEqual(200) + expect(shipping_profile).toEqual( + expect.objectContaining({ + id: expect.any(String), + created_at: expect.any(String), + updated_at: expect.any(String), + ...payload, + }) + ) + + const { + data: { shipping_profiles }, + } = await api.get("/admin/shipping-profiles", adminHeaders) + + // In V1, response should contain default and gift_card profiles too + expect(shipping_profiles.length).toEqual( + breaking( + () => 3, + () => 1 + ) + ) + + const { + data: { shipping_profile: retrievedProfile }, + } = await api.get( + `/admin/shipping-profiles/${shipping_profile.id}`, + adminHeaders + ) + + expect(status).toEqual(200) + expect(retrievedProfile).toEqual( + expect.objectContaining({ + id: shipping_profile.id, + updated_at: expect.any(String), + created_at: expect.any(String), + }) + ) + }) + }) + + describe("POST /admin/shipping-profiles", () => { + // TODO: There is no invalid types in V2 yet, so this will fail + it("fails to create a shipping profile with invalid type", async () => { + expect.assertions(2) + + const payload = { + name: "test-profile-2023", + type: "invalid", + } + + await api + .post("/admin/shipping-profiles", payload, adminHeaders) + .catch((err) => { + expect(err.response.status).toEqual(400) + expect(err.response.data.message).toEqual( + "type must be one of 'default', 'custom', 'gift_card'" + ) + }) + }) + + it("updates a shipping profile", async () => { + // TODO: Update is not added yet + const testProducts = await Promise.all( + [...Array(5).keys()].map(async () => { + return await simpleProductFactory(dbConnection) + }) + ) + + const testShippingOptions = await Promise.all( + [...Array(5).keys()].map(async () => { + return await simpleShippingOptionFactory(dbConnection) + }) + ) + + const payload = { + name: "test-profile-2023", + type: "custom", + metadata: { + my_key: "my_value", + }, + } + + const { + data: { shipping_profile: created }, + } = await api.post("/admin/shipping-profiles", payload, adminHeaders) + + const updatePayload = { + name: "test-profile-2023-updated", + products: testProducts.map((p) => p.id), + shipping_options: testShippingOptions.map((o) => o.id), + metadata: { + my_key: "", + my_new_key: "my_new_value", + }, + } + + const { + data: { shipping_profile }, + status, + } = await api.post( + `/admin/shipping-profiles/${created.id}`, + updatePayload, + adminHeaders + ) + + expect(status).toEqual(200) + expect(shipping_profile).toEqual( + expect.objectContaining({ + name: "test-profile-2023-updated", + created_at: expect.any(String), + updated_at: expect.any(String), + metadata: { + my_new_key: "my_new_value", + }, + deleted_at: null, + type: "custom", + }) + ) + + const { + data: { products }, + } = await api.get(`/admin/products`, adminHeaders) + + expect(products.length).toEqual(5) + expect(products).toEqual( + expect.arrayContaining( + testProducts.map((p) => { + return expect.objectContaining({ + id: p.id, + profile_id: shipping_profile.id, + }) + }) + ) + ) + + const { + data: { shipping_options }, + } = await api.get(`/admin/shipping-options`, adminHeaders) + + const numberOfShippingOptionsWithProfile = shipping_options.filter( + (so) => so.profile_id === shipping_profile.id + ).length + + expect(numberOfShippingOptionsWithProfile).toEqual(5) + expect(shipping_options).toEqual( + expect.arrayContaining( + testShippingOptions.map((o) => { + return expect.objectContaining({ + id: o.id, + profile_id: shipping_profile.id, + }) + }) + ) + ) + }) + }) + + describe("DELETE /admin/shipping-profiles", () => { + // TODO: Delete is not added yet + it("deletes a shipping profile", async () => { + expect.assertions(2) + + const profile = await simpleShippingProfileFactory(dbConnection) + + const { status } = await api.delete( + `/admin/shipping-profiles/${profile.id}`, + adminHeaders + ) + + expect(status).toEqual(200) + await api + .get(`/admin/shipping-profiles/${profile.id}`, adminHeaders) + .catch((err) => { + expect(err.response.status).toEqual(404) + }) + }) + }) + }, +}) diff --git a/packages/core-flows/src/fulfillment/steps/create-shipping-profiles.ts b/packages/core-flows/src/fulfillment/steps/create-shipping-profiles.ts new file mode 100644 index 0000000000..aa3c8de3cc --- /dev/null +++ b/packages/core-flows/src/fulfillment/steps/create-shipping-profiles.ts @@ -0,0 +1,36 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + CreateShippingProfileDTO, + IFulfillmentModuleService, +} from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +type StepInput = CreateShippingProfileDTO[] + +export const createShippingProfilesStepId = "create-shipping-profiles" +export const createShippingProfilesStep = createStep( + createShippingProfilesStepId, + async (input: StepInput, { container }) => { + const service = container.resolve( + ModuleRegistrationName.FULFILLMENT + ) + + const createdShippingProfiles = await service.createShippingProfiles(input) + + return new StepResponse( + createdShippingProfiles, + createdShippingProfiles.map((created) => created.id) + ) + }, + async (createdShippingProfiles, { container }) => { + if (!createdShippingProfiles?.length) { + return + } + + const service = container.resolve( + ModuleRegistrationName.FULFILLMENT + ) + + await service.deleteShippingProfiles(createdShippingProfiles) + } +) diff --git a/packages/core-flows/src/fulfillment/steps/index.ts b/packages/core-flows/src/fulfillment/steps/index.ts index 7405526dfa..f9ce27234b 100644 --- a/packages/core-flows/src/fulfillment/steps/index.ts +++ b/packages/core-flows/src/fulfillment/steps/index.ts @@ -4,4 +4,5 @@ export * from "./create-fulfillment-set" export * from "./create-service-zones" export * from "./upsert-shipping-options" export * from "./delete-service-zones" +export * from "./create-shipping-profiles" export * from "./remove-rules-from-fulfillment-shipping-option" diff --git a/packages/core-flows/src/fulfillment/workflows/create-shipping-profiles.ts b/packages/core-flows/src/fulfillment/workflows/create-shipping-profiles.ts new file mode 100644 index 0000000000..d1821c711b --- /dev/null +++ b/packages/core-flows/src/fulfillment/workflows/create-shipping-profiles.ts @@ -0,0 +1,16 @@ +import { FulfillmentWorkflow } from "@medusajs/types" +import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk" +import { createShippingProfilesStep } from "../steps" + +export const createShippingProfilesWorkflowId = + "create-shipping-profiles-workflow" +export const createShippingProfilesWorkflow = createWorkflow( + createShippingProfilesWorkflowId, + ( + input: WorkflowData + ): WorkflowData => { + const shippingProfiles = createShippingProfilesStep(input.data) + + return shippingProfiles + } +) diff --git a/packages/core-flows/src/fulfillment/workflows/index.ts b/packages/core-flows/src/fulfillment/workflows/index.ts index 4a0b55ed35..c3f198ed52 100644 --- a/packages/core-flows/src/fulfillment/workflows/index.ts +++ b/packages/core-flows/src/fulfillment/workflows/index.ts @@ -1,7 +1,8 @@ export * from "./add-rules-to-fulfillment-shipping-option" export * from "./create-service-zones" export * from "./create-shipping-options" -export * from "./update-shipping-options" +export * from "./create-shipping-profiles" export * from "./delete-service-zones" export * from "./remove-rules-from-fulfillment-shipping-option" export * from "./update-service-zones" +export * from "./update-shipping-options" diff --git a/packages/medusa/src/api-v2/admin/fulfillment-sets/[id]/service-zones/[zone_id]/route.ts b/packages/medusa/src/api-v2/admin/fulfillment-sets/[id]/service-zones/[zone_id]/route.ts index 805f523cbc..2bdd6df476 100644 --- a/packages/medusa/src/api-v2/admin/fulfillment-sets/[id]/service-zones/[zone_id]/route.ts +++ b/packages/medusa/src/api-v2/admin/fulfillment-sets/[id]/service-zones/[zone_id]/route.ts @@ -40,7 +40,7 @@ export const GET = async ( if (!service_zone) { throw new MedusaError( MedusaError.Types.NOT_FOUND, - `Service zone with id ${req.params.zone_id} not found` + `Service zone with id: ${req.params.zone_id} not found` ) } diff --git a/packages/medusa/src/api-v2/admin/shipping-profiles/[id]/route.ts b/packages/medusa/src/api-v2/admin/shipping-profiles/[id]/route.ts new file mode 100644 index 0000000000..352fc63516 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/shipping-profiles/[id]/route.ts @@ -0,0 +1,26 @@ +import { AdminShippingProfileResponse } from "@medusajs/types" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../../types/routing" + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const query = remoteQueryObjectFromString({ + entryPoint: "shipping_profiles", + variables: { id: req.params.id }, + fields: req.remoteQueryConfig.fields, + }) + + const [shippingProfile] = await remoteQuery(query) + + res.status(200).json({ shipping_profile: shippingProfile }) +} diff --git a/packages/medusa/src/api-v2/admin/shipping-profiles/middlewares.ts b/packages/medusa/src/api-v2/admin/shipping-profiles/middlewares.ts new file mode 100644 index 0000000000..9112483bf4 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/shipping-profiles/middlewares.ts @@ -0,0 +1,51 @@ +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" +import { + listTransformQueryConfig, + retrieveTransformQueryConfig, +} from "./query-config" +import { + AdminCreateShippingProfile, + AdminShippingProfileParams, + AdminShippingProfilesParams, +} from "./validators" + +export const adminShippingProfilesMiddlewares: MiddlewareRoute[] = [ + { + matcher: "/admin/shipping-profiles*", + middlewares: [authenticate("admin", ["bearer", "session", "api-key"])], + }, + { + method: ["POST"], + matcher: "/admin/shipping-profiles", + middlewares: [ + validateAndTransformQuery( + AdminShippingProfilesParams, + retrieveTransformQueryConfig + ), + validateAndTransformBody(AdminCreateShippingProfile), + ], + }, + { + method: ["GET"], + matcher: "/admin/shipping-profiles", + middlewares: [ + validateAndTransformQuery( + AdminShippingProfilesParams, + listTransformQueryConfig + ), + ], + }, + { + method: ["GET"], + matcher: "/admin/shipping-profiles/:id", + middlewares: [ + validateAndTransformQuery( + AdminShippingProfileParams, + retrieveTransformQueryConfig + ), + ], + }, +] diff --git a/packages/medusa/src/api-v2/admin/shipping-profiles/query-config.ts b/packages/medusa/src/api-v2/admin/shipping-profiles/query-config.ts new file mode 100644 index 0000000000..bdfc36524e --- /dev/null +++ b/packages/medusa/src/api-v2/admin/shipping-profiles/query-config.ts @@ -0,0 +1,18 @@ +export const defaultAdminShippingProfileFields = [ + "id", + "name", + "type", + "metadata", + "created_at", + "updated_at", +] + +export const retrieveTransformQueryConfig = { + defaults: defaultAdminShippingProfileFields, + isList: false, +} + +export const listTransformQueryConfig = { + ...retrieveTransformQueryConfig, + isList: true, +} diff --git a/packages/medusa/src/api-v2/admin/shipping-profiles/route.ts b/packages/medusa/src/api-v2/admin/shipping-profiles/route.ts new file mode 100644 index 0000000000..fccd4acfd6 --- /dev/null +++ b/packages/medusa/src/api-v2/admin/shipping-profiles/route.ts @@ -0,0 +1,71 @@ +import { createShippingProfilesWorkflow } from "@medusajs/core-flows" +import { + AdminShippingProfileResponse, + AdminShippingProfilesResponse, +} from "@medusajs/types" +import { + ContainerRegistrationKeys, + remoteQueryObjectFromString, +} from "@medusajs/utils" +import { + AuthenticatedMedusaRequest, + MedusaResponse, +} from "../../../types/routing" +import { AdminCreateShippingProfileType } from "./validators" + +export const POST = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const shippingProfilePayload = req.validatedBody + + const { result, errors } = await createShippingProfilesWorkflow( + req.scope + ).run({ + input: { data: [shippingProfilePayload] }, + throwOnError: false, + }) + + if (Array.isArray(errors) && errors[0]) { + throw errors[0].error + } + + const shippingProfileId = result?.[0].id + + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const query = remoteQueryObjectFromString({ + entryPoint: "shipping_profiles", + variables: { id: shippingProfileId }, + fields: req.remoteQueryConfig.fields, + }) + + const [shippingProfile] = await remoteQuery(query) + + res.status(200).json({ shipping_profile: shippingProfile }) +} + +export const GET = async ( + req: AuthenticatedMedusaRequest, + res: MedusaResponse +) => { + const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) + + const query = remoteQueryObjectFromString({ + entryPoint: "shipping_profiles", + variables: { + filters: req.filterableFields, + ...req.remoteQueryConfig.pagination, + }, + fields: req.remoteQueryConfig.fields, + }) + + const { rows: shippingProfiles, metadata } = await remoteQuery(query) + + res.status(200).json({ + shipping_profiles: shippingProfiles, + count: metadata.count, + offset: metadata.skip, + limit: metadata.take, + }) +} diff --git a/packages/medusa/src/api-v2/admin/shipping-profiles/validators.ts b/packages/medusa/src/api-v2/admin/shipping-profiles/validators.ts new file mode 100644 index 0000000000..66c53eda1f --- /dev/null +++ b/packages/medusa/src/api-v2/admin/shipping-profiles/validators.ts @@ -0,0 +1,25 @@ +import { z } from "zod" +import { createFindParams, createSelectParams } from "../../utils/validators" + +export const AdminShippingProfileParams = createSelectParams() +export const AdminShippingProfilesParams = createFindParams({ + limit: 20, + offset: 0, +}).merge( + z.object({ + type: z.string().optional(), + name: z.string().optional(), + }) +) + +export const AdminCreateShippingProfile = z + .object({ + name: z.string(), + type: z.string(), + metadata: z.record(z.string(), z.unknown()).optional(), + }) + .strict() + +export type AdminCreateShippingProfileType = z.infer< + typeof AdminCreateShippingProfile +> diff --git a/packages/medusa/src/api-v2/middlewares.ts b/packages/medusa/src/api-v2/middlewares.ts index 59afc2fe7d..c5c2d1b562 100644 --- a/packages/medusa/src/api-v2/middlewares.ts +++ b/packages/medusa/src/api-v2/middlewares.ts @@ -19,6 +19,7 @@ import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares" import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares" import { adminSalesChannelRoutesMiddlewares } from "./admin/sales-channels/middlewares" import { adminShippingOptionRoutesMiddlewares } from "./admin/shipping-options/middlewares" +import { adminShippingProfilesMiddlewares } from "./admin/shipping-profiles/middlewares" import { adminStockLocationRoutesMiddlewares } from "./admin/stock-locations/middlewares" import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares" import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares" @@ -69,5 +70,6 @@ export const config: MiddlewaresConfig = { ...adminUploadRoutesMiddlewares, ...adminFulfillmentSetsRoutesMiddlewares, ...adminProductCategoryRoutesMiddlewares, + ...adminShippingProfilesMiddlewares, ], } diff --git a/packages/medusa/src/index.js b/packages/medusa/src/index.js index 4b8f350e7f..e4a198a48c 100644 --- a/packages/medusa/src/index.js +++ b/packages/medusa/src/index.js @@ -2,7 +2,6 @@ export * from "./api" export * from "./api-v2" export * from "./api/middlewares" export * from "./interfaces" -export * from "./joiner-config" export * from "./models" export * from "./modules-config" export * from "./services" diff --git a/packages/medusa/src/joiner-config.ts b/packages/medusa/src/joiner-config.ts deleted file mode 100644 index 4cb05ead02..0000000000 --- a/packages/medusa/src/joiner-config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as joinerConfigs from "./joiner-configs" - -export const joinerConfig = Object.values(joinerConfigs).map( - (config) => config.default -) diff --git a/packages/medusa/src/joiner-configs/index.ts b/packages/medusa/src/joiner-configs/index.ts deleted file mode 100644 index e2b90d82a8..0000000000 --- a/packages/medusa/src/joiner-configs/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as shippingProfile from "./shipping-profile-service" diff --git a/packages/medusa/src/joiner-configs/shipping-profile-service.ts b/packages/medusa/src/joiner-configs/shipping-profile-service.ts deleted file mode 100644 index b04b3b2263..0000000000 --- a/packages/medusa/src/joiner-configs/shipping-profile-service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ModuleJoinerConfig } from "@medusajs/types" - -export default { - serviceName: "shippingProfileService", - primaryKeys: ["id"], - linkableKeys: { profile_id: "ShippingProfile" }, - schema: ` - scalar Date - scalar JSON - - type ShippingProfile { - id: ID! - name: String! - type: String! - created_at: Date! - updated_at: Date! - deleted_at: Date - metadata: JSON - } - `, - alias: [ - { - name: ["shipping_profile", "shipping_profiles"], - args: { - entity: "ShippingProfile", - }, - }, - ], -} as ModuleJoinerConfig diff --git a/packages/medusa/src/loaders/medusa-app.ts b/packages/medusa/src/loaders/medusa-app.ts index e1311b3270..ccfe6eecd8 100644 --- a/packages/medusa/src/loaders/medusa-app.ts +++ b/packages/medusa/src/loaders/medusa-app.ts @@ -1,3 +1,12 @@ +import { + MODULE_PACKAGE_NAMES, + MedusaApp, + MedusaAppMigrateUp, + MedusaAppOutput, + MedusaModule, + Modules, + ModulesDefinition, +} from "@medusajs/modules-sdk" import { CommonTypes, InternalModuleDeclaration, @@ -8,21 +17,11 @@ import { import { ContainerRegistrationKeys, FlagRouter, - isObject, MedusaV2Flag, + isObject, } from "@medusajs/utils" -import { - MedusaApp, - MedusaAppMigrateUp, - MedusaAppOutput, - MedusaModule, - MODULE_PACKAGE_NAMES, - Modules, - ModulesDefinition, -} from "@medusajs/modules-sdk" import { asValue } from "awilix" -import { joinerConfig } from "../joiner-config" import { remoteQueryFetchData } from "../utils/remote-query-fetch-data" export function mergeDefaultModules( @@ -101,7 +100,6 @@ export async function migrateMedusaApp( await MedusaAppMigrateUp({ modulesConfig: configModules, - servicesConfig: joinerConfig, remoteFetchData: remoteQueryFetchData(container), sharedContainer: container, sharedResourcesConfig, @@ -170,7 +168,6 @@ export const loadMedusaApp = async ( const medusaApp = await MedusaApp({ workerMode: configModule.projectConfig.worker_mode, modulesConfig: configModules, - servicesConfig: joinerConfig, remoteFetchData: remoteQueryFetchData(container), sharedContainer: container, sharedResourcesConfig, @@ -291,7 +288,6 @@ export async function runModulesLoader({ await MedusaApp({ modulesConfig: configModules, - servicesConfig: joinerConfig, remoteFetchData: remoteQueryFetchData(container), sharedContainer: container, sharedResourcesConfig, diff --git a/packages/types/src/http/fulfillment/admin/shipping-profile.ts b/packages/types/src/http/fulfillment/admin/shipping-profile.ts index 21408cb8fa..05e689901e 100644 --- a/packages/types/src/http/fulfillment/admin/shipping-profile.ts +++ b/packages/types/src/http/fulfillment/admin/shipping-profile.ts @@ -1,7 +1,9 @@ +import { PaginatedResponse } from "../../../common" + /** * @experimental */ -export interface AdminShippingProfileResponse { +export interface ShippingProfileResponse { id: string name: string type: string @@ -10,3 +12,11 @@ export interface AdminShippingProfileResponse { updated_at: Date deleted_at: Date | null } + +export interface AdminShippingProfileResponse { + shipping_profile: ShippingProfileResponse +} + +export interface AdminShippingProfilesResponse extends PaginatedResponse { + shipping_profiles: ShippingProfileResponse[] +} diff --git a/packages/types/src/workflow/fulfillment/index.ts b/packages/types/src/workflow/fulfillment/index.ts index b510e2b481..8ac89fcc92 100644 --- a/packages/types/src/workflow/fulfillment/index.ts +++ b/packages/types/src/workflow/fulfillment/index.ts @@ -1,3 +1,4 @@ export * from "./create-shipping-options" -export * from "./update-shipping-options" export * from "./service-zones" +export * from "./shipping-profiles" +export * from "./update-shipping-options" diff --git a/packages/types/src/workflow/fulfillment/shipping-profiles.ts b/packages/types/src/workflow/fulfillment/shipping-profiles.ts new file mode 100644 index 0000000000..2def6b6380 --- /dev/null +++ b/packages/types/src/workflow/fulfillment/shipping-profiles.ts @@ -0,0 +1,20 @@ +import { FilterableShippingProfileProps } from "../../fulfillment" + +interface CreateShippingProfile { + name: string + type: string +} + +export interface CreateShippingProfilesWorkflowInput { + data: CreateShippingProfile[] +} + +interface UpdateShippingProfile { + name?: string + type?: string +} + +export interface UpdateShippingProfilesWorkflowInput { + selector: FilterableShippingProfileProps + update: UpdateShippingProfile +}