Skip to main content
Version: 0.13.0 (Previous)

🛣️ Product routes

Product routes are SDK composers, not Express middleware. Each feature supplies its schema; details document method, path, validator order, pipeline, and response status.

Inventory

RouteMethod / protocolPathSchemaValidatorsSuccess status
createProductRoutePOST / HTTP/productscreateProductSchemaisAuthenticated(), checkIdentityType(['admin'])200
findProductsRouteGET / HTTP/productsfindProductsSchemaNone200
createProductBatchRoutePOST / HTTP/products/batchcreateProductBatchSchemaisAuthenticated(), checkIdentityType(['admin'])200
updateProductBatchRoutePATCH / HTTP/products/batchupdateProductBatchSchemaisAuthenticated(), checkIdentityType(['admin'])200
deleteProductBatchRouteDELETE / HTTP/products/batchdeleteProductBatchSchemaisAuthenticated(), checkIdentityType(['admin'])204
getProductRouteGET / HTTP/products/:productIdgetProductSchemaNone200
updateProductRoutePATCH / HTTP/products/:productIdupdateProductSchemaisAuthenticated(), checkIdentityType(['admin'])200
deleteProductRouteDELETE / HTTP/products/:productIddeleteProductSchemaisAuthenticated(), checkIdentityType(['admin'])204
copyProductRoutePOST / HTTP/products/:productId/copycopyProductSchemaisAuthenticated(), checkIdentityType(['admin'])200
copyProductBatchRoutePOST / HTTP/products/batch/copycopyProductBatchSchemaisAuthenticated(), checkIdentityType(['admin'])200
getProductImageUploadUrlRouteGET / HTTP/products/:productId/image-upload-urlgetSignedImageUploadUrlSchemaisAuthenticated(), checkIdentityType(['admin'])200
createProductImageRoutePOST / HTTP/products/:productId/imagescreateProductImageSchemaisAuthenticated(), checkIdentityType(['admin'])201
deleteProductImageRouteDELETE / HTTP/products/:productId/images/:imageIddeleteProductImageSchemaisAuthenticated(), checkIdentityType(['admin'])204
createProductVariantRoutePOST / HTTP/products/:productId/variantscreateProductVariantSchemaisAuthenticated(), checkIdentityType(['admin'])201
getProductVariantRouteGET / HTTP/products/:productId/variants/:productVariantIdgetProductVariantSchemaisAuthenticated()200
updateProductVariantRoutePATCH / HTTP/products/:productId/variants/:productVariantIdupdateProductVariantSchemaisAuthenticated(), checkIdentityType(['admin'])200
deleteProductVariantRouteDELETE / HTTP/products/:productId/variants/:productVariantIddeleteProductVariantSchemaisAuthenticated(), checkIdentityType(['admin'])204
findProductVariantsRouteGET / HTTP/products/:productId/variantsfindProductVariantsSchemaNone200
createProductVariantBulkRoutePOST / HTTP/product/:productId/variants/bulkcreateProductVariantBulkSchemaisAuthenticated(), checkIdentityType(['admin'])201
updateProductVariantBulkRoutePATCH / HTTP/product/:productId/variants/bulkupdateProductVariantBulkSchemaisAuthenticated(), checkIdentityType(['admin'])200
deleteProductVariantBulkRoutePOST / HTTP/product/:productId/variants/bulk-deletedeleteProductVariantBulkSchemaisAuthenticated(), checkIdentityType(['admin'])204
findProductsByOrganizationIdRouteGET / HTTP/products/organizations/:organizationIdfindByOrganizationIdSchemaisAuthenticated(), hasOrgRole(['owner', 'admin', 'member'], ['params', 'requestParams', 'organizationId'])200
getProductLikersRouteGET / HTTP/products/:productId/likersgetProductLikersSchemaisAuthenticated(), checkIdentityType(['admin'])200

Details

createProductRoute

Implementation

Endpoint: POST /products

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: createProductSchema defines the attached body, path, and/or query contract.

Pipeline: createProduct

Success: The final orThrow success map returns ordinary route data without a statusCode descriptor; this composer does not set a success status.

Failure: ProductNotFoundBlockError404, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const createProductRoute = withRoute({
handler: compose(
withLogging(createProduct),

flatMapAsync(
applyPayloadArgs(
getProductByIdBlock,
[
['context', 'db', 'products'],
['context', 'data', 'productId'],
],
'product'
)
),

flatMapAsync(
applyPayloadArgs(
normalizeProduct,
[['context', 'data', 'product']],
'normalizedProduct'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
],
[['context', 'data', 'normalizedProduct']]
)
)
),
method: 'POST',
path: '/products',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

findProductsRoute

Implementation

Endpoint: GET /products

Access: Public; the source supplies no route validators.

Request: findProductsSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: The final orThrow success map returns ordinary route data without a statusCode descriptor; this composer does not set a success status.

Failure: ProductNotFoundBlockError404, FileStorageServiceError500, ProductBlockError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const findProductsRoute = withRoute({
handler: compose(
withPagination(
withLogging(
applyPayloadArgs(
findProducts,
[
['context', 'db', 'products'],
['params', 'requestQuery'],
],
'paginatedProducts'
)
)
),
flatMapAsync(
applyPayloadArgs(
normalizeImagesOfProducts,
[
['context', 'fileStorageDriver'],
['context', 'data', 'paginatedProducts', 'data'],
],
'productsWithNormalizedImages'
)
),
flatMapAsync(
applyPayloadArgs(
normalizeProducts,
[['context', 'data', 'productsWithNormalizedImages']],
'normalizedProducts'
)
),
flatMapAsync(
applyPayloadArgs(
applySpec({ data: nthArg(0), metadata: { pagination: nthArg(1) } }),
[
['context', 'data', 'normalizedProducts'],
['context', 'data', 'paginatedProducts', 'metadata'],
],
'normalizedProductsWithPagination'
)
),
lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
[ProductBlockError, 500],
],
[['context', 'data', 'normalizedProductsWithPagination']]
)
)
),
method: 'GET',
path: '/products',
validators: [],
});

createProductBatchRoute

Implementation

Endpoint: POST /products/batch

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: createProductBatchSchema defines the attached body, path, and/or query contract.

Pipeline: createProductBatchgetProductsByIds

Success: The final orThrow success map returns ordinary route data without a statusCode descriptor; this composer does not set a success status.

Failure: ProductNotFoundBlockError404, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const createProductBatchRoute = withRoute({
handler: compose(
withLogging(createProductBatch),
flatMapAsync(withLogging(getProductsByIds)),

flatMapAsync(
applyPayloadArgs(
normalizeProducts,
[['context', 'data', 'products']],
'normalizedProducts'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
],
[['context', 'data', 'normalizedProducts']]
)
)
),
method: 'POST',
path: '/products/batch',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

updateProductBatchRoute

Implementation

Endpoint: PATCH /products/batch

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: updateProductBatchSchema defines the attached body, path, and/or query contract.

Pipeline: updateProductBatchgetProductsByIds

Success: The final orThrow success map returns ordinary route data without a statusCode descriptor; this composer does not set a success status.

Failure: ProductNotFoundBlockError404, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const updateProductBatchRoute = withRoute({
handler: compose(
withLogging(updateProductBatch),
flatMapAsync(withLogging(getProductsByIds)),

flatMapAsync(
applyPayloadArgs(
normalizeImagesOfProducts,
[
['context', 'fileStorageDriver'],
['context', 'data', 'products'],
],
'productsWithNormalizedImages'
)
),

flatMapAsync(
applyPayloadArgs(
normalizeProducts,
[['context', 'data', 'productsWithNormalizedImages']],
'normalizedProducts'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
],
[['context', 'data', 'normalizedProducts']]
)
)
),
method: 'PATCH',
path: '/products/batch',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

deleteProductBatchRoute

Implementation

Endpoint: DELETE /products/batch

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: deleteProductBatchSchema defines the attached body, path, and/or query contract.

Pipeline: deleteProductBatchdeleteBatchProductsTerminator

Success: 204 response descriptor from deleteBatchProductsTerminator.

Failure: No route-specific orThrow error mapping. Schema and validator failures are handled by their composed middleware.

View complete source
export const deleteProductBatchRoute = withRoute({
handler: compose(
withLogging(deleteProductBatch),
lift(withLogging(deleteBatchProductsTerminator))
),
method: 'DELETE',
path: '/products/batch',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

getProductRoute

Implementation

Endpoint: GET /products/:productId

Access: Public; the source supplies no route validators.

Request: getProductSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: The final orThrow success map returns ordinary route data without a statusCode descriptor; this composer does not set a success status.

Failure: ProductNotFoundBlockError404, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const getProductRoute = withRoute({
handler: compose(
applyPayloadArgs(
getProductByIdBlock,
[
['context', 'db', 'products'],
['params', 'requestParams', 'productId'],
],
'product'
),

flatMapAsync(
applyPayloadArgs(
normalizeImagesOfProduct,
[
['context', 'fileStorageDriver'],
['context', 'data', 'product'],
],
'productWithNormalizedImages'
)
),

flatMapAsync(
applyPayloadArgs(
normalizeProduct,
[['context', 'data', 'productWithNormalizedImages']],
'normalizedProduct'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
],
[['context', 'data', 'normalizedProduct']]
)
)
),
method: 'GET',
path: '/products/:productId',
validators: [],
});

updateProductRoute

Implementation

Endpoint: PATCH /products/:productId

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: updateProductSchema defines the attached body, path, and/or query contract.

Pipeline: updateProduct

Success: The final orThrow success map returns ordinary route data without a statusCode descriptor; this composer does not set a success status.

Failure: ProductNotFoundBlockError404, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const updateProductRoute = withRoute({
handler: compose(
withLogging(updateProduct),

flatMapAsync(
applyPayloadArgs(
getProductByIdBlock,
[
['context', 'db', 'products'],
['context', 'data', 'updateProductId'],
],
'product'
)
),

flatMapAsync(
applyPayloadArgs(
normalizeImagesOfProduct,
[
['context', 'fileStorageDriver'],
['context', 'data', 'product'],
],
'productWithNormalizedImages'
)
),

flatMapAsync(
applyPayloadArgs(
normalizeProduct,
[['context', 'data', 'productWithNormalizedImages']],
'normalizedProduct'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
],
[['context', 'data', 'normalizedProduct']]
)
)
),
method: 'PATCH',
path: '/products/:productId',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

deleteProductRoute

Implementation

Endpoint: DELETE /products/:productId

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: deleteProductSchema defines the attached body, path, and/or query contract.

Pipeline: deleteProduct

Success: 204 with no response body; source maps [['undefined'], 204].

Failure: ProductNotFoundBlockError404, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const deleteProductRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
deleteImagesOfProduct,
[
['context', 'fileStorageDriver'],
['context', 'db', 'products'],
['params', 'requestParams', 'productId'],
],
'wereProductImagesDeleted'
)
),
flatMapAsync(withLogging(deleteProduct)),
// TODO: when we delete a product, we should also delete all of its product variants
lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
],
[['undefined'], 204]
)
)
),
method: 'DELETE',
path: '/products/:productId',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

copyProductRoute

Implementation

Endpoint: POST /products/:productId/copy

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: copyProductSchema defines the attached body, path, and/or query contract.

Pipeline: copyProduct

Success: 204 with no response body; source maps [['undefined'], 204].

Failure: ProductNotFoundBlockError404, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const copyProductRoute = withRoute({
handler: compose(
withLogging(copyProduct),

flatMapAsync(
applyPayloadArgs(
getProductByIdBlock,
[
['context', 'db', 'products'],
['context', 'data', 'productId'],
],
'product'
)
),

flatMapAsync(
applyPayloadArgs(
normalizeImagesOfProduct,
[
['context', 'fileStorageDriver'],
['context', 'data', 'product'],
],
'productWithNormalizedImages'
)
),

flatMapAsync(
applyPayloadArgs(
normalizeProduct,
[['context', 'data', 'productWithNormalizedImages']],
'normalizedProduct'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
],
[['context', 'data', 'normalizedProduct']]
)
)
),
method: 'POST',
path: '/products/:productId/copy',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

copyProductBatchRoute

Implementation

Endpoint: POST /products/batch/copy

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: copyProductBatchSchema defines the attached body, path, and/or query contract.

Pipeline: copyProductBatchgetProductsByIds

Success: 204 with no response body; source maps [['undefined'], 204].

Failure: ProductNotFoundBlockError404, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const copyProductBatchRoute = withRoute({
handler: compose(
withLogging(copyProductBatch),
flatMapAsync(withLogging(getProductsByIds)),

flatMapAsync(
applyPayloadArgs(
normalizeImagesOfProducts,
[
['context', 'fileStorageDriver'],
['context', 'data', 'products'],
],
'productsWithNormalizedImages'
)
),

flatMapAsync(
applyPayloadArgs(
normalizeProducts,
[['context', 'data', 'productsWithNormalizedImages']],
'normalizedProducts'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[FileStorageServiceError, 500],
],
[['context', 'data', 'normalizedProducts']]
)
)
),
method: 'POST',
path: '/products/batch/copy',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

getProductImageUploadUrlRoute

Implementation

Endpoint: GET /products/:productId/image-upload-url

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: getSignedImageUploadUrlSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 200 from the final orThrow call.

Failure: FileStorageServiceError500, ProductBlockError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const getProductImageUploadUrlRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
generateProductImageUploadUrl,
[
['context', 'fileStorageDriver'],
['params', 'requestQuery', 'contentType'],
['params', 'requestQuery', 'contentLength'],
],
'imageUploadUrlResult'
)
),
lift(
orThrow(
[
[FileStorageServiceError, 500],
[ProductBlockError, 500],
],
[['context', 'data', 'imageUploadUrlResult'], 200]
)
)
),
method: 'GET',
path: '/products/:productId/image-upload-url',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

createProductImageRoute

Implementation

Endpoint: POST /products/:productId/images

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: createProductImageSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 201 from the final orThrow call.

Failure: ProductNotFoundBlockError404, ProductImageNotFoundBlockError404, ProductUnexpectedDBError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const createProductImageRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
createProductImage,
[
['context', 'db', 'products'],
['params', 'requestParams', 'productId'],
['params', 'requestBody'],
],
'productImageId'
)
),

flatMapAsync(
withLogging(
applyPayloadArgs(
getProductImageById,
[
['context', 'db', 'products'],
['params', 'requestParams', 'productId'],
['context', 'data', 'productImageId'],
],
'productImage'
)
)
),

flatMapAsync(
applyPayloadArgs(
normalizeProductImage,
[
['context', 'fileStorageDriver'],
['context', 'data', 'productImage'],
],
'normalizedProductImage'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[ProductImageNotFoundBlockError, 404],
[ProductUnexpectedDBError, 500],
],
[['context', 'data', 'normalizedProductImage'], 201]
)
)
),
method: 'POST',
path: '/products/:productId/images',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

deleteProductImageRoute

Implementation

Endpoint: DELETE /products/:productId/images/:imageId

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: deleteProductImageSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 204 from the final orThrow call.

Failure: ProductNotFoundBlockError404, ProductImageNotFoundBlockError404, ProductUnexpectedDBError500, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const deleteProductImageRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
deleteProductImage,
[
['context', 'fileStorageDriver'],
['context', 'db', 'products'],
['params', 'requestParams', 'productId'],
['params', 'requestParams', 'imageId'],
],
'productImageDeleted'
)
),

lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[ProductImageNotFoundBlockError, 404],
[ProductUnexpectedDBError, 500],
[FileStorageServiceError, 500],
],
[['context', 'data', 'productImageDeleted'], 204]
)
)
),
method: 'DELETE',
path: '/products/:productId/images/:imageId',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

createProductVariantRoute

Implementation

Endpoint: POST /products/:productId/variants

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: createProductVariantSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 201 from the final orThrow call.

Failure: ProductVariantDbError500, ProductVariantNotFoundError404. Schema and validator failures are handled by their composed middleware.

View complete source
export const createProductVariantRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
createProductVariant,
[
['context', 'db', 'productVariants'],
['params', 'requestBody'],
['params', 'requestParams', 'productId'],
],
'productVariantId'
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
getProductVariantById,
[
['context', 'db', 'productVariants'],
['context', 'data', 'productVariantId'],
],
'rawProductVariant'
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
normalizeRawDocument,
[['context', 'data', 'rawProductVariant']],
'normalizedBody'
)
)
),
lift(
withLogging(
orThrow(
[
[ProductVariantDbError, 500],
[ProductVariantNotFoundError, 404],
],
[['context', 'data', 'normalizedBody'], 201]
)
)
)
),
method: 'POST',
path: '/products/:productId/variants',
// TODO: go back and add validator to check if logged in identity is ['admin', 'owner'] of the organization of the product of the product variant
// TODO: there is also a validator that checks if the logged in identity's email is verified
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

getProductVariantRoute

Implementation

Endpoint: GET /products/:productId/variants/:productVariantId

Access: Authenticated; validators execute in source order: isAuthenticated().

Request: getProductVariantSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 200 from the final orThrow call.

Failure: ProductVariantDbError500, ProductVariantNotFoundError404. Schema and validator failures are handled by their composed middleware.

View complete source
export const getProductVariantRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
getProductVariantById,
[
['context', 'db', 'productVariants'],
['params', 'requestParams', 'productVariantId'],
['params', 'requestParams', 'productId'],
],
'rawProductVariant'
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
normalizeRawDocument,
[['context', 'data', 'rawProductVariant']],
'normalizedBody'
)
)
),
lift(
withLogging(
orThrow(
[
[ProductVariantDbError, 500],
[ProductVariantNotFoundError, 404],
],
[['context', 'data', 'normalizedBody'], 200]
)
)
)
),
method: 'GET',
path: '/products/:productId/variants/:productVariantId',
validators: [isAuthenticated()],
});

updateProductVariantRoute

Implementation

Endpoint: PATCH /products/:productId/variants/:productVariantId

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: updateProductVariantSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 200 from the final orThrow call.

Failure: ProductVariantDbError500, ProductVariantNotFoundError404. Schema and validator failures are handled by their composed middleware.

View complete source
export const updateProductVariantRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
updateProductVariant,
[
['context', 'db', 'productVariants'],
['params', 'requestBody'],
['params', 'requestParams', 'productVariantId'],
],
'hasUpdatedProductVariant'
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
getProductVariantById,
[
['context', 'db', 'productVariants'],
['params', 'requestParams', 'productVariantId'],
],
'rawProductVariant'
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
normalizeRawDocument,
[['context', 'data', 'rawProductVariant']],
'normalizedBody'
)
)
),
lift(
withLogging(
orThrow(
[
[ProductVariantDbError, 500],
[ProductVariantNotFoundError, 404],
],
[['context', 'data', 'normalizedBody'], 200]
)
)
)
),
method: 'PATCH',
path: '/products/:productId/variants/:productVariantId',
// TODO: go back and add validator to check if logged in identity is ['admin', 'owner'] of the organization of the product of the product variant
// TODO: there is also a validator that checks if the logged in identity's email is verified
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

deleteProductVariantRoute

Implementation

Endpoint: DELETE /products/:productId/variants/:productVariantId

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: deleteProductVariantSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 204 from the final orThrow call.

Failure: ProductVariantDbError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const deleteProductVariantRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
deleteProductVariant,
[
['context', 'db', 'productVariants'],
['params', 'requestParams', 'productVariantId'],
],
'hasDeletedProductVariant'
)
),
flatMapAsync(
withLogging(applyPayloadArgs(normalizeEmptyBody, [], 'normalizedBody'))
),
lift(
withLogging(
orThrow(
[[ProductVariantDbError, 500]],
[['context', 'data', 'normalizedBody'], 204]
)
)
)
),
method: 'DELETE',
path: '/products/:productId/variants/:productVariantId',
// TODO: go back and add validator to check if logged in identity is ['admin', 'owner'] of the organization of the product of the product variant
// TODO: there is also a validator that checks if the logged in identity's email is verified
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

findProductVariantsRoute

Implementation

Endpoint: GET /products/:productId/variants

Access: Public; the source supplies no route validators.

Request: findProductVariantsSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 200 from the final orThrow call.

Failure: ProductVariantDbError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const findProductVariantsRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
buildFilterToGetProductVariantsByProductId,
[['params', 'requestParams', 'productId']],
'filter'
)
),
flatMapAsync(
withPagination(
withLogging(
applyPayloadArgs(
findProductVariants,
[
['context', 'db', 'productVariants'],
['context', 'data', 'filter'],
],
'rawProductVariants'
)
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
normalizeDocuments,
[['context', 'data', 'rawProductVariants', 'data']],
'normalizedProductVariants'
)
)
),
flatMapAsync(
applyPayloadArgs(
applySpec({ data: nthArg(0), metadata: { pagination: nthArg(1) } }),
[
['context', 'data', 'normalizedProductVariants'],
['context', 'data', 'rawProductVariants', 'metadata'],
],
'normalizedBody'
)
),
lift(
withLogging(
orThrow(
[[ProductVariantDbError, 500]],
[['context', 'data', 'normalizedBody'], 200]
)
)
)
),
method: 'GET',
path: '/products/:productId/variants',
validators: [
// TODO: add validator to check productId in request params is a product that actually exists
],
});

createProductVariantBulkRoute

Implementation

Endpoint: POST /product/:productId/variants/bulk

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: createProductVariantBulkSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 201 from the final orThrow call.

Failure: ProductVariantDbError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const createProductVariantBulkRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
createProductVariantBulk,
[
['context', 'db', 'productVariants'],
['params', 'requestBody'],
['params', 'requestParams', 'productId'],
],
'productVariantIds'
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
buildFilterToGetProductVariantsByIds,
[['context', 'data', 'productVariantIds']],
'filter'
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
findProductVariants,
[
['context', 'db', 'productVariants'],
['context', 'data', 'filter'],
],
'rawProductVariants'
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
normalizeDocuments,
[['context', 'data', 'rawProductVariants']],
'normalizedBody'
)
)
),
lift(
withLogging(
orThrow(
[[ProductVariantDbError, 500]],
[['context', 'data', 'normalizedBody'], 201]
)
)
)
),
method: 'POST',
path: '/product/:productId/variants/bulk',
// TODO: go back and add validator to check if logged in identity is ['admin', 'owner'] of the organization of the product (of the product variant) for each item in the bulk
// TODO: there is also a validator that checks if the logged in identity's email is verified
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});

updateProductVariantBulkRoute

Implementation

Endpoint: PATCH /product/:productId/variants/bulk

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: updateProductVariantBulkSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 200 from the final orThrow call.

Failure: ProductVariantDbError500, ProductVariantNotFoundError404. Schema and validator failures are handled by their composed middleware.

View complete source
export const updateProductVariantBulkRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
updateProductVariantBulk,
[
['context', 'db', 'productVariants'],
['params', 'requestBody', 'ids'],
['params', 'requestBody', 'data'],
],
'hasUpdatedProductVariants'
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
buildFilterToGetProductVariantsByIds,
[['params', 'requestBody', 'ids']],
'filter'
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
findProductVariants,
[
['context', 'db', 'productVariants'],
['context', 'data', 'filter'],
],
'rawProductVariants'
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
normalizeDocuments,
[['context', 'data', 'rawProductVariants']],
'normalizedBody'
)
)
),
lift(
withLogging(
orThrow(
[
[ProductVariantDbError, 500],
[ProductVariantNotFoundError, 404],
],
[['context', 'data', 'normalizedBody'], 200]
)
)
)
),
method: 'PATCH',
path: '/product/:productId/variants/bulk',
// TODO: add check for if each product variant exists
// TODO: there is also a validator that checks if the logged in identity's email is verified
validators: [
isAuthenticated(),
// TODO: go back and add validator to check if logged in identity is ['admin', 'owner'] of the organization of the product of the product variant for each item in bulk
checkIdentityType(['admin']),
],
});

deleteProductVariantBulkRoute

Implementation

Endpoint: POST /product/:productId/variants/bulk-delete

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: deleteProductVariantBulkSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 204 from the final orThrow call.

Failure: ProductVariantDbError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const deleteProductVariantBulkRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
deleteProductVariantBulk,
[
['context', 'db', 'productVariants'],
['params', 'requestBody'],
],
'hasDeletedProductVariants'
)
),
flatMapAsync(
withLogging(applyPayloadArgs(normalizeEmptyBody, [], 'normalizedBody'))
),
lift(
withLogging(
orThrow(
[[ProductVariantDbError, 500]],
[['context', 'data', 'normalizedBody'], 204]
)
)
)
),
method: 'POST',
path: '/product/:productId/variants/bulk-delete',
// TODO: add check for if each product variant exists
// TODO: there is also a validator that checks if the logged in identity's email is verified
validators: [
isAuthenticated(),
// TODO: go back and add validator to check if logged in identity is ['admin', 'owner'] of the organization of the product of the product variant for each item in the bulk
checkIdentityType(['admin']),
],
});

findProductsByOrganizationIdRoute

Implementation

Endpoint: GET /products/organizations/:organizationId

Access: Authenticated; validators execute in source order: isAuthenticated(), hasOrgRole(...).

Request: findByOrganizationIdSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: The final orThrow success map returns ordinary route data without a statusCode descriptor; this composer does not set a success status.

Failure: ProductUnexpectedDBError500, FileStorageServiceError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const findProductsByOrganizationIdRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
buildOrganizationIdFilter,
[['params', 'requestParams', 'organizationId']],
'filter'
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(buildWithoutMongoIdFindOptions, [], 'options')
)
),
flatMapAsync(
withPagination(
withLogging(
applyPayloadArgs(
findProductResources,
[
['context', 'db', 'products'],
['context', 'data', 'filter'],
['context', 'data', 'options'],
],
'paginatedProducts'
)
)
)
),
flatMapAsync(
applyPayloadArgs(
normalizeImagesOfProducts,
[
['context', 'fileStorageDriver'],
['context', 'data', 'paginatedProducts', 'data'],
],
'productsWithNormalizedImages'
)
),
flatMapAsync(
applyPayloadArgs(
normalizeProducts,
[['context', 'data', 'productsWithNormalizedImages']],
'normalizedProducts'
)
),
flatMapAsync(
applyPayloadArgs(
applySpec({ data: nthArg(0), metadata: { pagination: nthArg(1) } }),
[
['context', 'data', 'normalizedProducts'],
['context', 'data', 'paginatedProducts', 'metadata'],
],
'normalizedBody'
)
),
lift(
orThrow(
[
[ProductUnexpectedDBError, 500],
[FileStorageServiceError, 500],
],
[['context', 'data', 'normalizedBody']]
)
)
),
method: 'GET',
path: '/products/organizations/:organizationId',
validators: [
isAuthenticated(),
hasOrgRole(
['owner', 'admin', 'member'],
['params', 'requestParams', 'organizationId']
),
],
});

getProductLikersRoute

Implementation

Endpoint: GET /products/:productId/likers

Access: Authenticated; validators execute in source order: isAuthenticated(), checkIdentityType(['admin']).

Request: getProductLikersSchema defines the attached body, path, and/or query contract.

Pipeline: This route composes Product blocks and shared utilities directly; see the complete source below.

Success: 200 from the final orThrow call.

Failure: ProductNotFoundBlockError404, ProductUnexpectedDBError500, ProfileDbBlockError500, AvatarBlockError500. Schema and validator failures are handled by their composed middleware.

View complete source
export const getProductLikersRoute = withRoute({
handler: compose(
withLogging(
applyPayloadArgs(
getProductByIdBlock,
[
['context', 'db', 'products'],
['params', 'requestParams', 'productId'],
],
'product'
)
),

flatMapAsync(
withLogging(
applyPayloadArgs(
buildProductLikersByLikeProductIdQuery,
[['params', 'requestParams', 'productId']],
'filter'
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(buildWithoutMongoIdFindOptions, [], 'options')
)
),
flatMapAsync(
withPagination(
withLogging(
applyPayloadArgs(
findProfiles,
[
['context', 'db', 'profiles'],
['context', 'data', 'filter'],
['context', 'data', 'options'],
],
'paginatedLikers'
)
)
)
),
flatMapAsync(
withLogging(
applyPayloadArgs(
normalizeFollowers,
[
['context', 'fileStorageDriver'],
['context', 'data', 'paginatedLikers', 'data'],
],
'normalizedLikers'
)
)
),

flatMapAsync(
applyPayloadArgs(
applySpec({ data: nthArg(0), metadata: { pagination: nthArg(1) } }),
[
['context', 'data', 'normalizedLikers'],
['context', 'data', 'paginatedLikers', 'metadata'],
],
'normalizedBody'
)
),
lift(
orThrow(
[
[ProductNotFoundBlockError, 404],
[ProductUnexpectedDBError, 500],
[ProfileDbBlockError, 500],
[AvatarBlockError, 500],
],
[['context', 'data', 'normalizedBody'], 200]
)
)
),
method: 'GET',
path: '/products/:productId/likers',
validators: [isAuthenticated(), checkIdentityType(['admin'])],
});