我正在开发 Medusa JS,需要在“Cart”的核心模型中添加一个自定义字段,这意味着扩展一个实体。因此,按照指南 -> 扩展实体,我能够做所有事情。在使用 Store API 更新新创建的购物车字段的值时,我没有收到任何错误,这意味着验证也工作正常,但问题是字段没有在数据库中更新。
第 2 步(根据文档):扩展购物车模型
import { Column, Entity } from "typeorm";
import {
// alias the core entity to not cause a naming conflict
Cart as MedusaCart,
} from "@medusajs/medusa";
@Entity()
export class Cart extends MedusaCart {
@Column()
is_subscribed?: boolean;
}
第 3 步:创建 Typescript 声明文件
export declare module "@medusajs/medusa/dist/models/order" {
declare interface Order {
is_subscribed: boolean;
}
}
扩展验证(使用更新购物车验证)
import { registerOverriddenValidators } from "@medusajs/medusa";
import { StorePostCartsCartReq as MedusaStorePostCartsCartReq } from "@medusajs/medusa/dist/api/routes/store/carts/update-cart";
import { IsBoolean, IsOptional } from "class-validator";
class StorePostCartsCartReq extends MedusaStorePostCartsCartReq {
@IsOptional()
@IsBoolean()
is_subscribed?: boolean;
}
registerOverriddenValidators(StorePostCartsCartReq);
我需要更新自定义字段的值,以便我可以根据该值创建订阅者。只有当我能够使用更新存储 API 修改此属性的值时,我才能做到这一点。
我相信您还必须扩展与购物车相关的存储库和服务(我假设您进行了迁移)。经过很长时间的摸索和研究节点模块后,我解决了这个问题。这是我在 Github 上的一个帖子中发布的解决方案:
经过长时间的尝试和研究节点模块后,我已经解决了这个问题。我将向您展示我的文件是什么样子。我想扩展嵌套在产品中的变体数组中的价格(MoneyAmount 实体)数组。因此,当我发布新产品时,应返回新的自定义属性并在数据库中更新。
延长金额:
import { Column, Entity } from "typeorm"
import {
// alias the core entity to not cause a naming conflict
MoneyAmount as MedusaMoneyAmount,
} from "@medusajs/medusa"
enum PurchaseType {
OneTime = "one_time",
Recurring = "recurring"
}
enum AggregrateUsageType{
LastDuringPeriod="last_during_period",
LastEver="last_ever",
Max="max",
Sum="sum"
}
enum IntervalType{
Day = "day",
Week = "week",
Month = "month",
Year = "year"
}
enum UsageTypeType{
Metered="metered",
Licensed="licensed"
}
@Entity()
export class MoneyAmount extends MedusaMoneyAmount {
@Column({
type:"enum",
enum:PurchaseType,
default: PurchaseType.OneTime, // Set a default value if needed
nullable:true
})
type: PurchaseType
@Column({
type: "jsonb",
nullable: true,
})
recurring: {
aggregate_usage: null | AggregrateUsageType;
interval: IntervalType;
interval_count: number;
trial_period_days: null | number;
usage_type: UsageTypeType;
};
}
迁移:
import { MigrationInterface, QueryRunner,TableColumn } from "typeorm";
export class MoneyAmountExtension1709761619941 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.addColumn(
"money_amount",
new TableColumn({
name: "type",
type: "enum",
enum: ["one_time", "recurring"],
default: "'one_time'",
isNullable:true,
})
)
await queryRunner.addColumn(
"money_amount",
new TableColumn({
name: "recurring",
type: "jsonb",
isNullable: true,
})
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("money_amount", "type");
await queryRunner.dropColumn("money_amount", "recurring");
}
}
允许返回自定义属性的加载器文件:
export default async function () {
const imports = (await import(
"@medusajs/medusa/dist/api/routes/store/products/index"
)) as any
imports.allowedStoreProductsFields = [
...imports.allowedStoreProductsFields,
"variants__prices.type",
"variants__prices.recurring"
]
imports.defaultStoreProductsFields = [
...imports.defaultStoreProductsFields,
"variants__prices.type",
"variants__prices.recurring"
]
}
扩展 MoneyAmount 的存储库(请注意,它实际上与 DOCS 不同,因为遵循文档显示的方式会导致错误。请参阅我遵循的此线程 https://github.com/medusajs/medusa/issues/6139 ):
import { MoneyAmount } from "@medusajs/medusa";
import { dataSource } from "@medusajs/medusa/dist/loaders/database";
import { MoneyAmountRepository as MedusaMoneyAmountRepository } from "@medusajs/medusa/dist/repositories/money-amount";
export const MoneyAmountRepository:any = dataSource
.getRepository(MoneyAmount)
// .extend({
// ...Object.assign(MedusaMoneyAmountRepository, {
// target: MoneyAmount
// }),
// });
// this causes "moneyAmountRepo.deleteVariantPricesNotIn is not a function\nCannot read properties of undefined (reading 'length')" but is in DOCS
.extend(
Object.assign(MedusaMoneyAmountRepository, {
target: MoneyAmount
}),
);
export default MoneyAmountRepository;
重写验证器:
import { registerOverriddenValidators } from "@medusajs/medusa"
import {AdminPostProductsReq as MedusaAdminPostProductsReq} from "@medusajs/medusa/dist/api/routes/admin/products/create-product"
import{AdminPostProductsProductVariantsReq as MedusaAdminPostProductsProductVariantsReq} from "@medusajs/medusa/dist/api/routes/admin/products/create-variant"
import{ ProductVariantPricesCreateReq as MedusaProductVariantPricesCreateReq }from "@medusajs/medusa/dist/types/product-variant"
import { IsString,ValidateNested,IsArray,IsEnum,IsObject,IsOptional } from "class-validator"
import {Type} from "class-transformer"
class AdminPostProductsReq extends MedusaAdminPostProductsReq {
@Type(() => AdminPostProductsProductVariantsReq)
@ValidateNested({ each: true })
@IsArray()
variants: AdminPostProductsProductVariantsReq[];
}
class AdminPostProductsProductVariantsReq extends MedusaAdminPostProductsProductVariantsReq {
@Type(() => ProductVariantPricesCreateReq)
@ValidateNested({ each: true })
@IsArray()
prices: ProductVariantPricesCreateReq[];
}
enum PurchaseType {
OneTime = "one_time",
Recurring = "recurring"
}
enum AggregrateUsageType{
LastDuringPeriod="last_during_period",
LastEver="last_ever",
Max="max",
Sum="sum"
}
enum IntervalType{
Day = "day",
Week = "week",
Month = "month",
Year = "year"
}
enum UsageTypeType{
Metered="metered",
Licensed="licensed"
}
class ProductVariantPricesCreateReq extends MedusaProductVariantPricesCreateReq{
@IsOptional()
@IsEnum(PurchaseType)
type:PurchaseType
@IsOptional()
@IsObject()
recurring:{
aggregate_usage: null | AggregrateUsageType;
interval: IntervalType;
interval_count: number;
trial_period_days: null | number;
usage_type: UsageTypeType;
}
}
registerOverriddenValidators(AdminPostProductsReq)
// registerOverriddenValidators(AdminPostProductsProductVariantsReq)
// post request goes through without overriding AdminPostProductsProductVariantsReq
// registerOverriddenValidators(ProductVariantPricesCreateReq)
// post request goes through without overriding ProductVariantPricesCreateReq)
扩展产品变体服务(创建产品需要产品变体服务):
import { ProductVariantService as MedusaProductVariantService } from "@medusajs/medusa";
import { Lifetime } from "awilix";
import MoneyAmountRepository from "src/repositories/money-amount";
import { CreateProductVariantInput as MedusaCreateProductVariantInput } from "@medusajs/medusa/dist/types/product-variant";
import { ProductVariantPrice as MedusaProductVariantPrice } from "@medusajs/medusa/dist/types/product-variant";
import ProductVariantRepository from "@medusajs/medusa/dist/repositories/product-variant";
type ProductVariantPrice={
type?:string,
recurring?:object
} & MedusaProductVariantPrice
type CreateProductVariantInput={
prices:ProductVariantPrice[]
} & MedusaCreateProductVariantInput[]
class ProductVariantService extends MedusaProductVariantService {
static LIFE_TIME = Lifetime.SCOPED;
protected readonly moneyAmountRepository_: typeof MoneyAmountRepository;
protected readonly productVariantRepository_: typeof ProductVariantRepository;
constructor(container, options) {
// @ts-expect-error prefer-rest-params
super(...arguments);
this.moneyAmountRepository_ = container.moneyAmountRepository;
this.productVariantRepository_ =container.productVariantRepository
}
async create(productOrProductId, variants: CreateProductVariantInput): Promise<any> {
const variantsWithExtras = variants.map(variant => {
const pricesWithExtras = variant.prices.map((price:ProductVariantPrice) => ({
...price,
type: price.type || null,
recurring: price.recurring || null
}));
// console.log(pricesWithExtras)
return {
...variant,
prices: pricesWithExtras
};
});
// variantsWithExtras.forEach(variant => console.log(variant));
const createdVariants:any = await super.create(productOrProductId, variantsWithExtras);
this.updateVariantPrices(variantsWithExtras.map(function (v:any) {
console.log(`this is v:`,v)
console.log(`this is createdvariantsid`,createdVariants[0].id)
console.log(`this is v.prices:`,v.prices)
return ({
variantId: createdVariants[0].id,
prices: v.prices,
});
}))
return createdVariants;
}
}
export default ProductVariantService;
我还想指出的是,我在 Github 代码空间上工作时试图让它工作,我的文件看起来与我长期以来向您展示的文件完全相同,并且它不会将自定义属性值输入到数据库中已在 POST 正文中提供,而只是将其保留为默认值。在我用完他们允许我的所有时间后,我必须将我在代码空间中所做的更改导出到新分支,并将 GitHub 存储库克隆到 VS code 并在那里工作。我运行 npm i 两次,因为第一次由于某种原因失败,然后我没有在发布请求正文中使用region_id,而是将其切换为货币代码(由于某种原因,POST 正文中的region_id 在发送请求时导致错误)。然后令我惊讶的是它起作用了。