chore(): Reorganize modules (#7210)

**What**
Move all modules to the modules directory
This commit is contained in:
Adrien de Peretti
2024-05-02 17:33:34 +02:00
committed by GitHub
parent 7a351eef09
commit 4eae25e1ef
870 changed files with 91 additions and 62 deletions

View File

@@ -0,0 +1,14 @@
import { moduleDefinition } from "./module-definition"
import { initializeFactory, Modules } from "@medusajs/modules-sdk"
export * from "./types"
export * from "./models"
export * from "./services"
export const initialize = initializeFactory({
moduleName: Modules.STORE,
moduleDefinition,
})
export const runMigrations = moduleDefinition.runMigrations
export const revertMigration = moduleDefinition.revertMigration
export default moduleDefinition

View File

@@ -0,0 +1,29 @@
import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils"
import Store from "./models/store"
export const LinkableKeys: Record<string, string> = {}
const entityLinkableKeysMap: MapToConfig = {}
Object.entries(LinkableKeys).forEach(([key, value]) => {
entityLinkableKeysMap[value] ??= []
entityLinkableKeysMap[value].push({
mapTo: key,
valueFrom: key.split("_").pop()!,
})
})
export const entityNameToLinkableKeysMap: MapToConfig = entityLinkableKeysMap
export const joinerConfig: ModuleJoinerConfig = {
serviceName: Modules.STORE,
primaryKeys: ["id"],
linkableKeys: LinkableKeys,
alias: [
{
name: ["store", "stores"],
args: { entity: Store.name },
},
],
} as ModuleJoinerConfig

View File

@@ -0,0 +1,143 @@
{
"namespaces": [
"public"
],
"name": "public",
"tables": [
{
"columns": {
"id": {
"name": "id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "text"
},
"name": {
"name": "name",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"default": "'Medusa Store'",
"mappedType": "text"
},
"supported_currency_codes": {
"name": "supported_currency_codes",
"type": "text[]",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"default": "'{}'",
"mappedType": "array"
},
"default_currency_code": {
"name": "default_currency_code",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"default_sales_channel_id": {
"name": "default_sales_channel_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"default_region_id": {
"name": "default_region_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"default_location_id": {
"name": "default_location_id",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "text"
},
"metadata": {
"name": "metadata",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"updated_at": {
"name": "updated_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"default": "now()",
"mappedType": "datetime"
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 6,
"mappedType": "datetime"
}
},
"name": "store",
"schema": "public",
"indexes": [
{
"keyName": "IDX_store_deleted_at",
"columnNames": [
"deleted_at"
],
"composite": false,
"primary": false,
"unique": false,
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_store_deleted_at\" ON \"store\" (deleted_at) WHERE deleted_at IS NOT NULL"
},
{
"keyName": "store_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
}
]
}

View File

@@ -0,0 +1,66 @@
import { Migration } from "@mikro-orm/migrations"
export class InitialSetup20240226130829 extends Migration {
async up(): Promise<void> {
// TODO: The migration needs to take care of moving data before dropping columns, among other things
const storeTables = await this.execute(
"select * from information_schema.tables where table_name = 'store' and table_schema = 'public'"
)
if (storeTables.length > 0) {
this.addSql(`alter table "store" alter column "id" TYPE text;`)
this.addSql(`alter table "store" alter column "name" TYPE text;`)
this.addSql(
`alter table "store" alter column "name" SET DEFAULT 'Medusa Store';`
)
this.addSql(
`alter table "store" alter column "default_currency_code" TYPE text;`
)
this.addSql(
`alter table "store" alter column "default_currency_code" drop not null;`
)
this.addSql(
`alter table "store" alter column "default_sales_channel_id" TYPE text;`
)
this.addSql(
`alter table "store" alter column "default_location_id" TYPE text;`
)
this.addSql(
`alter table "store" add column "default_region_id" text null;`
)
this.addSql(
`alter table "store" add column "deleted_at" timestamptz null;`
)
this.addSql(
`alter table "store" add column "supported_currency_codes" text[] not null default \'{}\';`
)
this.addSql(
'create index if not exists "IDX_store_deleted_at" on "store" (deleted_at) where deleted_at is not null;'
)
this.addSql(
`alter table "store" drop constraint if exists "FK_61b0f48cccbb5f41c750bac7286";`
)
this.addSql(
`alter table "store" drop constraint if exists "FK_55beebaa09e947cccca554af222";`
)
// this.addSql(`alter table "store" drop column "default_currency_code";`)
// this.addSql(`alter table "store" drop column "swap_link_template";`)
// this.addSql(`alter table "store" drop column "payment_link_template";`)
// this.addSql(`alter table "store" drop column "invite_link_template";`)
} else {
this.addSql(`create table if not exists "store"
("id" text not null, "name" text not null default \'Medusa Store\', "supported_currency_codes" text[] not null default \'{}\',
"default_currency_code" text null, "default_sales_channel_id" text null, "default_region_id" text null, "default_location_id" text null,
"metadata" jsonb null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null,
constraint "store_pkey" primary key ("id"));`)
this.addSql(
'create index if not exists "IDX_store_deleted_at" on "store" (deleted_at) where deleted_at is not null;'
)
}
}
}

View File

@@ -0,0 +1 @@
export { default as Store } from "./store"

View File

@@ -0,0 +1,86 @@
import {
DALUtils,
Searchable,
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import { DAL } from "@medusajs/types"
import {
BeforeCreate,
Entity,
OnInit,
PrimaryKey,
Property,
Filter,
OptionalProps,
} from "@mikro-orm/core"
type StoreOptionalProps = DAL.SoftDeletableEntityDateColumns
const StoreDeletedAtIndex = createPsqlIndexStatementHelper({
tableName: "store",
columns: "deleted_at",
where: "deleted_at IS NOT NULL",
})
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class Store {
[OptionalProps]?: StoreOptionalProps
@PrimaryKey({ columnType: "text" })
id: string
@Searchable()
@Property({ columnType: "text", default: "Medusa Store" })
name: string
@Property({ type: "array", default: "{}" })
supported_currency_codes: string[] = []
@Property({ columnType: "text", nullable: true })
default_currency_code: string | null = null
@Property({ columnType: "text", nullable: true })
default_sales_channel_id: string | null = null
@Property({ columnType: "text", nullable: true })
default_region_id: string | null = null
@Property({ columnType: "text", nullable: true })
default_location_id: string | null = null
@Property({ columnType: "jsonb", nullable: true })
metadata: Record<string, unknown> | null = null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
created_at: Date
@Property({
onCreate: () => new Date(),
onUpdate: () => new Date(),
columnType: "timestamptz",
defaultRaw: "now()",
})
updated_at: Date
@StoreDeletedAtIndex.MikroORMIndex()
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "store")
}
@OnInit()
onInit() {
this.id = generateEntityId(this.id, "store")
}
}

View File

@@ -0,0 +1,44 @@
import { ModuleExports } from "@medusajs/types"
import * as ModuleServices from "@services"
import { StoreModuleService } from "@services"
import { Modules } from "@medusajs/modules-sdk"
import * as Models from "@models"
import * as ModuleModels from "@models"
import { ModulesSdkUtils } from "@medusajs/utils"
import * as ModuleRepositories from "@repositories"
const migrationScriptOptions = {
moduleName: Modules.STORE,
models: Models,
pathToMigrations: __dirname + "/migrations",
}
const runMigrations = ModulesSdkUtils.buildMigrationScript(
migrationScriptOptions
)
const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
migrationScriptOptions
)
const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
moduleModels: ModuleModels,
moduleRepositories: ModuleRepositories,
moduleServices: ModuleServices,
})
const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
moduleName: Modules.STORE,
moduleModels: Object.values(Models),
migrationsPath: __dirname + "/migrations",
})
const service = StoreModuleService
const loaders = [containerLoader, connectionLoader] as any
export const moduleDefinition: ModuleExports = {
service,
loaders,
revertMigration,
runMigrations,
}

View File

@@ -0,0 +1 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils"

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
import { ModulesSdkUtils } from "@medusajs/utils"
import { Modules } from "@medusajs/modules-sdk"
import * as Models from "@models"
import { EOL } from "os"
const args = process.argv
const path = args.pop() as string
export default (async () => {
const { config } = await import("dotenv")
config()
if (!path) {
throw new Error(
`filePath is required.${EOL}Example: medusa-store-seed <filePath>`
)
}
const run = ModulesSdkUtils.buildSeedScript({
moduleName: Modules.STORE,
models: Models,
pathToMigrations: __dirname + "/../../migrations",
seedHandler: async ({ manager, data }) => {
// TODO: Add seed logic
},
})
await run({ path })
})()

View File

@@ -0,0 +1,5 @@
describe("noop", function () {
it("should run", function () {
expect(true).toBe(true)
})
})

View File

@@ -0,0 +1 @@
export { default as StoreModuleService } from "./store-module-service"

View File

@@ -0,0 +1,265 @@
import {
DAL,
InternalModuleDeclaration,
ModuleJoinerConfig,
ModulesSdkTypes,
IStoreModuleService,
StoreTypes,
Context,
} from "@medusajs/types"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
MedusaError,
ModulesSdkUtils,
isString,
promiseAll,
removeUndefined,
} from "@medusajs/utils"
import { Store } from "@models"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import { UpdateStoreInput } from "@types"
const generateMethodForModels = []
type InjectedDependencies = {
baseRepository: DAL.RepositoryService
storeService: ModulesSdkTypes.InternalModuleService<any>
}
export default class StoreModuleService<TEntity extends Store = Store>
extends ModulesSdkUtils.abstractModuleServiceFactory<
InjectedDependencies,
StoreTypes.StoreDTO,
{
Store: { dto: StoreTypes.StoreDTO }
}
>(Store, generateMethodForModels, entityNameToLinkableKeysMap)
implements IStoreModuleService
{
protected baseRepository_: DAL.RepositoryService
protected readonly storeService_: ModulesSdkTypes.InternalModuleService<TEntity>
constructor(
{ baseRepository, storeService }: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration
) {
// @ts-ignore
super(...arguments)
this.baseRepository_ = baseRepository
this.storeService_ = storeService
}
__joinerConfig(): ModuleJoinerConfig {
return joinerConfig
}
async create(
data: StoreTypes.CreateStoreDTO[],
sharedContext?: Context
): Promise<StoreTypes.StoreDTO[]>
async create(
data: StoreTypes.CreateStoreDTO,
sharedContext?: Context
): Promise<StoreTypes.StoreDTO>
@InjectManager("baseRepository_")
async create(
data: StoreTypes.CreateStoreDTO | StoreTypes.CreateStoreDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<StoreTypes.StoreDTO | StoreTypes.StoreDTO[]> {
const input = Array.isArray(data) ? data : [data]
const result = await this.create_(input, sharedContext)
return await this.baseRepository_.serialize<StoreTypes.StoreDTO[]>(
Array.isArray(data) ? result : result[0]
)
}
@InjectTransactionManager("baseRepository_")
async create_(
data: StoreTypes.CreateStoreDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<Store[]> {
let normalizedInput = StoreModuleService.normalizeInput(data)
StoreModuleService.validateCreateRequest(normalizedInput)
return await this.storeService_.create(normalizedInput, sharedContext)
}
async upsert(
data: StoreTypes.UpsertStoreDTO[],
sharedContext?: Context
): Promise<StoreTypes.StoreDTO[]>
async upsert(
data: StoreTypes.UpsertStoreDTO,
sharedContext?: Context
): Promise<StoreTypes.StoreDTO>
@InjectTransactionManager("baseRepository_")
async upsert(
data: StoreTypes.UpsertStoreDTO | StoreTypes.UpsertStoreDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<StoreTypes.StoreDTO | StoreTypes.StoreDTO[]> {
const input = Array.isArray(data) ? data : [data]
const forUpdate = input.filter(
(store): store is UpdateStoreInput => !!store.id
)
const forCreate = input.filter(
(store): store is StoreTypes.CreateStoreDTO => !store.id
)
const operations: Promise<Store[]>[] = []
if (forCreate.length) {
operations.push(this.create_(forCreate, sharedContext))
}
if (forUpdate.length) {
operations.push(this.update_(forUpdate, sharedContext))
}
const result = (await promiseAll(operations)).flat()
return await this.baseRepository_.serialize<
StoreTypes.StoreDTO[] | StoreTypes.StoreDTO
>(Array.isArray(data) ? result : result[0])
}
async update(
id: string,
data: StoreTypes.UpdateStoreDTO,
sharedContext?: Context
): Promise<StoreTypes.StoreDTO>
async update(
selector: StoreTypes.FilterableStoreProps,
data: StoreTypes.UpdateStoreDTO,
sharedContext?: Context
): Promise<StoreTypes.StoreDTO[]>
@InjectManager("baseRepository_")
async update(
idOrSelector: string | StoreTypes.FilterableStoreProps,
data: StoreTypes.UpdateStoreDTO,
@MedusaContext() sharedContext: Context = {}
): Promise<StoreTypes.StoreDTO | StoreTypes.StoreDTO[]> {
let normalizedInput: UpdateStoreInput[] = []
if (isString(idOrSelector)) {
normalizedInput = [{ id: idOrSelector, ...data }]
} else {
const stores = await this.storeService_.list(
idOrSelector,
{},
sharedContext
)
normalizedInput = stores.map((store) => ({
id: store.id,
...data,
}))
}
const updateResult = await this.update_(normalizedInput, sharedContext)
const stores = await this.baseRepository_.serialize<
StoreTypes.StoreDTO[] | StoreTypes.StoreDTO
>(updateResult)
return isString(idOrSelector) ? stores[0] : stores
}
@InjectTransactionManager("baseRepository_")
protected async update_(
data: UpdateStoreInput[],
@MedusaContext() sharedContext: Context = {}
): Promise<Store[]> {
const normalizedInput = StoreModuleService.normalizeInput(data)
await this.validateUpdateRequest(normalizedInput)
return await this.storeService_.update(normalizedInput, sharedContext)
}
private static normalizeInput<T extends StoreTypes.UpdateStoreDTO>(
stores: T[]
): T[] {
return stores.map((store) =>
removeUndefined({
...store,
name: store.name?.trim(),
})
)
}
private static validateCreateRequest(stores: StoreTypes.CreateStoreDTO[]) {
for (const store of stores) {
// If we are setting the default currency code on creating, make sure it is supported
if (store.default_currency_code) {
if (
!store.supported_currency_codes?.includes(
store.default_currency_code ?? ""
)
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Store does not have currency: ${store.default_currency_code}`
)
}
}
}
}
private async validateUpdateRequest(stores: UpdateStoreInput[]) {
const dbStores = await this.storeService_.list(
{ id: stores.map((s) => s.id) },
{ take: null }
)
const dbStoresMap = new Map<string, Store>(
dbStores.map((dbStore) => [dbStore.id, dbStore])
)
for (const store of stores) {
const dbStore = dbStoresMap.get(store.id)
// If it is updating both the supported currency codes and the default one, look in that list
if (store.supported_currency_codes && store.default_currency_code) {
if (
!store.supported_currency_codes.includes(
store.default_currency_code ?? ""
)
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Store does not have currency: ${store.default_currency_code}`
)
}
return
}
// If it is updating only the default currency code, look in the db store
if (store.default_currency_code) {
if (
!dbStore?.supported_currency_codes?.includes(
store.default_currency_code
)
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Store does not have currency: ${store.default_currency_code}`
)
}
}
// If it is updating only the supported currency codes, make sure one of them is not set as a default one
if (store.supported_currency_codes) {
if (
!store.supported_currency_codes.includes(
dbStore?.default_currency_code ?? ""
)
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"You are not allowed to remove default currency from store currencies without replacing it as well"
)
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
import { StoreTypes } from "@medusajs/types"
import { IEventBusModuleService, Logger } from "@medusajs/types"
export type InitializeModuleInjectableDependencies = {
logger?: Logger
eventBusService?: IEventBusModuleService
}
export type UpdateStoreInput = StoreTypes.UpdateStoreDTO & { id: string }