feat: Sales Channels API routes + workflows (#6722)
**What** - Admin sales channels API routes - Workflows for creating, updating, and deleting sales channels - Align `ISalesChannelModuleService` interface with update + upsert conventions - Integrate v2 tests into v1 sales channels integration test suite - Add metadata to sales channel entity Sales channel <> Product management will come in a follow-up PR. On the integration tests, creating, updating, and deleting are passing with the V2 flag enabled. You'll have to take my word for it (or run them yourself), as they are not included in the CI.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`sales channels DELETE /admin/sales-channels/:id should delete the requested sales channel 1`] = `
|
||||
exports[` DELETE /admin/sales-channels/:id should delete the requested sales channel 1`] = `
|
||||
Object {
|
||||
"deleted": true,
|
||||
"id": Any<String>,
|
||||
@@ -8,7 +8,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sales channels POST /admin/sales-channels successfully creates a disabled sales channel 1`] = `
|
||||
exports[` POST /admin/sales-channels successfully creates a disabled sales channel 1`] = `
|
||||
Object {
|
||||
"sales_channel": ObjectContaining {
|
||||
"is_disabled": true,
|
||||
@@ -17,7 +17,7 @@ Object {
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`sales channels POST /admin/sales-channels successfully creates a sales channel 1`] = `
|
||||
exports[` POST /admin/sales-channels successfully creates a sales channel 1`] = `
|
||||
Object {
|
||||
"sales_channel": ObjectContaining {
|
||||
"description": "sales channel description",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
type DeleteCustomerStepInput = string[]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from "./auth"
|
||||
export * from "./api-key"
|
||||
export * from "./auth"
|
||||
export * from "./customer"
|
||||
export * from "./customer-group"
|
||||
export * from "./definition"
|
||||
@@ -14,6 +14,7 @@ export * from "./pricing"
|
||||
export * from "./product"
|
||||
export * from "./promotion"
|
||||
export * from "./region"
|
||||
export * from "./sales-channel"
|
||||
export * from "./shipping-options"
|
||||
export * from "./store"
|
||||
export * from "./tax"
|
||||
|
||||
2
packages/core-flows/src/sales-channel/index.ts
Normal file
2
packages/core-flows/src/sales-channel/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./steps"
|
||||
export * from "./workflows"
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
CreateSalesChannelDTO,
|
||||
ISalesChannelModuleService,
|
||||
} from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
interface StepInput {
|
||||
data: CreateSalesChannelDTO[]
|
||||
}
|
||||
|
||||
export const createSalesChannelsStepId = "create-sales-channels"
|
||||
export const createSalesChannelsStep = createStep(
|
||||
createSalesChannelsStepId,
|
||||
async (input: StepInput, { container }) => {
|
||||
const salesChannelService = container.resolve<ISalesChannelModuleService>(
|
||||
ModuleRegistrationName.SALES_CHANNEL
|
||||
)
|
||||
|
||||
const salesChannels = await salesChannelService.create(input.data)
|
||||
|
||||
return new StepResponse(
|
||||
salesChannels,
|
||||
salesChannels.map((s) => s.id)
|
||||
)
|
||||
},
|
||||
async (createdIds, { container }) => {
|
||||
if (!createdIds) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<ISalesChannelModuleService>(
|
||||
ModuleRegistrationName.SALES_CHANNEL
|
||||
)
|
||||
|
||||
await service.delete(createdIds)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type DeleteSalesChannelsInput = string[]
|
||||
|
||||
export const deleteSalesChannelsStepId = "delete-sales-channels"
|
||||
export const deleteSalesChannelsStep = createStep(
|
||||
deleteSalesChannelsStepId,
|
||||
async (ids: DeleteSalesChannelsInput, { container }) => {
|
||||
const service = container.resolve<ISalesChannelModuleService>(
|
||||
ModuleRegistrationName.SALES_CHANNEL
|
||||
)
|
||||
|
||||
await service.softDelete(ids)
|
||||
|
||||
return new StepResponse(void 0, ids)
|
||||
},
|
||||
async (prevSalesChannelIds, { container }) => {
|
||||
if (!prevSalesChannelIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<ISalesChannelModuleService>(
|
||||
ModuleRegistrationName.SALES_CHANNEL
|
||||
)
|
||||
|
||||
await service.restore(prevSalesChannelIds)
|
||||
}
|
||||
)
|
||||
3
packages/core-flows/src/sales-channel/steps/index.ts
Normal file
3
packages/core-flows/src/sales-channel/steps/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./create-sales-channels"
|
||||
export * from "./update-sales-channels"
|
||||
export * from "./delete-sales-channels"
|
||||
@@ -0,0 +1,55 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
FilterableSalesChannelProps,
|
||||
ISalesChannelModuleService,
|
||||
UpdateSalesChannelDTO,
|
||||
} from "@medusajs/types"
|
||||
import { getSelectsAndRelationsFromObjectArray } from "@medusajs/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type UpdateSalesChannelsStepInput = {
|
||||
selector: FilterableSalesChannelProps
|
||||
update: UpdateSalesChannelDTO
|
||||
}
|
||||
|
||||
export const updateSalesChannelsStepId = "update-sales-channels"
|
||||
export const updateSalesChannelsStep = createStep(
|
||||
updateSalesChannelsStepId,
|
||||
async (data: UpdateSalesChannelsStepInput, { container }) => {
|
||||
const service = container.resolve<ISalesChannelModuleService>(
|
||||
ModuleRegistrationName.SALES_CHANNEL
|
||||
)
|
||||
|
||||
const { selects, relations } = getSelectsAndRelationsFromObjectArray([
|
||||
data.update,
|
||||
])
|
||||
|
||||
const prevData = await service.list(data.selector, {
|
||||
select: selects,
|
||||
relations,
|
||||
})
|
||||
|
||||
const channels = await service.update(data.selector, data.update)
|
||||
|
||||
return new StepResponse(channels, prevData)
|
||||
},
|
||||
async (prevData, { container }) => {
|
||||
if (!prevData?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<ISalesChannelModuleService>(
|
||||
ModuleRegistrationName.SALES_CHANNEL
|
||||
)
|
||||
|
||||
await service.upsert(
|
||||
prevData.map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
description: r.description,
|
||||
is_disabled: r.is_disabled,
|
||||
metadata: r.metadata,
|
||||
}))
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
import { CreateSalesChannelDTO, SalesChannelDTO } from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { createSalesChannelsStep } from "../steps/create-sales-channels"
|
||||
|
||||
type WorkflowInput = { salesChannelsData: CreateSalesChannelDTO[] }
|
||||
|
||||
export const createSalesChannelsWorkflowId = "create-sales-channels"
|
||||
export const createSalesChannelsWorkflow = createWorkflow(
|
||||
createSalesChannelsWorkflowId,
|
||||
(input: WorkflowData<WorkflowInput>): WorkflowData<SalesChannelDTO[]> => {
|
||||
return createSalesChannelsStep({ data: input.salesChannelsData })
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { deleteSalesChannelsStep } from "../steps/delete-sales-channels"
|
||||
|
||||
type WorkflowInput = { ids: string[] }
|
||||
|
||||
export const deleteSalesChannelsWorkflowId = "delete-sales-channels"
|
||||
export const deleteSalesChannelsWorkflow = createWorkflow(
|
||||
deleteSalesChannelsWorkflowId,
|
||||
(input: WorkflowData<WorkflowInput>): WorkflowData<void> => {
|
||||
return deleteSalesChannelsStep(input.ids)
|
||||
}
|
||||
)
|
||||
3
packages/core-flows/src/sales-channel/workflows/index.ts
Normal file
3
packages/core-flows/src/sales-channel/workflows/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./create-sales-channels"
|
||||
export * from "./update-sales-channels"
|
||||
export * from "./delete-sales-channels"
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
FilterableSalesChannelProps,
|
||||
SalesChannelDTO,
|
||||
UpdateSalesChannelDTO,
|
||||
} from "@medusajs/types"
|
||||
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
import { updateSalesChannelsStep } from "../steps/update-sales-channels"
|
||||
|
||||
type UpdateSalesChannelsStepInput = {
|
||||
selector: FilterableSalesChannelProps
|
||||
update: UpdateSalesChannelDTO
|
||||
}
|
||||
|
||||
export const updateSalesChannelsWorkflowId = "update-sales-channels"
|
||||
export const updateSalesChannelsWorkflow = createWorkflow(
|
||||
updateSalesChannelsWorkflowId,
|
||||
(
|
||||
input: WorkflowData<UpdateSalesChannelsStepInput>
|
||||
): WorkflowData<SalesChannelDTO[]> => {
|
||||
return updateSalesChannelsStep(input)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
deleteSalesChannelsWorkflow,
|
||||
updateSalesChannelsWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../types/routing"
|
||||
import { defaultAdminSalesChannelFields } from "../query-config"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const variables = {
|
||||
id: req.params.id,
|
||||
}
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "sales_channels",
|
||||
variables,
|
||||
fields: defaultAdminSalesChannelFields,
|
||||
})
|
||||
|
||||
const [sales_channel] = await remoteQuery(queryObject)
|
||||
|
||||
res.json({ sales_channel })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const { errors } = await updateSalesChannelsWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: req.params.id },
|
||||
update: req.validatedBody,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "sales_channels",
|
||||
variables: { id: req.params.id },
|
||||
fields: defaultAdminSalesChannelFields,
|
||||
})
|
||||
|
||||
const [sales_channel] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ sales_channel })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const id = req.params.id
|
||||
|
||||
const { errors } = await deleteSalesChannelsWorkflow(req.scope).run({
|
||||
input: { ids: [id] },
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
id,
|
||||
object: "sales-channel",
|
||||
deleted: true,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
import * as QueryConfig from "./query-config"
|
||||
import {
|
||||
AdminGetSalesChannelsParams,
|
||||
AdminGetSalesChannelsSalesChannelParams,
|
||||
AdminPostSalesChannelsReq,
|
||||
AdminPostSalesChannelsSalesChannelReq,
|
||||
} from "./validators"
|
||||
|
||||
export const adminSalesChannelRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: ["ALL"],
|
||||
matcher: "/admin/sales-channels*",
|
||||
middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/sales-channels",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetSalesChannelsParams,
|
||||
QueryConfig.listTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/admin/sales-channels/:id",
|
||||
middlewares: [
|
||||
transformQuery(
|
||||
AdminGetSalesChannelsSalesChannelParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/sales-channels",
|
||||
middlewares: [
|
||||
transformBody(AdminPostSalesChannelsReq),
|
||||
transformQuery(
|
||||
AdminGetSalesChannelsSalesChannelParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/sales-channels/:id",
|
||||
middlewares: [transformBody(AdminPostSalesChannelsSalesChannelReq)],
|
||||
},
|
||||
{
|
||||
method: ["DELETE"],
|
||||
matcher: "/admin/sales-channels/:id",
|
||||
middlewares: [],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
export const defaultAdminSalesChannelFields = [
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"is_disabled",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const retrieveTransformQueryConfig = {
|
||||
defaults: defaultAdminSalesChannelFields,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listTransformQueryConfig = {
|
||||
...retrieveTransformQueryConfig,
|
||||
isList: true,
|
||||
}
|
||||
65
packages/medusa/src/api-v2/admin/sales-channels/route.ts
Normal file
65
packages/medusa/src/api-v2/admin/sales-channels/route.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { createSalesChannelsWorkflow } from "@medusajs/core-flows"
|
||||
import { CreateSalesChannelDTO } 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 variables = {
|
||||
filters: req.filterableFields,
|
||||
...req.remoteQueryConfig.pagination,
|
||||
}
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "sales_channels",
|
||||
variables,
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const { rows: sales_channels, metadata } = await remoteQuery(queryObject)
|
||||
|
||||
res.json({
|
||||
sales_channels,
|
||||
count: metadata.count,
|
||||
offset: metadata.skip,
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<CreateSalesChannelDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const salesChannelsData = [req.validatedBody]
|
||||
|
||||
const { errors } = await createSalesChannelsWorkflow(req.scope).run({
|
||||
input: { salesChannelsData },
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "sales_channels",
|
||||
variables: { id: req.params.id },
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const [sales_channel] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ sales_channel })
|
||||
}
|
||||
102
packages/medusa/src/api-v2/admin/sales-channels/validators.ts
Normal file
102
packages/medusa/src/api-v2/admin/sales-channels/validators.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { OperatorMap } from "@medusajs/types"
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||
import { OperatorMapValidator } from "../../../types/validators/operator-map"
|
||||
|
||||
export class AdminGetSalesChannelsSalesChannelParams extends FindParams {}
|
||||
|
||||
export class AdminGetSalesChannelsParams extends extendedFindParamsMixin({
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
}) {
|
||||
/**
|
||||
* ID to filter sales channels by.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
id?: string
|
||||
|
||||
/**
|
||||
* Name to filter sales channels by.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string
|
||||
|
||||
/**
|
||||
* Description to filter sales channels by.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* Date filters to apply on sales channels' `created_at` field.
|
||||
*/
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OperatorMapValidator)
|
||||
created_at?: OperatorMap<string>
|
||||
|
||||
/**
|
||||
* Date filters to apply on sales channels' `updated_at` field.
|
||||
*/
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OperatorMapValidator)
|
||||
updated_at?: OperatorMap<string>
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetSalesChannelsParams)
|
||||
$and?: AdminGetSalesChannelsParams[]
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => AdminGetSalesChannelsParams)
|
||||
$or?: AdminGetSalesChannelsParams[]
|
||||
}
|
||||
|
||||
export class AdminPostSalesChannelsReq {
|
||||
@IsString()
|
||||
name: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description: string
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
is_disabled?: boolean
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export class AdminPostSalesChannelsSalesChannelReq {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
is_disabled?: boolean
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { adminPricingRoutesMiddlewares } from "./admin/pricing/middlewares"
|
||||
import { adminProductRoutesMiddlewares } from "./admin/products/middlewares"
|
||||
import { adminPromotionRoutesMiddlewares } from "./admin/promotions/middlewares"
|
||||
import { adminRegionRoutesMiddlewares } from "./admin/regions/middlewares"
|
||||
import { adminSalesChannelRoutesMiddlewares } from "./admin/sales-channels/middlewares"
|
||||
import { adminStoreRoutesMiddlewares } from "./admin/stores/middlewares"
|
||||
import { adminTaxRateRoutesMiddlewares } from "./admin/tax-rates/middlewares"
|
||||
import { adminTaxRegionRoutesMiddlewares } from "./admin/tax-regions/middlewares"
|
||||
@@ -55,5 +56,6 @@ export const config: MiddlewaresConfig = {
|
||||
...adminCollectionRoutesMiddlewares,
|
||||
...adminPricingRoutesMiddlewares,
|
||||
...adminFulfillmentRoutesMiddlewares,
|
||||
...adminSalesChannelRoutesMiddlewares,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
|
||||
import { initialize } from "../../../src"
|
||||
|
||||
import { DB_URL, MikroOrmWrapper } from "../../utils"
|
||||
import { createSalesChannels } from "../../__fixtures__"
|
||||
import { DB_URL, MikroOrmWrapper } from "../../utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -84,13 +84,10 @@ describe("Sales Channel Service", () => {
|
||||
const id = "channel-2"
|
||||
|
||||
it("should update the name of the SalesChannel successfully", async () => {
|
||||
await service.update([
|
||||
{
|
||||
id,
|
||||
name: "Update name 2",
|
||||
is_disabled: true,
|
||||
},
|
||||
])
|
||||
await service.update(id, {
|
||||
name: "Update name 2",
|
||||
is_disabled: true,
|
||||
})
|
||||
|
||||
const channel = await service.retrieve(id)
|
||||
|
||||
@@ -102,11 +99,9 @@ describe("Sales Channel Service", () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.update([
|
||||
{
|
||||
id: "does-not-exist",
|
||||
},
|
||||
])
|
||||
await service.update("does-not-exist", {
|
||||
name: "does-not-exist",
|
||||
})
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
@@ -43,6 +43,15 @@
|
||||
"default": "false",
|
||||
"mappedType": "boolean"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"unsigned": false,
|
||||
"autoincrement": false,
|
||||
"primary": false,
|
||||
"nullable": true,
|
||||
"mappedType": "json"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamptz",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Migration } from "@mikro-orm/migrations"
|
||||
export class Migration20240115152146 extends Migration {
|
||||
async up(): Promise<void> {
|
||||
this.addSql(
|
||||
'create table if not exists "sales_channel" ("id" text not null, "name" text not null, "description" text null, "is_disabled" boolean not null default false, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "sales_channel_pkey" primary key ("id"));'
|
||||
'create table if not exists "sales_channel" ("id" text not null, "name" text not null, "description" text null, "is_disabled" boolean not null default false, "metadata" jsonb NULL, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "sales_channel_pkey" primary key ("id"));'
|
||||
)
|
||||
this.addSql(
|
||||
'create index "IDX_sales_channel_deleted_at" on "sales_channel" ("deleted_at");'
|
||||
|
||||
@@ -38,6 +38,9 @@ export default class SalesChannel {
|
||||
})
|
||||
created_at: Date
|
||||
|
||||
@Property({ columnType: "jsonb", nullable: true })
|
||||
metadata: Record<string, unknown> | null = null
|
||||
|
||||
@Property({
|
||||
onCreate: () => new Date(),
|
||||
onUpdate: () => new Date(),
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
Context,
|
||||
CreateSalesChannelDTO,
|
||||
DAL,
|
||||
FilterableSalesChannelProps,
|
||||
InternalModuleDeclaration,
|
||||
ISalesChannelModuleService,
|
||||
ModuleJoinerConfig,
|
||||
@@ -9,10 +10,19 @@ import {
|
||||
SalesChannelDTO,
|
||||
UpdateSalesChannelDTO,
|
||||
} from "@medusajs/types"
|
||||
import { MedusaContext, ModulesSdkUtils } from "@medusajs/utils"
|
||||
import {
|
||||
InjectManager,
|
||||
InjectTransactionManager,
|
||||
isString,
|
||||
MedusaContext,
|
||||
ModulesSdkUtils,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
import { SalesChannel } from "@models"
|
||||
|
||||
import { UpsertSalesChannelDTO } from "@medusajs/types"
|
||||
import { UpdateSalesChanneInput } from "@types"
|
||||
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
|
||||
|
||||
type InjectedDependencies = {
|
||||
@@ -51,19 +61,18 @@ export default class SalesChannelModuleService<
|
||||
data: CreateSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
|
||||
async create(
|
||||
data: CreateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
|
||||
@InjectManager("baseRepository_")
|
||||
async create(
|
||||
data: CreateSalesChannelDTO | CreateSalesChannelDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<SalesChannelDTO | SalesChannelDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
|
||||
const result = await this.salesChannelService_.create(input, sharedContext)
|
||||
const result = await this.create_(input, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<SalesChannelDTO[]>(
|
||||
Array.isArray(data) ? result : result[0],
|
||||
@@ -73,23 +82,47 @@ export default class SalesChannelModuleService<
|
||||
)
|
||||
}
|
||||
|
||||
async update(
|
||||
data: UpdateSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async create_(
|
||||
data: CreateSalesChannelDTO[],
|
||||
@MedusaContext() sharedContext: Context
|
||||
): Promise<SalesChannel[]> {
|
||||
return await this.salesChannelService_.create(data, sharedContext)
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
data: UpdateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
|
||||
async update(
|
||||
selector: FilterableSalesChannelProps,
|
||||
data: UpdateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
@InjectManager("baseRepository_")
|
||||
async update(
|
||||
idOrSelector: string | FilterableSalesChannelProps,
|
||||
data: UpdateSalesChannelDTO | UpdateSalesChannelDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<SalesChannelDTO | SalesChannelDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
let normalizedInput: UpdateSalesChanneInput[] = []
|
||||
if (isString(idOrSelector)) {
|
||||
normalizedInput = [{ id: idOrSelector, ...data }]
|
||||
} else {
|
||||
const channels = await this.salesChannelService_.list(
|
||||
idOrSelector,
|
||||
{},
|
||||
sharedContext
|
||||
)
|
||||
|
||||
const result = await this.salesChannelService_.update(input, sharedContext)
|
||||
normalizedInput = channels.map((channel) => ({
|
||||
id: channel.id,
|
||||
...data,
|
||||
}))
|
||||
}
|
||||
|
||||
const result = await this.update_(normalizedInput, sharedContext)
|
||||
|
||||
return await this.baseRepository_.serialize<SalesChannelDTO[]>(
|
||||
Array.isArray(data) ? result : result[0],
|
||||
@@ -98,4 +131,45 @@ export default class SalesChannelModuleService<
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async update_(data: UpdateSalesChannelDTO[], sharedContext: Context) {
|
||||
return await this.salesChannelService_.update(data, sharedContext)
|
||||
}
|
||||
|
||||
async upsert(
|
||||
data: UpsertSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
async upsert(
|
||||
data: UpsertSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
@InjectTransactionManager("baseRepository_")
|
||||
async upsert(
|
||||
data: UpsertSalesChannelDTO | UpsertSalesChannelDTO[],
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
): Promise<SalesChannelDTO | SalesChannelDTO[]> {
|
||||
const input = Array.isArray(data) ? data : [data]
|
||||
const forUpdate = input.filter(
|
||||
(channel): channel is UpdateSalesChannelDTO => !!channel.id
|
||||
)
|
||||
const forCreate = input.filter(
|
||||
(channel): channel is CreateSalesChannelDTO => !channel.id
|
||||
)
|
||||
|
||||
const operations: Promise<SalesChannel[]>[] = []
|
||||
|
||||
if (forCreate.length) {
|
||||
operations.push(this.create_(forCreate, sharedContext))
|
||||
}
|
||||
if (forUpdate.length) {
|
||||
operations.push(this.update_(forUpdate, sharedContext))
|
||||
}
|
||||
|
||||
const result = (await promiseAll(operations)).flat()
|
||||
return await this.baseRepository_.serialize<
|
||||
SalesChannelDTO[] | SalesChannelDTO
|
||||
>(Array.isArray(data) ? result : result[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Logger } from "@medusajs/types"
|
||||
import { Logger, UpdateSalesChannelDTO } from "@medusajs/types"
|
||||
|
||||
export type InitializeModuleInjectableDependencies = {
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
export type UpdateSalesChanneInput = UpdateSalesChannelDTO & { id: string }
|
||||
@@ -63,12 +63,12 @@ export interface FilterableSalesChannelProps
|
||||
/**
|
||||
* The IDs to filter the sales channel by.
|
||||
*/
|
||||
id?: string[]
|
||||
id?: string | string[]
|
||||
|
||||
/**
|
||||
* Filter sales channels by their names.
|
||||
*/
|
||||
name?: string[]
|
||||
name?: string | string[]
|
||||
|
||||
/**
|
||||
* Filter sales channels by whether they're disabled.
|
||||
|
||||
@@ -22,11 +22,6 @@ export interface CreateSalesChannelDTO {
|
||||
* The attributes to update in the sales channel.
|
||||
*/
|
||||
export interface UpdateSalesChannelDTO {
|
||||
/**
|
||||
* The ID of the sales channel.
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* The name of the sales channel.
|
||||
*/
|
||||
@@ -41,4 +36,34 @@ export interface UpdateSalesChannelDTO {
|
||||
* Whether the sales channel is disabled.
|
||||
*/
|
||||
is_disabled?: boolean
|
||||
/**
|
||||
* Holds custom data in key-value pairs.
|
||||
*/
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface UpsertSalesChannelDTO {
|
||||
/**
|
||||
* The ID of the sales channel.
|
||||
*/
|
||||
id?: string
|
||||
|
||||
/**
|
||||
* The name of the sales channel.
|
||||
*/
|
||||
name?: string
|
||||
|
||||
/**
|
||||
* The description of the sales channel.
|
||||
*/
|
||||
description?: string | null
|
||||
|
||||
/**
|
||||
* Whether the sales channel is disabled.
|
||||
*/
|
||||
is_disabled?: boolean
|
||||
/**
|
||||
* Holds custom data in key-value pairs.
|
||||
*/
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
import { FilterableSalesChannelProps, SalesChannelDTO } from "./common"
|
||||
import { FindConfig } from "../common"
|
||||
import { Context } from "../shared-context"
|
||||
import { RestoreReturn, SoftDeleteReturn } from "../dal"
|
||||
import { CreateSalesChannelDTO, UpdateSalesChannelDTO } from "./mutations"
|
||||
import { IModuleService } from "../modules-sdk"
|
||||
import { Context } from "../shared-context"
|
||||
import { FilterableSalesChannelProps, SalesChannelDTO } from "./common"
|
||||
import { CreateSalesChannelDTO, UpdateSalesChannelDTO, UpsertSalesChannelDTO } from "./mutations"
|
||||
|
||||
/**
|
||||
* The main service interface for the sales channel module.
|
||||
@@ -39,35 +39,25 @@ export interface ISalesChannelModuleService extends IModuleService {
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
|
||||
/**
|
||||
* This method updates existing sales channels.
|
||||
*
|
||||
* @param {UpdateSalesChannelDTO[]} data - The attributes to update in each sales channel.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<SalesChannelDTO[]>} The updated sales channels.
|
||||
*
|
||||
* @example
|
||||
* {example-code}
|
||||
*/
|
||||
update(
|
||||
data: UpdateSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
|
||||
/**
|
||||
* This method updates an existing sales channel.
|
||||
*
|
||||
* @param {UpdateSalesChannelDTO} data - The attributes to update in the sales channel.
|
||||
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
|
||||
* @returns {Promise<SalesChannelDTO>} The updated sales channel.
|
||||
*
|
||||
* @example
|
||||
* {example-code}
|
||||
*/
|
||||
update(
|
||||
channelId: string,
|
||||
data: UpdateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
update(
|
||||
selector: FilterableSalesChannelProps,
|
||||
data: UpdateSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
|
||||
upsert(
|
||||
data: UpsertSalesChannelDTO,
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO>
|
||||
upsert(
|
||||
data: UpsertSalesChannelDTO[],
|
||||
sharedContext?: Context
|
||||
): Promise<SalesChannelDTO[]>
|
||||
|
||||
/**
|
||||
* This method deletes sales channels by their IDs.
|
||||
|
||||
Reference in New Issue
Block a user