feat: add admin dashboard schema and seeder

- Add Plan, Subscription, Payment, PaymentMethod, Coupon models
- Add ApiKey, Webhook models for API access
- Add AppConfig model for dynamic configuration
- Add role, suspendedAt fields to User model
- Create comprehensive seeder with:
  - Admin user (dwindi.ramadhana@gmail.com)
  - Default plans (Free, Pro Monthly, Pro Yearly)
  - Payment methods (BCA, Mandiri, GoPay)
  - App config (maintenance mode)
- Zero data loss migration strategy
This commit is contained in:
dwindown
2025-10-11 14:06:55 +07:00
parent 249f3a9d7d
commit c3bc181063
11 changed files with 1052 additions and 37 deletions

238
apps/api/dist/seed.js vendored
View File

@@ -1,13 +1,233 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const client_1 = require("@prisma/client");
const bcrypt = __importStar(require("bcrypt"));
const prisma = new client_1.PrismaClient();
const adminSeeder = {
email: 'dwindi.ramadhana@gmail.com',
password: 'tabungin2k25!@#',
};
const TEMP_USER_ID = process.env.TEMP_USER_ID || '16b74848-daa3-4dc9-8de2-3cf59e08f8e3';
const ADMIN_EMAIL = 'dwindi.ramadhana@gmail.com';
const ADMIN_PASSWORD = 'tabungin2k25!@#';
async function main() {
console.log('🌱 Starting seed...');
console.log('\n👤 Creating admin user...');
const passwordHash = await bcrypt.hash(ADMIN_PASSWORD, 10);
const admin = await prisma.user.upsert({
where: { email: ADMIN_EMAIL },
update: {
role: 'admin',
passwordHash,
emailVerified: true,
},
create: {
email: ADMIN_EMAIL,
passwordHash,
name: 'Dwindi Ramadhana',
role: 'admin',
emailVerified: true,
},
});
console.log('✅ Admin user created:', admin.email);
console.log('\n💰 Creating default plans...');
const freePlan = await prisma.plan.upsert({
where: { slug: 'free' },
update: {},
create: {
name: 'Free',
slug: 'free',
description: 'Perfect for getting started',
price: 0,
currency: 'IDR',
durationType: 'lifetime',
durationDays: null,
trialDays: 0,
features: {
wallets: { limit: 5, label: '5 wallets' },
goals: { limit: 3, label: '3 goals' },
team: { enabled: false, label: 'No team feature' },
api: { enabled: false, label: 'No API access' },
support: { level: 'basic', label: 'Basic support' },
export: { enabled: true, formats: ['csv'], label: 'CSV export' },
},
badge: null,
sortOrder: 1,
isActive: true,
isVisible: true,
isFeatured: false,
maxWallets: 5,
maxGoals: 3,
maxTeamMembers: 0,
apiEnabled: false,
apiRateLimit: null,
},
});
const proMonthly = await prisma.plan.upsert({
where: { slug: 'pro-monthly' },
update: {},
create: {
name: 'Pro Monthly',
slug: 'pro-monthly',
description: 'Perfect for individuals and small teams',
price: 49000,
currency: 'IDR',
durationType: 'monthly',
durationDays: 30,
trialDays: 7,
features: {
wallets: { limit: null, label: 'Unlimited wallets' },
goals: { limit: null, label: 'Unlimited goals' },
team: { enabled: true, maxMembers: 10, label: 'Team feature (10 members)' },
api: { enabled: true, rateLimit: 1000, label: 'API access (1000 req/hr)' },
support: { level: 'priority', label: 'Priority support' },
export: { enabled: true, formats: ['csv', 'excel', 'pdf'], label: 'All export formats' },
},
badge: 'Popular',
badgeColor: 'blue',
highlightColor: '#3B82F6',
sortOrder: 2,
isActive: true,
isVisible: true,
isFeatured: true,
maxWallets: null,
maxGoals: null,
maxTeamMembers: 10,
apiEnabled: true,
apiRateLimit: 1000,
},
});
const proYearly = await prisma.plan.upsert({
where: { slug: 'pro-yearly' },
update: {},
create: {
name: 'Pro Yearly',
slug: 'pro-yearly',
description: 'Best value - Save 17% with annual billing',
price: 490000,
currency: 'IDR',
durationType: 'yearly',
durationDays: 365,
trialDays: 7,
features: {
wallets: { limit: null, label: 'Unlimited wallets' },
goals: { limit: null, label: 'Unlimited goals' },
team: { enabled: true, maxMembers: 10, label: 'Team feature (10 members)' },
api: { enabled: true, rateLimit: 1000, label: 'API access (1000 req/hr)' },
support: { level: 'priority', label: 'Priority support' },
export: { enabled: true, formats: ['csv', 'excel', 'pdf'], label: 'All export formats' },
discount: { value: '17%', label: 'Save 17% (2 months free)' },
},
badge: 'Best Value',
badgeColor: 'green',
highlightColor: '#10B981',
sortOrder: 3,
isActive: true,
isVisible: true,
isFeatured: true,
maxWallets: null,
maxGoals: null,
maxTeamMembers: 10,
apiEnabled: true,
apiRateLimit: 1000,
},
});
console.log('✅ Plans created:', [freePlan.name, proMonthly.name, proYearly.name]);
console.log('\n💳 Creating default payment methods...');
const bcaMethod = await prisma.paymentMethod.upsert({
where: { id: 'bca-method' },
update: {},
create: {
id: 'bca-method',
type: 'bank_transfer',
provider: 'BCA',
accountName: 'PT Tabungin Indonesia',
accountNumber: '1234567890',
displayName: 'BCA Virtual Account',
logoUrl: '/logos/bca.png',
instructions: 'Transfer to the account above and upload proof of payment.',
isActive: true,
sortOrder: 1,
},
});
const mandiriMethod = await prisma.paymentMethod.upsert({
where: { id: 'mandiri-method' },
update: {},
create: {
id: 'mandiri-method',
type: 'bank_transfer',
provider: 'Mandiri',
accountName: 'PT Tabungin Indonesia',
accountNumber: '9876543210',
displayName: 'Mandiri Virtual Account',
logoUrl: '/logos/mandiri.png',
instructions: 'Transfer to the account above and upload proof of payment.',
isActive: true,
sortOrder: 2,
},
});
const gopayMethod = await prisma.paymentMethod.upsert({
where: { id: 'gopay-method' },
update: {},
create: {
id: 'gopay-method',
type: 'e-wallet',
provider: 'GoPay',
accountName: 'Dwindi Ramadhana',
accountNumber: '081234567890',
displayName: 'GoPay',
logoUrl: '/logos/gopay.png',
instructions: 'Send payment to the number above and upload proof.',
isActive: true,
sortOrder: 3,
},
});
console.log('✅ Payment methods created:', [bcaMethod.displayName, mandiriMethod.displayName, gopayMethod.displayName]);
console.log('\n⚙ Creating app config...');
await prisma.appConfig.upsert({
where: { key: 'MAINTENANCE_MODE' },
update: {},
create: {
key: 'MAINTENANCE_MODE',
value: 'false',
category: 'general',
label: 'Maintenance Mode',
description: 'Enable to show maintenance page to users',
type: 'boolean',
isSecret: false,
},
});
console.log('✅ App config created');
console.log('\n🔧 Creating temp user (legacy)...');
const user = await prisma.user.upsert({
where: { id: TEMP_USER_ID },
update: {},
@@ -27,7 +247,15 @@ async function main() {
},
});
}
console.log('Seed complete. TEMP_USER_ID=', user.id);
console.log('✅ Temp user created:', user.id);
console.log('\n🎉 Seed complete!');
console.log('\n📋 Summary:');
console.log(' Admin Email:', ADMIN_EMAIL);
console.log(' Admin Password:', ADMIN_PASSWORD);
console.log(' Plans:', [freePlan.name, proMonthly.name, proYearly.name].join(', '));
console.log(' Payment Methods:', [bcaMethod.displayName, mandiriMethod.displayName, gopayMethod.displayName].join(', '));
console.log('\n⚠ IMPORTANT: Change admin password after first login!');
console.log('\n🔗 Login at: http://localhost:5174/auth/login');
}
main()
.catch((e) => {