Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { AuthenticatedMedusaRequest, MedusaResponse } from '@medusajs/framework'
import { ContainerRegistrationKeys } from '@medusajs/framework/utils'

import { filterSellerProductsByCollection } from '../../utils'
import { batchLinkProductsToCollectionWorkflow } from "@medusajs/core-flows"
import { LinkMethodRequest } from "@medusajs/framework/types"

export const GET = async (
req: AuthenticatedMedusaRequest,
Expand Down Expand Up @@ -32,3 +34,135 @@ export const GET = async (
limit: req.queryConfig.pagination?.take || 10
})
}

/**
* @oas [post] /vendor/product-collections/{id}/products
* summary: Manage Vendor Products of a Collection
* x-sidebar-summary: Manage Vendor Products of a Collection
* description: Manage the vendor products of a collection by adding or removing them from the collection.
* x-authenticated: true
* parameters:
* - name: id
* in: path
* description: The collection's ID.
* required: true
* schema:
* type: string
* - name: fields
* in: query
* description: Comma-separated fields that should be included in the returned data. if a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default
* fields. without prefix it will replace the entire default fields.
* required: false
* schema:
* type: string
* title: fields
* description: Comma-separated fields that should be included in the returned data. if a field is prefixed with `+` it will be added to the default fields, using `-` will remove it from the default
* fields. without prefix it will replace the entire default fields.
* externalDocs:
* url: "#select-fields-and-relations"
* security:
* - api_token: []
* - cookie_auth: []
* - jwt_token: []
* requestBody:
* content:
* application/json:
* schema:
* type: object
* description: The products to add or remove.
* properties:
* add:
* type: array
* description: The products to add to the collection.
* items:
* type: string
* title: add
* description: A product's ID.
* remove:
* type: array
* description: The products to remove from the collection.
* items:
* type: string
* title: remove
* description: A product's ID.
* tags:
* - Vendor Product Collections
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* products:
* type: array
* items:
* $ref: "#/components/schemas/VendorProduct"
* count:
* type: integer
* description: The total number of items available
* offset:
* type: integer
* description: The number of items skipped before these items
* limit:
* type: integer
* description: The number of items per page
* "400":
* $ref: "#/components/responses/400_error"
* "401":
* $ref: "#/components/responses/unauthorized"
* "404":
* $ref: "#/components/responses/not_found_error"
* "409":
* $ref: "#/components/responses/invalid_state_error"
* "422":
* $ref: "#/components/responses/invalid_request_error"
* "500":
* $ref: "#/components/responses/500_error"
* x-workflow: batchLinkProductsToCollectionWorkflow
* x-events: []
*
*/

export const POST = async (
req: AuthenticatedMedusaRequest<LinkMethodRequest>,
res: MedusaResponse
) => {
const id = req.params.id
const { add = [], remove = [] } = req.validatedBody

const workflow = batchLinkProductsToCollectionWorkflow(req.scope)
await workflow.run({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like that

 await batchLinkProductsToCollectionWorkflow.run({
    container: req.scope
    input: {
      id,
      add,
      remove,
    },
  })

input: {
id,
add,
remove,
},
})

const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)

const { productIds, count } = await filterSellerProductsByCollection(
req.scope,
req.params.id,
req.filterableFields.seller_id as string,
req.queryConfig.pagination?.skip || 0,
req.queryConfig.pagination?.take || 10
)

const { data: products } = await query.graph({
entity: 'product',
fields: req.queryConfig.fields,
filters: {
id: productIds
}
})

res.status(200).json({
products,
count,
offset: req.queryConfig.pagination?.skip || 0,
limit: req.queryConfig.pagination?.take || 10
})
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MiddlewareRoute, validateAndTransformQuery } from '@medusajs/framework'
import { MiddlewareRoute, validateAndTransformBody, validateAndTransformQuery } from '@medusajs/framework'

import { filterBySellerId } from '../../../shared/infra/http/middlewares'
import { checkResourcesOwnershipByResourceBatch, filterBySellerId } from '../../../shared/infra/http/middlewares'
import {
vendorProductCollectionQueryConfig,
vendorProductCollectionsProductsQueryConfig
Expand All @@ -9,6 +9,8 @@ import {
VendorGetProductCollectionsParams,
VendorGetProductCollectionsProductsParams
} from './validators'
import { createLinkBody } from '@medusajs/medusa/api/utils/validators'
import sellerProductLink from "../../../links/seller-product";

export const vendorProductCollectionsMiddlewares: MiddlewareRoute[] = [
{
Expand Down Expand Up @@ -41,5 +43,21 @@ export const vendorProductCollectionsMiddlewares: MiddlewareRoute[] = [
),
filterBySellerId()
]
}
},
{
method: ["POST"],
matcher: "/vendor/product-collections/:id/products",
middlewares: [
validateAndTransformQuery(
VendorGetProductCollectionsProductsParams,
vendorProductCollectionsProductsQueryConfig.list
),
validateAndTransformBody(createLinkBody()),
filterBySellerId(),
checkResourcesOwnershipByResourceBatch({
entryPoint: sellerProductLink.entryPoint,
filterField: 'product_id',
}),
],
},
]
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ import {
ContainerRegistrationKeys,
MedusaError
} from '@medusajs/framework/utils'
import { LinkMethodRequest } from '@medusajs/framework/types'

type CheckResourceOwnershipByResourceIdOptions<Body> = {
entryPoint: string
filterField?: string
resourceId?: (req: AuthenticatedMedusaRequest<Body>) => string
}

type CheckResourcesOwnershipByResourceBatchOptions<Body> = {
entryPoint: string
filterField?: string
resourceIds?: (req: AuthenticatedMedusaRequest<Body>) => { add: string[], remove: string[] }
}

/**
* Middleware that verifies if the authenticated member owns/has access to the requested resource.
* This is done by checking if the member's seller ID matches the resource's seller ID.
Expand Down Expand Up @@ -93,3 +100,55 @@ export const checkResourceOwnershipByResourceId = <Body>({
next()
}
}

export const checkResourcesOwnershipByResourceBatch = <Body>({
entryPoint,
filterField = 'id',
resourceIds = (req) => ({ add: req.validatedBody.add || [], remove: req.validatedBody.remove || [] })
}: CheckResourcesOwnershipByResourceBatchOptions<LinkMethodRequest>) => {
return async (
req: AuthenticatedMedusaRequest<LinkMethodRequest>,
res: MedusaResponse,
next: NextFunction
) => {
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)

const {
data: [member]
} = await query.graph(
{
entity: 'member',
fields: ['seller.id'],
filters: {
id: req.auth_context.actor_id
}
},
{ throwIfKeyNotFound: true }
)

const { add, remove } = resourceIds(req)
const allResourceIds = add.concat(remove)


const {
data: resources
} = await query.graph({
entity: entryPoint,
fields: ['seller_id', filterField],
filters: {
[filterField]: allResourceIds,
seller_id: member.seller.id
}
})

if (!resources.some((resource) => allResourceIds.includes(resource[filterField]))) {
res.status(404).json({
message: `You are not allowed to perform this action`,
type: MedusaError.Types.NOT_FOUND
})
return
}

next()
}
}