feat(medusa): List batch jobs + Introduce composable handler pattern (#1541)
This commit is contained in:
1 parent
f0ecef6b9a
commit
4489b75f5a
15 files changed
+521
-10
No files matched your search
@@ -0,0 +1,20 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import BatchJobService from "../../../services/batch-job"
|
||||
|
||||
export async function canAccessBatchJob(req, res, next) {
|
||||
const id = req.params.id
|
||||
const batchJobService: BatchJobService = req.scope.resolve("batchJobService")
|
||||
const batch_job = req.batch_job ?? (await batchJobService.retrieve(id))
|
||||
|
||||
const userId = req.user.id ?? req.user.userId
|
||||
if (batch_job.created_by !== userId) {
|
||||
return next(
|
||||
new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Cannot access a batch job that does not belong to the logged in user"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import BatchJobService from "../../../services/batch-job"
|
||||
|
||||
export async function getRequestedBatchJob(req, res, next) {
|
||||
const id = req.params.id
|
||||
const batchJobService: BatchJobService = req.scope.resolve("batchJobService")
|
||||
|
||||
try {
|
||||
req.batch_job = await batchJobService.retrieve(id)
|
||||
} catch (error) {
|
||||
return next(error)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
@@ -3,6 +3,9 @@ import { default as authenticate } from "./authenticate"
|
||||
import { default as normalizeQuery } from "./normalized-query"
|
||||
import { default as wrap } from "./await-middleware"
|
||||
|
||||
export { getRequestedBatchJob } from "./batch-job/get-requested-batch-job"
|
||||
export { canAccessBatchJob } from "./batch-job/can-access-batch-job"
|
||||
|
||||
export default {
|
||||
authenticate,
|
||||
authenticateCustomer,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Router } from "express"
|
||||
import { BatchJob } from "../../../.."
|
||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
export default (app) => {
|
||||
const route = Router()
|
||||
|
||||
app.use("/batch", route)
|
||||
|
||||
route.get(
|
||||
"/",
|
||||
middlewares.normalizeQuery(),
|
||||
middlewares.wrap(require("./list-batch-jobs").default)
|
||||
)
|
||||
return app
|
||||
}
|
||||
|
||||
export type AdminBatchJobRes = {
|
||||
batch_job: BatchJob
|
||||
}
|
||||
|
||||
export type AdminBatchJobDeleteRes = DeleteResponse
|
||||
|
||||
export type AdminBatchJobListRes = PaginatedResponse & {
|
||||
batch_jobs: BatchJob[]
|
||||
}
|
||||
|
||||
export const defaultAdminBatchFields = [
|
||||
"id",
|
||||
"status",
|
||||
"type",
|
||||
"context",
|
||||
"result",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export * from "./list-batch-jobs"
|
||||
@@ -0,0 +1,142 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { pickBy, omit, identity } from "lodash"
|
||||
import { defaultAdminBatchFields } from "."
|
||||
import BatchJobService from "../../../../services/batch-job"
|
||||
import { BatchJob } from "../../../../models"
|
||||
import { BatchJobStatus } from "../../../../types/batch-job"
|
||||
import { DateComparisonOperator } from "../../../../types/common"
|
||||
import { IsType } from "../../../../utils/validators/is-type"
|
||||
import { getListConfig } from "../../../../utils/get-query-config"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
* @oas [get] /batch
|
||||
* operationId: "GetBatch"
|
||||
* summary: "List Batch Jobs"
|
||||
* description: "Retrieve a list of Batch Jobs."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (query) limit {string} The number of collections to return.
|
||||
* - (query) offset {string} The offset of collections to return.
|
||||
* - (query) type {string | string[]} Filter by the batch type
|
||||
* - (query) status {string} Filter by the status of the batch operation
|
||||
* - (query) order {string} Order used when retrieving batch jobs
|
||||
* - (query) expand[] {string} (Comma separated) Which fields should be expanded in each order of the result.
|
||||
* - (query) fields[] {string} (Comma separated) Which fields should be included in each order of the result.
|
||||
* - (query) deleted_at {DateComparisonOperator} Date comparison for when resulting collections was deleted, i.e. less than, greater than etc.
|
||||
* - (query) created_at {DateComparisonOperator} Date comparison for when resulting collections was created, i.e. less than, greater than etc.
|
||||
* - (query) updated_at {DateComparisonOperator} Date comparison for when resulting collections was updated, i.e. less than, greater than etc.
|
||||
* tags:
|
||||
* - Batch Job
|
||||
* responses:
|
||||
* "200":
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* batch_job:
|
||||
* $ref: "#/components/schemas/batch_job"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { fields, expand, order, limit, offset, ...filterableFields } =
|
||||
await validator(AdminGetBatchParams, req.query)
|
||||
|
||||
const batchService: BatchJobService = req.scope.resolve("batchJobService")
|
||||
|
||||
let orderBy: { [k: symbol]: "DESC" | "ASC" } | undefined
|
||||
if (typeof order !== "undefined") {
|
||||
if (order.startsWith("-")) {
|
||||
const [, field] = order.split("-")
|
||||
orderBy = { [field]: "DESC" }
|
||||
} else {
|
||||
orderBy = { [order]: "ASC" }
|
||||
}
|
||||
}
|
||||
|
||||
const listConfig = getListConfig<BatchJob>(
|
||||
defaultAdminBatchFields as (keyof BatchJob)[],
|
||||
[],
|
||||
fields?.split(",") as (keyof BatchJob)[],
|
||||
expand?.split(","),
|
||||
limit,
|
||||
offset,
|
||||
orderBy
|
||||
)
|
||||
|
||||
const created_by: string = req.user.id || req.user.userId
|
||||
|
||||
const [jobs, count] = await batchService.listAndCount(
|
||||
pickBy(
|
||||
{ created_by, ...filterableFields },
|
||||
(val) => typeof val !== "undefined"
|
||||
),
|
||||
listConfig
|
||||
)
|
||||
|
||||
res.status(200).json({
|
||||
batch_jobs: jobs,
|
||||
count,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
})
|
||||
}
|
||||
|
||||
export class AdminGetBatchPaginationParams {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
limit = 10
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
offset = 0
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
expand?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
fields?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
order?: string
|
||||
}
|
||||
|
||||
export class AdminGetBatchParams extends AdminGetBatchPaginationParams {
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsType([String, [String]])
|
||||
id?: string | string[]
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsEnum(BatchJobStatus, { each: true })
|
||||
status?: BatchJobStatus[]
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
type?: string[]
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => DateComparisonOperator)
|
||||
created_at?: DateComparisonOperator
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => DateComparisonOperator)
|
||||
updated_at?: DateComparisonOperator
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import noteRoutes from "./notes"
|
||||
import notificationRoutes from "./notifications"
|
||||
import orderRoutes from "./orders"
|
||||
import priceListRoutes from "./price-lists"
|
||||
import batchRoutes from "./batch"
|
||||
import productTagRoutes from "./product-tags"
|
||||
import productTypesRoutes from "./product-types"
|
||||
import productRoutes from "./products"
|
||||
@@ -63,6 +64,7 @@ export default (app, container, config) => {
|
||||
|
||||
appRoutes(route)
|
||||
productRoutes(route)
|
||||
batchRoutes(route)
|
||||
userRoutes(route)
|
||||
regionRoutes(route)
|
||||
shippingOptionRoutes(route)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from "./api"
|
||||
export * from "./interfaces"
|
||||
export * from "./types/price-list"
|
||||
export * from "./types/batch-job"
|
||||
export * from "./models"
|
||||
export * from "./services"
|
||||
@@ -1,11 +1,5 @@
|
||||
export * from "./shipping-tax-rate"
|
||||
export * from "./product-tax-rate"
|
||||
export * from "./product-type-tax-rate"
|
||||
export * from "./tax-rate"
|
||||
export * from "./shipping-method-tax-line"
|
||||
export * from "./line-item-tax-line"
|
||||
export * from "./line-item-adjustment"
|
||||
export * from "./address"
|
||||
export * from "./batch-job"
|
||||
export * from "./cart"
|
||||
export * from "./claim-image"
|
||||
export * from "./claim-item"
|
||||
@@ -17,12 +11,12 @@ export * from "./custom-shipping-option"
|
||||
export * from "./customer"
|
||||
export * from "./customer-group"
|
||||
export * from "./discount"
|
||||
export * from "./discount-condition"
|
||||
export * from "./discount-condition-customer-group"
|
||||
export * from "./discount-condition-product"
|
||||
export * from "./discount-condition-product-collection"
|
||||
export * from "./discount-condition-product-tag"
|
||||
export * from "./discount-condition-product-type"
|
||||
export * from "./discount-condition-product"
|
||||
export * from "./discount-condition"
|
||||
export * from "./discount-rule"
|
||||
export * from "./draft-order"
|
||||
export * from "./fulfillment"
|
||||
@@ -34,6 +28,8 @@ export * from "./idempotency-key"
|
||||
export * from "./image"
|
||||
export * from "./invite"
|
||||
export * from "./line-item"
|
||||
export * from "./line-item-adjustment"
|
||||
export * from "./line-item-tax-line"
|
||||
export * from "./money-amount"
|
||||
export * from "./note"
|
||||
export * from "./notification"
|
||||
@@ -48,7 +44,9 @@ export * from "./product-collection"
|
||||
export * from "./product-option"
|
||||
export * from "./product-option-value"
|
||||
export * from "./product-tag"
|
||||
export * from "./product-tax-rate"
|
||||
export * from "./product-type"
|
||||
export * from "./product-type-tax-rate"
|
||||
export * from "./product-variant"
|
||||
export * from "./refund"
|
||||
export * from "./region"
|
||||
@@ -56,11 +54,14 @@ export * from "./return"
|
||||
export * from "./return-item"
|
||||
export * from "./return-reason"
|
||||
export * from "./shipping-method"
|
||||
export * from "./shipping-method-tax-line"
|
||||
export * from "./shipping-option"
|
||||
export * from "./shipping-option-requirement"
|
||||
export * from "./shipping-profile"
|
||||
export * from "./shipping-tax-rate"
|
||||
export * from "./staged-job"
|
||||
export * from "./store"
|
||||
export * from "./swap"
|
||||
export * from "./user"
|
||||
export * from "./tax-provider"
|
||||
export * from "./tax-rate"
|
||||
export * from "./user"
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { BatchJob } from "../models"
|
||||
|
||||
@EntityRepository(BatchJob)
|
||||
export class BatchJobRepository extends Repository<BatchJob> {}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
import { BatchJob } from "../models"
|
||||
import { BatchJobRepository } from "../repositories/batch-job"
|
||||
import { FilterableBatchJobProps } from "../types/batch-job"
|
||||
import { FindConfig } from "../types/common"
|
||||
import { TransactionBaseService } from "../interfaces"
|
||||
import { buildQuery, validateId } from "../utils"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
batchJobRepository: typeof BatchJobRepository
|
||||
}
|
||||
|
||||
class BatchJobService extends TransactionBaseService<BatchJobService> {
|
||||
protected manager_: EntityManager
|
||||
protected transactionManager_: EntityManager | undefined
|
||||
protected readonly batchJobRepository_: typeof BatchJobRepository
|
||||
|
||||
static readonly Events = {
|
||||
CREATED: "batch.created",
|
||||
UPDATED: "batch.updated",
|
||||
CANCELED: "batch.canceled",
|
||||
}
|
||||
|
||||
constructor({ manager, batchJobRepository }: InjectedDependencies) {
|
||||
super({ manager, batchJobRepository })
|
||||
|
||||
this.manager_ = manager
|
||||
this.batchJobRepository_ = batchJobRepository
|
||||
}
|
||||
|
||||
async retrieve(
|
||||
batchJobId: string,
|
||||
config: FindConfig<BatchJob> = {}
|
||||
): Promise<BatchJob | never> {
|
||||
return await this.atomicPhase_(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const batchJobRepo = transactionManager.getCustomRepository(
|
||||
this.batchJobRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery<BatchJob>({ id: batchJobId }, config)
|
||||
const batchJob = await batchJobRepo.findOne(query)
|
||||
|
||||
if (!batchJob) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Batch job with id ${batchJobId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
return batchJob
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
selector: FilterableBatchJobProps = {},
|
||||
config: FindConfig<BatchJob> = { skip: 0, take: 20 }
|
||||
): Promise<[BatchJob[], number]> {
|
||||
return await this.atomicPhase_(
|
||||
async (manager: EntityManager): Promise<[BatchJob[], number]> => {
|
||||
const batchJobRepo = manager.getCustomRepository(
|
||||
this.batchJobRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery(selector, config)
|
||||
return await batchJobRepo.findAndCount(query)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default BatchJobService
|
||||
@@ -1,6 +1,46 @@
|
||||
import { Type } from "class-transformer"
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { IsType } from "../utils/validators/is-type"
|
||||
import { DateComparisonOperator } from "./common"
|
||||
|
||||
export enum BatchJobStatus {
|
||||
CREATED = "created",
|
||||
PROCESSING = "processing",
|
||||
AWAITING_CONFIRMATION = "awaiting_confirmation",
|
||||
COMPLETED = "completed",
|
||||
}
|
||||
|
||||
export class FilterableBatchJobProps {
|
||||
@IsOptional()
|
||||
@IsType([String, [String]])
|
||||
id?: string | string[]
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(BatchJobStatus, { each: true })
|
||||
status?: BatchJobStatus[]
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
type?: string[]
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsType([String, [String]])
|
||||
created_by?: string | string[]
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => DateComparisonOperator)
|
||||
created_at?: DateComparisonOperator
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => DateComparisonOperator)
|
||||
updated_at?: DateComparisonOperator
|
||||
}
|
||||
Reference in new issue
Block a user