fix pluggin bug
This commit is contained in:
@@ -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 $$;
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user