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}"
|
MODE="${1:---repair-if-needed}"
|
||||||
|
|
||||||
# Tables that must match publisher for public API (order: parents before children).
|
# 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}"
|
APP_DB_USER="${REPLICA_APP_DB_USER:-montime}"
|
||||||
|
|
||||||
case "$MODE" in
|
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,
|
NotFoundException,
|
||||||
OnModuleDestroy,
|
OnModuleDestroy,
|
||||||
OnModuleInit,
|
OnModuleInit,
|
||||||
|
ServiceUnavailableException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
PluginCatalogItem,
|
PluginCatalogItem,
|
||||||
@@ -271,14 +272,14 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
|
|||||||
create: {
|
create: {
|
||||||
catalogItemId: id,
|
catalogItemId: id,
|
||||||
durationDays: price.durationDays,
|
durationDays: price.durationDays,
|
||||||
enabled: price.enabled,
|
enabled: Boolean(price.enabled),
|
||||||
originalPriceRials: price.originalPriceRials,
|
originalPriceRials: Math.trunc(Number(price.originalPriceRials) || 0),
|
||||||
finalPriceRials: price.finalPriceRials,
|
finalPriceRials: Math.trunc(Number(price.finalPriceRials) || 0),
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
enabled: price.enabled,
|
enabled: Boolean(price.enabled),
|
||||||
originalPriceRials: price.originalPriceRials,
|
originalPriceRials: Math.trunc(Number(price.originalPriceRials) || 0),
|
||||||
finalPriceRials: price.finalPriceRials,
|
finalPriceRials: Math.trunc(Number(price.finalPriceRials) || 0),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -491,33 +492,53 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
|
|||||||
return await this.buildRemoteStore(tenantId);
|
return await this.buildRemoteStore(tenantId);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error instanceof ForbiddenException) throw error;
|
if (error instanceof ForbiddenException) throw error;
|
||||||
this.logger.warn(`remoteStore skipped: ${error?.message || error}`);
|
this.logger.warn(`remoteStore failed: ${error?.message || error}`);
|
||||||
return { plugin_store_enabled: true, items: [] };
|
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) {
|
private async buildRemoteStore(tenantId: string) {
|
||||||
|
await this.seedCatalog();
|
||||||
const items = await this.prisma.pluginCatalogItem.findMany({
|
const items = await this.prisma.pluginCatalogItem.findMany({
|
||||||
where: { inStore: true },
|
where: { inStore: true },
|
||||||
orderBy: { titleFa: 'asc' },
|
orderBy: { titleFa: 'asc' },
|
||||||
include: { prices: { orderBy: { durationDays: 'asc' } } },
|
include: { prices: { orderBy: { durationDays: 'asc' } } },
|
||||||
});
|
});
|
||||||
const subs = await this.prisma.pluginSubscription.findMany({
|
let subs: Array<{
|
||||||
where: { tenantId, settingKey: { not: null } },
|
settingKey: string | null;
|
||||||
});
|
expiresAt: Date | null;
|
||||||
const pending = await this.prisma.pluginPurchaseRequest.findMany({
|
isActive: boolean;
|
||||||
where: { tenantId, status: 'pending' },
|
}> = [];
|
||||||
select: { catalogItemId: true },
|
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 pendingIds = new Set(pending.map((p) => p.catalogItemId));
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
const mapped = items
|
||||||
return {
|
.map((item) => {
|
||||||
plugin_store_enabled: true,
|
const enabledPrices = item.prices.filter((p) => this.isCatalogPriceEnabled(p));
|
||||||
items: items
|
if (!enabledPrices.length) return null;
|
||||||
.map((item) => {
|
|
||||||
const enabledPrices = item.prices.filter((p) => p.enabled);
|
|
||||||
if (!enabledPrices.length) return null;
|
|
||||||
const sub = subs
|
const sub = subs
|
||||||
.filter((s) => s.settingKey === item.settingKey)
|
.filter((s) => s.settingKey === item.settingKey)
|
||||||
.sort((a, b) => (b.expiresAt?.getTime() || 0) - (a.expiresAt?.getTime() || 0))[0];
|
.sort((a, b) => (b.expiresAt?.getTime() || 0) - (a.expiresAt?.getTime() || 0))[0];
|
||||||
@@ -553,7 +574,15 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
|
|||||||
pendingRequest: hasPending,
|
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