fix pluggin bug

This commit is contained in:
2026-09-09 17:04:33 +03:30
parent 58744d0f91
commit 08f648457f
3 changed files with 142 additions and 24 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ CONFIG_FILE="${REPLICAS_ENV_FILE:-$API_DIR/deploy/replicas.env}"
MODE="${1:---repair-if-needed}"
# Tables that must match publisher for public API (order: parents before children).
SYNC_TABLES=(setting_templates tenants settings)
SYNC_TABLES=(setting_templates tenants settings plugin_catalog_items plugin_catalog_prices plugin_purchase_requests)
APP_DB_USER="${REPLICA_APP_DB_USER:-montime}"
case "$MODE" in
@@ -0,0 +1,89 @@
-- Plugin store tables were added to schema.prisma without a CREATE TABLE
-- migration (publisher likely used db push, or never created them at all).
-- Replica ALTER of images/receipt_url then fails. IF NOT EXISTS is safe
-- on publisher if tables already exist.
DO $$ BEGIN
CREATE TYPE "PluginPurchaseStatus" AS ENUM ('pending', 'approved', 'rejected', 'cancelled');
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
CREATE TABLE IF NOT EXISTS "plugin_catalog_items" (
"id" TEXT NOT NULL,
"setting_key" VARCHAR(50) NOT NULL,
"title_fa" TEXT NOT NULL,
"description" TEXT,
"images" JSONB,
"in_store" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "plugin_catalog_items_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "plugin_catalog_items_setting_key_key"
ON "plugin_catalog_items"("setting_key");
CREATE TABLE IF NOT EXISTS "plugin_catalog_prices" (
"id" TEXT NOT NULL,
"catalog_item_id" TEXT NOT NULL,
"duration_days" INTEGER NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"original_price_rials" INTEGER NOT NULL DEFAULT 0,
"final_price_rials" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "plugin_catalog_prices_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "plugin_catalog_prices_catalog_item_id_duration_days_key"
ON "plugin_catalog_prices"("catalog_item_id", "duration_days");
CREATE TABLE IF NOT EXISTS "plugin_purchase_requests" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"catalog_item_id" TEXT NOT NULL,
"duration_days" INTEGER NOT NULL,
"original_price_rials" INTEGER NOT NULL,
"final_price_rials" INTEGER NOT NULL,
"status" "PluginPurchaseStatus" NOT NULL DEFAULT 'pending',
"auto_cut_on_expiry" BOOLEAN,
"plugin_subscription_id" TEXT,
"receipt_url" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"decided_at" TIMESTAMP(3),
CONSTRAINT "plugin_purchase_requests_pkey" PRIMARY KEY ("id")
);
CREATE INDEX IF NOT EXISTS "plugin_purchase_requests_tenant_id_status_idx"
ON "plugin_purchase_requests"("tenant_id", "status");
CREATE INDEX IF NOT EXISTS "plugin_purchase_requests_catalog_item_id_idx"
ON "plugin_purchase_requests"("catalog_item_id");
CREATE INDEX IF NOT EXISTS "plugin_purchase_requests_created_at_idx"
ON "plugin_purchase_requests"("created_at");
DO $$ BEGIN
ALTER TABLE "plugin_catalog_prices"
ADD CONSTRAINT "plugin_catalog_prices_catalog_item_id_fkey"
FOREIGN KEY ("catalog_item_id") REFERENCES "plugin_catalog_items"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "plugin_purchase_requests"
ADD CONSTRAINT "plugin_purchase_requests_tenant_id_fkey"
FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id")
ON DELETE CASCADE ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
ALTER TABLE "plugin_purchase_requests"
ADD CONSTRAINT "plugin_purchase_requests_catalog_item_id_fkey"
FOREIGN KEY ("catalog_item_id") REFERENCES "plugin_catalog_items"("id")
ON DELETE RESTRICT ON UPDATE CASCADE;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
+52 -23
View File
@@ -7,6 +7,7 @@ import {
NotFoundException,
OnModuleDestroy,
OnModuleInit,
ServiceUnavailableException,
} from '@nestjs/common';
import {
PluginCatalogItem,
@@ -271,14 +272,14 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
create: {
catalogItemId: id,
durationDays: price.durationDays,
enabled: price.enabled,
originalPriceRials: price.originalPriceRials,
finalPriceRials: price.finalPriceRials,
enabled: Boolean(price.enabled),
originalPriceRials: Math.trunc(Number(price.originalPriceRials) || 0),
finalPriceRials: Math.trunc(Number(price.finalPriceRials) || 0),
},
update: {
enabled: price.enabled,
originalPriceRials: price.originalPriceRials,
finalPriceRials: price.finalPriceRials,
enabled: Boolean(price.enabled),
originalPriceRials: Math.trunc(Number(price.originalPriceRials) || 0),
finalPriceRials: Math.trunc(Number(price.finalPriceRials) || 0),
},
});
}
@@ -491,33 +492,53 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
return await this.buildRemoteStore(tenantId);
} catch (error: any) {
if (error instanceof ForbiddenException) throw error;
this.logger.warn(`remoteStore skipped: ${error?.message || error}`);
return { plugin_store_enabled: true, items: [] };
this.logger.warn(`remoteStore failed: ${error?.message || error}`);
throw new ServiceUnavailableException(
error?.message || 'فروشگاه افزونه در سوپرادمین در دسترس نیست',
);
}
}
private isCatalogPriceEnabled(price: Pick<PluginCatalogPrice, 'enabled'> | { enabled?: unknown }) {
const flag = (price as { enabled?: unknown }).enabled;
return flag === true || flag === 1 || flag === 'true' || flag === 't';
}
private async buildRemoteStore(tenantId: string) {
await this.seedCatalog();
const items = await this.prisma.pluginCatalogItem.findMany({
where: { inStore: true },
orderBy: { titleFa: 'asc' },
include: { prices: { orderBy: { durationDays: 'asc' } } },
});
const subs = await this.prisma.pluginSubscription.findMany({
where: { tenantId, settingKey: { not: null } },
});
const pending = await this.prisma.pluginPurchaseRequest.findMany({
where: { tenantId, status: 'pending' },
select: { catalogItemId: true },
});
let subs: Array<{
settingKey: string | null;
expiresAt: Date | null;
isActive: boolean;
}> = [];
let pending: Array<{ catalogItemId: string }> = [];
try {
subs = await this.prisma.pluginSubscription.findMany({
where: { tenantId, settingKey: { not: null } },
select: { settingKey: true, expiresAt: true, isActive: true },
});
} catch (error: any) {
this.logger.warn(`remoteStore subs skipped: ${error?.message || error}`);
}
try {
pending = await this.prisma.pluginPurchaseRequest.findMany({
where: { tenantId, status: 'pending' },
select: { catalogItemId: true },
});
} catch (error: any) {
this.logger.warn(`remoteStore pending skipped: ${error?.message || error}`);
}
const pendingIds = new Set(pending.map((p) => p.catalogItemId));
const now = Date.now();
return {
plugin_store_enabled: true,
items: items
.map((item) => {
const enabledPrices = item.prices.filter((p) => p.enabled);
if (!enabledPrices.length) return null;
const mapped = items
.map((item) => {
const enabledPrices = item.prices.filter((p) => this.isCatalogPriceEnabled(p));
if (!enabledPrices.length) return null;
const sub = subs
.filter((s) => s.settingKey === item.settingKey)
.sort((a, b) => (b.expiresAt?.getTime() || 0) - (a.expiresAt?.getTime() || 0))[0];
@@ -553,7 +574,15 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
pendingRequest: hasPending,
};
})
.filter(Boolean),
.filter(Boolean);
this.logger.log(
`remoteStore tenant=${tenantId} catalog=${items.length} listed=${mapped.length}`,
);
return {
plugin_store_enabled: true,
items: mapped,
};
}