Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add basic CRUD functionality to store module #6510

Merged
merged 1 commit into from
Feb 26, 2024
Merged
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
10 changes: 10 additions & 0 deletions packages/store/integration-tests/__fixtures__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StoreTypes } from "@medusajs/types"

export const createStoreFixture: StoreTypes.CreateStoreDTO = {
name: "Test store",
default_sales_channel_id: "test-sales-channel",
default_region_id: "test-region",
metadata: {
test: "test",
},
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Modules } from "@medusajs/modules-sdk"
import { IStoreModuleService } from "@medusajs/types"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { createStoreFixture } from "../__fixtures__"

jest.setTimeout(100000)

Expand All @@ -11,9 +12,88 @@ moduleIntegrationTestRunner({
service,
}: SuiteOptions<IStoreModuleService>) => {
describe("Store Module Service", () => {
describe("noop", function () {
it("should run", function () {
expect(true).toBe(true)
describe("creating a store", () => {
it("should get created successfully", async function () {
const store = await service.create(createStoreFixture)

expect(store).toEqual(
expect.objectContaining({
name: "Test store",
default_sales_channel_id: "test-sales-channel",
default_region_id: "test-region",
metadata: {
test: "test",
},
})
)
})
})

describe("upserting a store", () => {
it("should get created if it does not exist", async function () {
const store = await service.upsert(createStoreFixture)

expect(store).toEqual(
expect.objectContaining({
name: "Test store",
default_sales_channel_id: "test-sales-channel",
default_region_id: "test-region",
metadata: {
test: "test",
},
})
)
})

it("should get created if it does not exist", async function () {
const createdStore = await service.upsert(createStoreFixture)
const upsertedStore = await service.upsert({ name: "Upserted store" })

expect(upsertedStore).toEqual(
expect.objectContaining({
name: "Upserted store",
})
)
expect(upsertedStore.id).not.toEqual(createdStore.id)
})
})

describe("updating a store", () => {
it("should update the name successfully", async function () {
const createdStore = await service.create(createStoreFixture)
const updatedStore = await service.update(createdStore.id, {
title: "Updated store",
})
expect(updatedStore.title).toEqual("Updated store")
})
})

describe("deleting a store", () => {
it("should successfully delete existing stores", async function () {
const createdStore = await service.create([
createStoreFixture,
createStoreFixture,
])

await service.delete([createdStore[0].id, createdStore[1].id])

const storeInDatabase = await service.list()
expect(storeInDatabase).toHaveLength(0)
})
})

describe("retrieving a store", () => {
it("should successfully return all existing stores", async function () {
await service.create([
createStoreFixture,
{ ...createStoreFixture, name: "Another store" },
])

const storesInDatabase = await service.list()
expect(storesInDatabase).toHaveLength(2)
expect(storesInDatabase.map((s) => s.name)).toEqual(
expect.arrayContaining(["Test store", "Another store"])
)
})
})
})
Expand Down
92 changes: 92 additions & 0 deletions packages/store/src/migrations/.snapshot-medusa-store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"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,
"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"
}
},
"name": "store",
"schema": "public",
"indexes": [
{
"keyName": "store_pkey",
"columnNames": [
"id"
],
"composite": false,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {}
}
]
}
9 changes: 9 additions & 0 deletions packages/store/src/migrations/InitialSetup20240226130829.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Migration } from "@mikro-orm/migrations"

export class InitialSetup20240226130829 extends Migration {
async up(): Promise<void> {
this.addSql(
'create table if not exists "store" ("id" text not null, "name" text not 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(), constraint "store_pkey" primary key ("id"));'
)
}
}
15 changes: 15 additions & 0 deletions packages/store/src/models/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ export default class Store {
@PrimaryKey({ columnType: "text" })
id: string

@Property({ columnType: "text" })
name: string

@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",
Expand Down
140 changes: 139 additions & 1 deletion packages/store/src/services/store-module-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ import {
ModulesSdkTypes,
IStoreModuleService,
StoreTypes,
Context,
} from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import {
InjectManager,
InjectTransactionManager,
MedusaContext,
ModulesSdkUtils,
isString,
promiseAll,
removeUndefined,
} from "@medusajs/utils"

import { Store } from "@models"
import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config"
import { UpdateStoreInput } from "@types"

const generateMethodForModels = []

Expand Down Expand Up @@ -44,4 +54,132 @@ export default class StoreModuleService<TEntity extends Store = Store>
__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)
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)
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(),
})
)
}
}
Loading
Loading