feat(product): Create (+ workflow), delete, restore (#4459)

* Feat: create product with product module

* feat: create product wip

* feat: create product wip

* feat: update product relation and generate image migration

* lint

* conitnue implementation

* continue implementation and add integration tests for produceService.create

* Add integration tests for product creation at the module level for the complete flow

* only use persist since write operations are always wrapped in a transaction which will be committed and flushed

* simplify the transaction wrapper to make future changes easier

* feat: move some utils to the utils package to simplify its usage

* tests: fix unit tests

* feat: create variants along side the product

* Add more integration tests an update migrations

* chore: Update actions workflow to include packages integration tests

* small types and utils cleanup

* chore: Add support for database debug option

* chore: Add missing types in package.json from types and util, validate that all the models are sync with medusa

* expose retrieve method

* fix types issues

* fix unit tests and move integration tests workflow with the plugins integration tests

* chore: remove migration function export from the definition to prevent them to be ran by the medusa cli just in case

* fix package.json script

* chore: workflows

* feat: start creating the create product workflow

* feat: add empty step for prices and sales channel

* tests: update scripts and action envs

* fix imports

* feat: Add proper soft deleted support + add product deletion service public api

* chore: update migrations

* chore: update migrations

* chore: update todo

* feat: Add product deletion to the create-product workflow as compensation

* chore: cleanup product utils

* feat: Add support for cascade soft-remove

* feat: refactor repository to take into account withDeleted

* fix integration tests

* Add support for force delete -> delete, cleanup repositories and improvements

* Add support for restoring a product and add integration tests

* cleaup + tests

* types

* fix integration tests

* remove unnecessary comments

* move specific mikro orm usage to the DAL

* Cleanup workflow functions

* Make deleted_at optional at the property level and add url index for the images

* address feedback + cleanup

* fix export

* merge migrations into one

* feat(product, types): added missing product variant methods (#4475)

* chore: added missing product variant methods

* chore: address PR feedback

* chore: catch undefined case for retrieve + specs for variant service

* chore: align TEntity + add changeset

* chore: revert changeset, TEntity to ProductVariant

* chore: write tests for pagination, unskip the test

* Create chilled-mice-deliver.md

* update integration fixtuers

* update pipeline node version

* rename github action

* fix pipeline

* feat(medusa, types): added missing category tests and service methods (#4499)

* chore: added missing category tests and service methods

* chore: added type changes to module service

* chore: address pr feedback

* update repositories manager usage and serialisation from the write public API

* move serializisation to the DAL

* rename template args

* chore: added collection methods for module and collection service (#4505)

* chore: added collection methods for module and collection service

* Create fresh-islands-teach.md

* chore: move retrieve entity to utils package

* chore: make products optional in DTO type

---------

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>

* feat(product): Apply transaction decorators to the services (#4512)

---------

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2023-07-16 20:19:23 +02:00
committed by GitHub
co-authored by Oliver Windall Juhl Riqwan Thamir Carlos R. L. Rodrigues
parent 5b91a3503a
commit befc2f1c80
98 changed files with 5444 additions and 688 deletions
+1
View File
@@ -3,6 +3,7 @@
"version": "1.8.10",
"description": "Medusa Types definition",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa",
+2 -1
View File
@@ -41,11 +41,12 @@ export type Writable<T> = {
}
export interface FindConfig<Entity> {
select?: (keyof Entity)[]
select?: (keyof Entity | string)[]
skip?: number
take?: number
relations?: string[]
order?: { [K: string]: "ASC" | "DESC" }
withDeleted?: boolean
}
export type ExtendedFindConfig<TEntity> = (
+2
View File
@@ -21,4 +21,6 @@ export type FindOptions<T = any> = {
options?: OptionsQuery<T, any>
}
export const SoftDeletableFilterKey = "softDeletable"
export * from "./repository-service"
+51 -2
View File
@@ -1,5 +1,6 @@
import { FindOptions } from "./index"
import { RepositoryTransformOptions } from "../common"
import { Context } from "../shared-context"
/**
* Data access layer (DAL) interface to implements for any repository service.
@@ -7,6 +8,54 @@ import { RepositoryTransformOptions } from "../common"
* ORM directly and allows to switch to another ORM without changing the business logic.
*/
export interface RepositoryService<T = any> {
find(options?: FindOptions<T>, transformOptions?: RepositoryTransformOptions): Promise<T[]>
findAndCount(options?: FindOptions<T>, transformOptions?: RepositoryTransformOptions): Promise<[T[], number]>
transaction(
task: (transactionManager: unknown) => Promise<any>,
context?: {
isolationLevel?: string
transaction?: unknown
enableNestedTransactions?: boolean
}
): Promise<any>
serialize<TData extends object, TResult extends object, TOptions = any>(
data: TData,
options?: TOptions
): Promise<TResult>
serialize<TData extends object[], TResult extends object[], TOptions = any>(
data: TData[],
options?: TOptions
): Promise<TResult>
find(options?: FindOptions<T>, context?: Context): Promise<T[]>
findAndCount(
options?: FindOptions<T>,
context?: Context
): Promise<[T[], number]>
// Only required for some repositories
upsert?(data: any, context?: Context): Promise<T[]>
create(data: unknown[], context?: Context): Promise<T[]>
delete(ids: string[], context?: Context): Promise<void>
softDelete(ids: string[], context?: Context): Promise<T[]>
restore(ids: string[], context?: Context): Promise<T[]>
}
export interface TreeRepositoryService<T = any> extends RepositoryService<T> {
find(
options?: FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<T[]>
findAndCount(
options?: FindOptions<T>,
transformOptions?: RepositoryTransformOptions,
context?: Context
): Promise<[T[], number]>
}
@@ -56,6 +56,7 @@ export interface IInventoryService {
context?: SharedContext
): Promise<ReservationItemDTO>
// TODO make it bulk
createInventoryItem(
input: CreateInventoryItemInput,
context?: SharedContext
@@ -95,6 +96,7 @@ export interface IInventoryService {
context?: SharedContext
): Promise<void>
// TODO make it bulk
deleteInventoryItem(
inventoryItemId: string,
context?: SharedContext
+15
View File
@@ -1,5 +1,6 @@
import { MedusaContainer } from "../common"
import { Logger } from "../logger"
import { RepositoryService } from "../dal"
export type Constructor<T> = new (...args: any[]) => T
export * from "../common/medusa-container"
@@ -93,3 +94,17 @@ export type ModuleExports = {
moduleDeclaration?: InternalModuleDeclaration
): Promise<void>
}
export interface ModuleServiceInitializeOptions {
database: {
clientUrl: string
schema?: string
driverOptions?: Record<string, unknown>
debug?: boolean
}
}
export type ModuleServiceInitializeCustomDataLayerOptions = {
manager?: any
repositories?: { [key: string]: Constructor<RepositoryService> }
}
+133 -1
View File
@@ -34,6 +34,7 @@ export interface ProductDTO {
tags: ProductTagDTO[]
variants: ProductVariantDTO[]
options: ProductOptionDTO[]
images: ProductImageDTO[]
discountable?: boolean
external_id?: string | null
created_at?: string | Date
@@ -86,7 +87,7 @@ export interface ProductTagDTO {
id: string
value: string
metadata?: Record<string, unknown> | null
products: ProductDTO[]
products?: ProductDTO[]
}
export interface ProductCollectionDTO {
@@ -95,6 +96,7 @@ export interface ProductCollectionDTO {
handle: string
metadata?: Record<string, unknown> | null
deleted_at?: string | Date
products?: ProductDTO[]
}
export interface ProductTypeDTO {
@@ -113,6 +115,13 @@ export interface ProductOptionDTO {
deleted_at?: string | Date
}
export interface ProductImageDTO {
id: string
url: string
metadata?: Record<string, unknown> | null
deleted_at?: string | Date
}
export interface ProductOptionValueDTO {
id: string
value: string
@@ -164,3 +173,126 @@ export interface FilterableProductCategoryProps
is_internal?: boolean
include_descendants_tree?: boolean
}
/**
* Write DTO (module API input)
*/
export interface CreateProductTypeDTO {
id?: string
value: string
}
export interface CreateProductTagDTO {
id?: string
value: string
}
export interface CreateProductOptionDTO {
title: string
}
export interface CreateProductVariantOptionDTO {
value: string
}
export interface CreateProductVariantDTO {
title: string
sku?: string
barcode?: string
ean?: string
upc?: string
allow_backorder?: boolean
inventory_quantity?: number
manage_inventory?: boolean
hs_code?: string
origin_country?: string
mid_code?: string
material?: string
weight?: number
length?: number
height?: number
width?: number
options?: CreateProductVariantOptionDTO[]
metadata?: Record<string, unknown>
}
export interface CreateProductDTO {
title: string
subtitle?: string
description?: string
is_giftcard?: boolean
discountable?: boolean
images?: string[] | { id?: string; url: string }[]
thumbnail?: string
handle?: string
status?: ProductStatus
type?: CreateProductTypeDTO
type_id?: string
collection_id?: string
tags?: CreateProductTagDTO[]
// sales_channel
categories?: { id: string }[]
options?: CreateProductOptionDTO[]
variants?: CreateProductVariantDTO[]
width?: number
height?: number
length?: number
weight?: number
origin_country?: string
hs_code?: string
material?: string
mid_code?: string
metadata?: Record<string, unknown>
}
export interface CreateProductOnlyDTO {
title: string
subtitle?: string
description?: string
is_giftcard?: boolean
discountable?: boolean
images?: { id?: string; url: string }[]
thumbnail?: string
handle?: string
status?: ProductStatus
collection_id?: string
width?: number
height?: number
length?: number
weight?: number
origin_country?: string
hs_code?: string
material?: string
mid_code?: string
metadata?: Record<string, unknown>
tags?: { id: string }[]
categories?: { id: string }[]
type_id?: string
}
export interface CreateProductVariantOnlyDTO {
title: string
sku?: string
barcode?: string
ean?: string
upc?: string
allow_backorder?: boolean
inventory_quantity?: number
manage_inventory?: boolean
hs_code?: string
origin_country?: string
mid_code?: string
material?: string
weight?: number
length?: number
height?: number
width?: number
options?: (CreateProductVariantOptionDTO & { option: any })[]
metadata?: Record<string, unknown>
}
export interface CreateProductOptionOnlyDTO {
product: { id: string }
title: string
}
+61 -14
View File
@@ -1,4 +1,5 @@
import {
CreateProductDTO,
FilterableProductCategoryProps,
FilterableProductCollectionProps,
FilterableProductProps,
@@ -11,48 +12,94 @@ import {
ProductVariantDTO,
} from "./common"
import { FindConfig } from "../common"
import { SharedContext } from "../shared-context"
import { Context } from "../shared-context"
export interface IProductModuleService {
retrieve(productId: string, sharedContext?: Context): Promise<ProductDTO>
export interface IProductModuleService<
TProduct = any,
TProductVariant = any,
TProductTag = any,
TProductCollection = any,
TProductCategory = any
> {
list(
filters?: FilterableProductProps,
config?: FindConfig<ProductDTO>,
sharedContext?: SharedContext
sharedContext?: Context
): Promise<ProductDTO[]>
listAndCount(
filters?: FilterableProductProps,
config?: FindConfig<ProductDTO>,
sharedContext?: SharedContext
sharedContext?: Context
): Promise<[ProductDTO[], number]>
listTags(
filters?: FilterableProductTagProps,
config?: FindConfig<ProductTagDTO>,
sharedContext?: SharedContext
sharedContext?: Context
): Promise<ProductTagDTO[]>
retrieveVariant(
productVariantId: string,
config?: FindConfig<ProductVariantDTO>,
sharedContext?: Context
): Promise<ProductVariantDTO>
listVariants(
filters?: FilterableProductVariantProps,
config?: FindConfig<ProductVariantDTO>,
sharedContext?: SharedContext
sharedContext?: Context
): Promise<ProductVariantDTO[]>
listAndCountVariants(
filters?: FilterableProductVariantProps,
config?: FindConfig<ProductVariantDTO>,
sharedContext?: Context
): Promise<[ProductVariantDTO[], number]>
retrieveCollection(
productCollectionId: string,
config?: FindConfig<ProductCollectionDTO>,
sharedContext?: Context
): Promise<ProductCollectionDTO>
listCollections(
filters?: FilterableProductCollectionProps,
config?: FindConfig<ProductCollectionDTO>,
sharedContext?: SharedContext
sharedContext?: Context
): Promise<ProductCollectionDTO[]>
listAndCountCollections(
filters?: FilterableProductCollectionProps,
config?: FindConfig<ProductCollectionDTO>,
sharedContext?: Context
): Promise<[ProductCollectionDTO[], number]>
retrieveCategory(
productCategoryId: string,
config?: FindConfig<ProductCategoryDTO>,
sharedContext?: Context
): Promise<ProductCategoryDTO>
listCategories(
filters?: FilterableProductCategoryProps,
config?: FindConfig<ProductCategoryDTO>,
sharedContext?: SharedContext
sharedContext?: Context
): Promise<ProductCategoryDTO[]>
listAndCountCategories(
filters?: FilterableProductCategoryProps,
config?: FindConfig<ProductCategoryDTO>,
sharedContext?: Context
): Promise<[ProductCategoryDTO[], number]>
create(
data: CreateProductDTO[],
sharedContext?: Context
): Promise<ProductDTO[]>
delete(productIds: string[], sharedContext?: Context): Promise<void>
softDelete(
productIds: string[],
sharedContext?: Context
): Promise<ProductDTO[]>
restore(productIds: string[], sharedContext?: Context): Promise<ProductDTO[]>
}
+6
View File
@@ -3,3 +3,9 @@ import { EntityManager } from "typeorm"
export type SharedContext = {
transactionManager?: EntityManager
}
export type Context<TManager = unknown> = {
transactionManager?: TManager
isolationLevel?: string
enableNestedTransactions?: boolean
}