diff --git a/prisma/migrations/20260910120000_plugin_subscription_catalog_key/migration.sql b/prisma/migrations/20260910120000_plugin_subscription_catalog_key/migration.sql new file mode 100644 index 0000000..70c34cb --- /dev/null +++ b/prisma/migrations/20260910120000_plugin_subscription_catalog_key/migration.sql @@ -0,0 +1,13 @@ +-- Catalog linkage on plugin_subscriptions was in schema.prisma without a +-- CREATE COLUMN migration. Remote store GET swallows the missing column; +-- POST create did not, so purchase requests returned HTTP 500. + +ALTER TABLE "plugin_subscriptions" + ADD COLUMN IF NOT EXISTS "setting_key" VARCHAR(50); +ALTER TABLE "plugin_subscriptions" + ADD COLUMN IF NOT EXISTS "auto_cut_on_expiry" BOOLEAN NOT NULL DEFAULT false; +ALTER TABLE "plugin_subscriptions" + ADD COLUMN IF NOT EXISTS "alert_marks" JSONB; + +CREATE INDEX IF NOT EXISTS "plugin_subscriptions_setting_key_idx" + ON "plugin_subscriptions"("setting_key"); diff --git a/src/plugins/plugin-store.service.ts b/src/plugins/plugin-store.service.ts index d0e5f5b..332ba3a 100644 --- a/src/plugins/plugin-store.service.ts +++ b/src/plugins/plugin-store.service.ts @@ -3,6 +3,7 @@ import { ConflictException, ForbiddenException, Injectable, + InternalServerErrorException, Logger, NotFoundException, OnModuleDestroy, @@ -722,10 +723,16 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy { throw new ConflictException('درخواست در انتظار برای این افزونه وجود دارد'); } - const sub = await this.prisma.pluginSubscription.findFirst({ - where: { tenantId, settingKey: item.settingKey, isActive: true }, - orderBy: { expiresAt: 'desc' }, - }); + let sub: { isActive: boolean; expiresAt: Date | null } | null = null; + try { + sub = await this.prisma.pluginSubscription.findFirst({ + where: { tenantId, settingKey: item.settingKey, isActive: true }, + orderBy: { expiresAt: 'desc' }, + select: { isActive: true, expiresAt: true }, + }); + } catch (error: any) { + this.logger.warn(`remoteCreateRequest sub skipped: ${error?.message || error}`); + } if (sub?.isActive && !sub.expiresAt) { throw new ConflictException('این افزونه یکبار پرداخت شده و فعال است'); } @@ -736,35 +743,50 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy { } } - const created = await this.prisma.pluginPurchaseRequest.create({ - data: { - tenantId, - catalogItemId: item.id, - durationDays: dto.durationDays, - originalPriceRials: price.originalPriceRials, - finalPriceRials: price.finalPriceRials, - status: 'pending', - }, - include: { - catalogItem: { select: { titleFa: true, settingKey: true } }, - tenant: { select: { nameFa: true, domain: true } }, - }, - }); + try { + const created = await this.prisma.pluginPurchaseRequest.create({ + data: { + tenantId, + catalogItemId: item.id, + durationDays: dto.durationDays, + originalPriceRials: price.originalPriceRials, + finalPriceRials: price.finalPriceRials, + status: 'pending', + }, + include: { + catalogItem: { select: { titleFa: true, settingKey: true } }, + tenant: { select: { nameFa: true, domain: true } }, + }, + }); - this.notify.notifyAdmins( - `درخواست افزونه: «${created.catalogItem.titleFa}» ${pluginStoreDurationLabel(dto.durationDays)} برای «${created.tenant.nameFa}» (${created.tenant.domain}) — ${price.finalPriceRials.toLocaleString('fa-IR')} ریال`, - ); + this.notify.notifyAdmins( + `درخواست افزونه: «${created.catalogItem.titleFa}» ${pluginStoreDurationLabel(dto.durationDays)} برای «${created.tenant.nameFa}» (${created.tenant.domain}) — ${price.finalPriceRials.toLocaleString('fa-IR')} ریال`, + ); - return this.toRequestRow({ - ...created, - tenant: { - id: tenantId, - nameFa: created.tenant.nameFa, - domain: created.tenant.domain, - slug: '', - product: ProductType.khanegi, - }, - } as any); + return this.toRequestRow({ + ...created, + tenant: { + id: tenantId, + nameFa: created.tenant.nameFa, + domain: created.tenant.domain, + slug: '', + product: ProductType.khanegi, + }, + } as any); + } catch (error: any) { + if ( + error instanceof BadRequestException || + error instanceof ConflictException || + error instanceof ForbiddenException || + error instanceof NotFoundException + ) { + throw error; + } + this.logger.error(`remoteCreateRequest failed: ${error?.message || error}`); + throw new InternalServerErrorException( + error?.message || 'ثبت درخواست افزونه ناموفق بود', + ); + } } async remoteCancelRequest(tenantId: string, product: string, requestId: string) {