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,127 +0,0 @@
import { loadDatabaseConfig } from "../load-database-config"
describe("loadDatabaseConfig", function () {
afterEach(() => {
delete process.env.POSTGRES_URL
delete process.env.PRODUCT_POSTGRES_URL
})
it("should return the local configuration using the environment variable", function () {
process.env.POSTGRES_URL = "postgres://localhost:5432/medusa"
let config = loadDatabaseConfig()
expect(config).toEqual({
clientUrl: process.env.POSTGRES_URL,
driverOptions: {
connection: {
ssl: false,
},
},
schema: "",
})
delete process.env.POSTGRES_URL
process.env.PRODUCT_POSTGRES_URL = "postgres://localhost:5432/medusa"
config = loadDatabaseConfig()
expect(config).toEqual({
clientUrl: process.env.PRODUCT_POSTGRES_URL,
driverOptions: {
connection: {
ssl: false,
},
},
schema: "",
})
})
it("should return the remote configuration using the environment variable", function () {
process.env.POSTGRES_URL = "postgres://https://test.com:5432/medusa"
let config = loadDatabaseConfig()
expect(config).toEqual({
clientUrl: process.env.POSTGRES_URL,
driverOptions: {
connection: {
ssl: {
rejectUnauthorized: false,
},
},
},
schema: "",
})
delete process.env.POSTGRES_URL
process.env.PRODUCT_POSTGRES_URL = "postgres://https://test.com:5432/medusa"
config = loadDatabaseConfig()
expect(config).toEqual({
clientUrl: process.env.PRODUCT_POSTGRES_URL,
driverOptions: {
connection: {
ssl: {
rejectUnauthorized: false,
},
},
},
schema: "",
})
})
it("should return the local configuration using the options", function () {
process.env.POSTGRES_URL = "postgres://localhost:5432/medusa"
const options = {
database: {
clientUrl: "postgres://localhost:5432/medusa-test",
},
}
const config = loadDatabaseConfig(options)
expect(config).toEqual({
clientUrl: options.database.clientUrl,
driverOptions: {
connection: {
ssl: false,
},
},
schema: "",
})
})
it("should return the remote configuration using the options", function () {
process.env.POSTGRES_URL = "postgres://localhost:5432/medusa"
const options = {
database: {
clientUrl: "postgres://https://test.com:5432/medusa-test",
},
}
const config = loadDatabaseConfig(options)
expect(config).toEqual({
clientUrl: options.database.clientUrl,
driverOptions: {
connection: {
ssl: {
rejectUnauthorized: false,
},
},
},
schema: "",
})
})
it("should throw if no clientUrl is provided", function () {
let error
try {
loadDatabaseConfig()
} catch (e) {
error = e
}
expect(error.message).toEqual(
"No database clientUrl provided. Please provide the clientUrl through the PRODUCT_POSTGRES_URL or POSTGRES_URL environment variable or the options object in the initialize function."
)
})
})
@@ -1,15 +1,15 @@
import { MikroORM, PostgreSqlDriver } from "@mikro-orm/postgresql"
import { ProductServiceInitializeOptions } from "../types"
import { ModuleServiceInitializeOptions } from "@medusajs/types"
export async function createConnection(
database: ProductServiceInitializeOptions["database"],
database: ModuleServiceInitializeOptions["database"],
entities: any[]
) {
const schema = database.schema || "public"
const orm = await MikroORM.init<PostgreSqlDriver>({
discovery: { disableDynamicFileAccess: true },
entities,
debug: process.env.NODE_ENV === "development",
debug: database.debug ?? process.env.NODE_ENV?.startsWith("dev") ?? false,
baseDir: process.cwd(),
clientUrl: database.clientUrl,
schema,
+11 -2
View File
@@ -1,3 +1,12 @@
export * from "./query"
import { MODULE_RESOURCE_TYPE } from "@medusajs/types"
export * from "./create-connection"
export * from "./load-database-config"
export * from "./soft-deletable"
export function shouldForceTransaction(target: any): boolean {
return target.moduleDeclaration?.resources === MODULE_RESOURCE_TYPE.ISOLATED
}
export function doNotForceTransaction(): boolean {
return false
}
@@ -1,82 +0,0 @@
import {
ProductServiceInitializeCustomDataLayerOptions,
ProductServiceInitializeOptions,
} from "../types"
import { MedusaError } from "@medusajs/utils"
function getEnv(key: string): string {
const value = process.env[`PRODUCT_${key}`] ?? process.env[`${key}`]
return value ?? ""
}
function isProductServiceInitializeOptions(
obj: unknown
): obj is ProductServiceInitializeOptions {
return !!(obj as ProductServiceInitializeOptions)?.database
}
function getDefaultDriverOptions(
clientUrl: string
): ProductServiceInitializeOptions["database"]["driverOptions"] {
const localOptions = {
connection: {
ssl: false,
},
}
const remoteOptions = {
connection: {
ssl: {
rejectUnauthorized: false,
},
},
}
if (clientUrl) {
return clientUrl.match(/localhost/i) ? localOptions : remoteOptions
}
return process.env.NODE_ENV?.match(/prod/i)
? remoteOptions
: process.env.NODE_ENV?.match(/dev/i)
? localOptions
: {}
}
/**
* Load the config for the database connection. The options can be retrieved
* through PRODUCT_* (e.g PRODUCT_POSTGRES_URL) or * (e.g POSTGRES_URL) environment variables or the options object.
* @param options
*/
export function loadDatabaseConfig(
options?:
| ProductServiceInitializeOptions
| ProductServiceInitializeCustomDataLayerOptions
): ProductServiceInitializeOptions["database"] {
const clientUrl = getEnv("POSTGRES_URL")
const database: ProductServiceInitializeOptions["database"] = {
clientUrl: getEnv("POSTGRES_URL"),
schema: getEnv("POSTGRES_SCHEMA") ?? "public",
driverOptions: JSON.parse(
getEnv("POSTGRES_DRIVER_OPTIONS") ||
JSON.stringify(getDefaultDriverOptions(clientUrl))
),
}
if (isProductServiceInitializeOptions(options)) {
database.clientUrl = options.database.clientUrl ?? database.clientUrl
database.schema = options.database.schema ?? database.schema
database.driverOptions =
options.database.driverOptions ??
getDefaultDriverOptions(database.clientUrl)
}
if (!database.clientUrl) {
throw new MedusaError(
MedusaError.Types.INVALID_ARGUMENT,
"No database clientUrl provided. Please provide the clientUrl through the PRODUCT_POSTGRES_URL or POSTGRES_URL environment variable or the options object in the initialize function."
)
}
return database
}
-44
View File
@@ -1,44 +0,0 @@
/**
* Move to a new build query utils
*/
import { DAL, FindConfig } from "@medusajs/types"
import { isObject } from "@medusajs/utils"
export function deduplicateIfNecessary<T = any>(collection: T | T[]) {
return Array.isArray(collection) ? [...new Set(collection)] : collection
}
export function buildQuery<T = any, TDto = any>(
filters: Record<string, any> = {},
config: FindConfig<TDto> = {}
): DAL.FindOptions<T> {
const where: DAL.FilterQuery<T> = {}
buildWhere(filters, where)
const findOptions: DAL.OptionsQuery<T, any> = {
populate: config.relations ?? [],
fields: config.select,
limit: config.take,
offset: config.skip,
} as any
return { where, options: findOptions }
}
function buildWhere(filters: Record<string, any> = {}, where = {}) {
for (let [prop, value] of Object.entries(filters)) {
if (Array.isArray(value)) {
value = deduplicateIfNecessary(value)
where[prop] = ["$in", "$nin"].includes(prop) ? value : { $in: value }
continue
}
if (isObject(value)) {
where[prop] = {}
buildWhere(value, where[prop])
continue
}
where[prop] = value
}
}
@@ -0,0 +1,23 @@
// TODO: Should we create a mikro orm specific package for this and the base repository?
import { Filter } from "@mikro-orm/core"
import { DAL } from "@medusajs/types"
interface FilterArguments {
withDeleted?: boolean
}
export const SoftDeletable = (): ClassDecorator => {
return Filter({
name: DAL.SoftDeletableFilterKey,
cond: ({ withDeleted }: FilterArguments = {}) => {
if (withDeleted) {
return {}
}
return {
deleted_at: null,
}
},
default: true,
})
}