Files
filezzy-staging/backend/scripts/seed-app-config.ts
2026-02-04 14:16:04 +01:00

139 lines
15 KiB
TypeScript

/**
* Seed AppConfig (022-runtime-config) with all Tier 2 keys from current config/env.
* Run from backend: npx ts-node scripts/seed-app-config.ts
* Or run as part of: npm run db:seed (if prisma/seed.ts calls this).
*/
import type { PrismaClient } from '@prisma/client';
import { PrismaClient as PrismaClientCtor } from '@prisma/client';
import { config } from '../src/config';
const defaultPrisma = new PrismaClientCtor();
const rateLimitGlobal = config.server.rateLimitMax;
// Per-tier API rate limits (req/min). Guest low to curb abuse; Free moderate; Day pass/Pro generous.
const rateLimitGuest = parseInt(process.env.RATE_LIMIT_GUEST || '60', 10);
const rateLimitFree = parseInt(process.env.RATE_LIMIT_FREE || '120', 10);
const rateLimitDaypass = parseInt(process.env.RATE_LIMIT_DAYPASS || '180', 10);
const rateLimitPro = parseInt(process.env.RATE_LIMIT_PRO || '400', 10);
type Row = {
key: string;
value: unknown;
valueType: 'string' | 'number' | 'boolean' | 'json';
category: string;
description: string | null;
isSensitive: boolean;
isPublic: boolean;
};
const ROWS: Row[] = [
// features
{ key: 'ads_enabled', value: config.features.adsEnabled, valueType: 'boolean', category: 'features', description: 'Master switch: when false, no ads for any tier. When true, per-tier keys (ads_guest, ads_free, etc.) apply.', isSensitive: false, isPublic: true },
{ key: 'ads_guest', value: config.features.adsGuest, valueType: 'string', category: 'features', description: 'Ads level for guest tier: full (all slots), reduced (fewer slots), or none. Fallback: ADS_GUEST_LEVEL.', isSensitive: false, isPublic: true },
{ key: 'ads_free', value: config.features.adsFree, valueType: 'string', category: 'features', description: 'Ads level for free tier: full, reduced, or none. Fallback: ADS_FREE_LEVEL.', isSensitive: false, isPublic: true },
{ key: 'ads_daypass', value: config.features.adsDaypass, valueType: 'string', category: 'features', description: 'Ads level for day pass tier: full, reduced, or none. Fallback: ADS_DAYPASS_LEVEL.', isSensitive: false, isPublic: true },
{ key: 'ads_pro', value: config.features.adsPro, valueType: 'string', category: 'features', description: 'Ads level for pro tier: full, reduced, or none. Fallback: ADS_PRO_LEVEL.', isSensitive: false, isPublic: true },
{ key: 'maintenance_mode', value: false, valueType: 'boolean', category: 'features', description: 'When true, API returns 503 for non-admin routes.', isSensitive: false, isPublic: true },
{ key: 'registration_open', value: config.features.registrationEnabled, valueType: 'boolean', category: 'features', description: 'Allow new user registration.', isSensitive: false, isPublic: true },
{ key: 'payments_enabled', value: config.features.paymentsEnabled, valueType: 'boolean', category: 'features', description: 'Enable payments.', isSensitive: false, isPublic: true },
{ key: 'premium_tools_enabled', value: config.features.premiumToolsEnabled, valueType: 'boolean', category: 'features', description: 'Gate premium tools by tier.', isSensitive: false, isPublic: true },
{ key: 'paddle_enabled', value: config.features.paddleEnabled, valueType: 'boolean', category: 'features', description: 'Enable Paddle payment provider.', isSensitive: false, isPublic: true },
{ key: 'social_auth_enabled', value: config.features.socialAuthEnabled, valueType: 'boolean', category: 'features', description: 'Enable social login (Google, etc.).', isSensitive: false, isPublic: true },
{ key: 'batch_processing_enabled', value: config.batch.batchProcessingEnabled, valueType: 'boolean', category: 'features', description: 'Enable batch upload feature.', isSensitive: false, isPublic: true },
{ key: 'tier_enabled_guest', value: true, valueType: 'boolean', category: 'features', description: 'Enable guest tier (unauthenticated users).', isSensitive: false, isPublic: true },
{ key: 'tier_enabled_free', value: true, valueType: 'boolean', category: 'features', description: 'Enable free tier (registered, no subscription).', isSensitive: false, isPublic: true },
{ key: 'tier_enabled_daypass', value: true, valueType: 'boolean', category: 'features', description: 'Enable day pass tier (temporary premium).', isSensitive: false, isPublic: true },
{ key: 'tier_enabled_pro', value: true, valueType: 'boolean', category: 'features', description: 'Enable pro tier (subscription).', isSensitive: false, isPublic: true },
// limits - tier
{ key: 'max_file_size_mb_guest', value: config.limits.guest.maxFileSizeMb, valueType: 'number', category: 'limits', description: 'Guest tier max file size (MB).', isSensitive: false, isPublic: true },
{ key: 'max_file_size_mb_free', value: config.limits.free.maxFileSizeMb, valueType: 'number', category: 'limits', description: 'Free tier max file size (MB).', isSensitive: false, isPublic: true },
{ key: 'max_file_size_mb_daypass', value: config.limits.dayPass.maxFileSizeMb, valueType: 'number', category: 'limits', description: 'Day Pass tier max file size (MB).', isSensitive: false, isPublic: true },
{ key: 'max_file_size_mb_pro', value: config.limits.pro.maxFileSizeMb, valueType: 'number', category: 'limits', description: 'Pro tier max file size (MB).', isSensitive: false, isPublic: true },
{ key: 'max_files_per_batch_guest', value: config.limits.guest.maxFilesPerBatch, valueType: 'number', category: 'limits', description: 'Guest tier max files per batch.', isSensitive: false, isPublic: true },
{ key: 'max_files_per_batch_free', value: config.limits.free.maxFilesPerBatch, valueType: 'number', category: 'limits', description: 'Free tier max files per batch.', isSensitive: false, isPublic: true },
{ key: 'max_files_per_batch_daypass', value: config.limits.dayPass.maxFilesPerBatch, valueType: 'number', category: 'limits', description: 'Day Pass tier max files per batch.', isSensitive: false, isPublic: true },
{ key: 'max_files_per_batch_pro', value: config.limits.pro.maxFilesPerBatch, valueType: 'number', category: 'limits', description: 'Pro tier max files per batch.', isSensitive: false, isPublic: true },
{ key: 'max_batch_size_mb_guest', value: config.limits.guest.maxBatchSizeMb, valueType: 'number', category: 'limits', description: 'Guest tier max batch size (MB).', isSensitive: false, isPublic: true },
{ key: 'max_batch_size_mb_free', value: config.limits.free.maxBatchSizeMb, valueType: 'number', category: 'limits', description: 'Free tier max batch size (MB).', isSensitive: false, isPublic: true },
{ key: 'max_batch_size_mb_daypass', value: config.limits.dayPass.maxBatchSizeMb, valueType: 'number', category: 'limits', description: 'Day Pass tier max batch size (MB).', isSensitive: false, isPublic: true },
{ key: 'max_batch_size_mb_pro', value: config.limits.pro.maxBatchSizeMb, valueType: 'number', category: 'limits', description: 'Pro tier max batch size (MB).', isSensitive: false, isPublic: true },
{ key: 'max_ops_per_day_guest', value: config.ops.guest.maxOpsPerDay, valueType: 'number', category: 'limits', description: 'Daily operations limit for guests.', isSensitive: false, isPublic: true },
{ key: 'max_ops_per_day_free', value: config.ops.free.maxOpsPerDay, valueType: 'number', category: 'limits', description: 'Daily operations limit for free users.', isSensitive: false, isPublic: true },
{ key: 'max_ops_per_24h_daypass', value: config.ops.dayPass.maxOpsPer24h, valueType: 'number', category: 'limits', description: 'Operations per 24h for day pass.', isSensitive: false, isPublic: true },
{ key: 'retention_hours_guest', value: config.retention.guestHours, valueType: 'number', category: 'limits', description: 'File retention (hours until MinIO files deleted) for guest.', isSensitive: false, isPublic: false },
{ key: 'retention_hours_free', value: config.retention.freeHours, valueType: 'number', category: 'limits', description: 'File retention (hours) for free tier.', isSensitive: false, isPublic: false },
{ key: 'retention_hours_daypass', value: config.retention.dayPassHours, valueType: 'number', category: 'limits', description: 'File retention (hours) for day pass.', isSensitive: false, isPublic: false },
{ key: 'retention_hours_pro', value: config.retention.proHours, valueType: 'number', category: 'limits', description: 'File retention (hours) for pro tier.', isSensitive: false, isPublic: false },
// email
{ key: 'email_subscription_expiring_enabled', value: config.email.featureFlags.subscriptionExpiringSoonEnabled, valueType: 'boolean', category: 'email', description: 'Send subscription renewal reminders.', isSensitive: false, isPublic: false },
// limits - batch
{ key: 'max_files_per_batch', value: config.batch.maxFilesPerBatch, valueType: 'number', category: 'limits', description: 'Max files per batch job.', isSensitive: false, isPublic: true },
{ key: 'max_batch_size_mb', value: config.batch.maxBatchSizeMb, valueType: 'number', category: 'limits', description: 'Max total size of all files in a batch (MB), premium.', isSensitive: false, isPublic: true },
{ key: 'max_batch_size_mb_free', value: config.batch.maxBatchSizeMbFree, valueType: 'number', category: 'limits', description: 'Max total batch size (MB) for free/guest users.', isSensitive: false, isPublic: true },
{ key: 'batch_expiration_hours', value: config.batch.batchExpirationHours, valueType: 'number', category: 'limits', description: 'How long batch jobs are kept (hours).', isSensitive: false, isPublic: false },
{ key: 'max_batch_files', value: config.batch.maxBatchFiles, valueType: 'number', category: 'limits', description: 'Max files per PDF batch job; API returns 400 if exceeded.', isSensitive: false, isPublic: true },
// rate limits (per tier: guest low to curb abuse, free moderate, day pass/pro generous)
{ key: 'rate_limit_global_max', value: rateLimitGlobal, valueType: 'number', category: 'limits', description: 'Max requests per minute per IP (global fallback).', isSensitive: false, isPublic: false },
{ key: 'rate_limit_guest', value: rateLimitGuest, valueType: 'number', category: 'limits', description: 'API rate limit (req/min) for guest tier. Default 60; env RATE_LIMIT_GUEST.', isSensitive: false, isPublic: false },
{ key: 'rate_limit_free', value: rateLimitFree, valueType: 'number', category: 'limits', description: 'API rate limit (req/min) for free tier. Default 120; env RATE_LIMIT_FREE.', isSensitive: false, isPublic: false },
{ key: 'rate_limit_daypass', value: rateLimitDaypass, valueType: 'number', category: 'limits', description: 'API rate limit (req/min) for day pass tier. Default 180; env RATE_LIMIT_DAYPASS.', isSensitive: false, isPublic: false },
{ key: 'rate_limit_pro', value: rateLimitPro, valueType: 'number', category: 'limits', description: 'API rate limit (req/min) for pro tier. Default 400; env RATE_LIMIT_PRO.', isSensitive: false, isPublic: false },
// pricing
{ key: 'day_pass_price_usd', value: config.prices.dayPassUsd, valueType: 'string', category: 'pricing', description: 'Display price for day pass (must match Paddle catalog).', isSensitive: false, isPublic: true },
{ key: 'pro_monthly_price_usd', value: config.prices.proMonthlyUsd, valueType: 'string', category: 'pricing', description: 'Display price for Pro monthly (must match Paddle catalog).', isSensitive: false, isPublic: true },
{ key: 'pro_yearly_price_usd', value: config.prices.proYearlyUsd, valueType: 'string', category: 'pricing', description: 'Display price for Pro yearly (must match Paddle catalog).', isSensitive: false, isPublic: true },
// ui
{ key: 'announcement_enabled', value: false, valueType: 'boolean', category: 'ui', description: 'Show announcement banner on site.', isSensitive: false, isPublic: true },
{ key: 'announcement_message', value: '', valueType: 'string', category: 'ui', description: 'Announcement banner text (shown when announcement_enabled is true).', isSensitive: false, isPublic: true },
{ key: 'announcement_type', value: 'info', valueType: 'string', category: 'ui', description: 'Banner type: info, warning, or success.', isSensitive: false, isPublic: true },
{ key: 'arabic_enabled', value: false, valueType: 'boolean', category: 'ui', description: 'Enable Arabic language support (RTL). When true, Arabic appears in language switcher and /ar routes work.', isSensitive: false, isPublic: true },
{ key: 'support_email', value: config.email.resend.replyToEmail, valueType: 'string', category: 'ui', description: 'Support / reply-to email shown to users (e.g. footer, contact).', isSensitive: false, isPublic: true },
// seo (no env in backend; use empty or from process.env for seed)
{ key: 'google_analytics_id', value: process.env.NEXT_PUBLIC_GA_ID ?? '', valueType: 'string', category: 'seo', description: 'Google Analytics 4 measurement ID.', isSensitive: false, isPublic: true },
{ key: 'gtm_id', value: process.env.NEXT_PUBLIC_GTAG_ID ?? '', valueType: 'string', category: 'seo', description: 'Google Tag ID.', isSensitive: false, isPublic: true },
{ key: 'default_meta_title', value: process.env.NEXT_PUBLIC_SITE_NAME ?? 'Filezzy', valueType: 'string', category: 'seo', description: 'Default meta title for pages.', isSensitive: false, isPublic: true },
{ key: 'default_meta_desc', value: process.env.NEXT_PUBLIC_SITE_DESCRIPTION ?? 'Transform any file in seconds.', valueType: 'string', category: 'seo', description: 'Default meta description for pages.', isSensitive: false, isPublic: true },
// admin
{ key: 'admin_dashboard_enabled', value: config.admin.dashboardEnabled, valueType: 'boolean', category: 'admin', description: 'Enable admin API (false = 403 for all admin routes).', isSensitive: false, isPublic: false },
{ key: 'admin_email_batch_limit', value: config.email.adminEmailBatchLimit, valueType: 'number', category: 'admin', description: 'Max recipients per admin batch send (e.g. email campaigns).', isSensitive: false, isPublic: false },
];
export async function seedAppConfig(client?: PrismaClient): Promise<void> {
const prisma = client ?? defaultPrisma;
for (const row of ROWS) {
await prisma.appConfig.upsert({
where: { key: row.key },
create: {
key: row.key,
value: row.value as object,
valueType: row.valueType,
category: row.category,
description: row.description,
isSensitive: row.isSensitive,
isPublic: row.isPublic,
},
update: {
value: row.value as object,
valueType: row.valueType,
category: row.category,
description: row.description,
isSensitive: row.isSensitive,
isPublic: row.isPublic,
},
});
}
}
async function main() {
console.log('Seeding AppConfig...');
await seedAppConfig();
console.log(`AppConfig: ${ROWS.length} keys upserted.`);
}
main()
.catch((e) => {
console.error('Seed app config failed:', e);
process.exit(1);
})
.finally(() => defaultPrisma.$disconnect());