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:
238
apps/api/dist/seed.js
vendored
238
apps/api/dist/seed.js
vendored
@@ -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) => {
|
||||
|
||||
2
apps/api/dist/seed.js.map
vendored
2
apps/api/dist/seed.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -13,11 +13,11 @@ export declare class TransactionsController {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: import("@prisma/client/runtime/library").Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
create(req: RequestWithUser, walletId: string, body: {
|
||||
@@ -31,11 +31,11 @@ export declare class TransactionsController {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: import("@prisma/client/runtime/library").Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
exportCsv(req: RequestWithUser, walletId: string, from: string | undefined, to: string | undefined, category: string | undefined, direction: 'in' | 'out' | undefined, res: Response): Promise<void>;
|
||||
@@ -44,11 +44,11 @@ export declare class TransactionsController {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: import("@prisma/client/runtime/library").Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
delete(req: RequestWithUser, walletId: string, id: string): Promise<{
|
||||
@@ -56,11 +56,11 @@ export declare class TransactionsController {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: import("@prisma/client/runtime/library").Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ export declare class TransactionsService {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: Prisma.Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
listAll(userId: string): Prisma.PrismaPromise<{
|
||||
@@ -21,11 +21,11 @@ export declare class TransactionsService {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: Prisma.Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
listWithFilters(userId: string, walletId: string, filters: {
|
||||
@@ -38,11 +38,11 @@ export declare class TransactionsService {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: Prisma.Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
create(userId: string, walletId: string, input: {
|
||||
@@ -56,11 +56,11 @@ export declare class TransactionsService {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: Prisma.Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
update(userId: string, walletId: string, id: string, dto: TransactionUpdateDto): Promise<{
|
||||
@@ -68,11 +68,11 @@ export declare class TransactionsService {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: Prisma.Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
delete(userId: string, walletId: string, id: string): Promise<{
|
||||
@@ -80,11 +80,11 @@ export declare class TransactionsService {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: Prisma.Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
2
apps/api/dist/tsconfig.build.tsbuildinfo
vendored
2
apps/api/dist/tsconfig.build.tsbuildinfo
vendored
File diff suppressed because one or more lines are too long
4
apps/api/dist/users/users.controller.d.ts
vendored
4
apps/api/dist/users/users.controller.d.ts
vendored
@@ -25,6 +25,10 @@ export declare class UsersController {
|
||||
otpWhatsappEnabled: boolean;
|
||||
otpTotpEnabled: boolean;
|
||||
otpTotpSecret: string | null;
|
||||
role: string;
|
||||
suspendedAt: Date | null;
|
||||
suspendedReason: string | null;
|
||||
lastLoginAt: Date | null;
|
||||
} | null>;
|
||||
updateProfile(req: RequestWithUser, body: {
|
||||
name?: string;
|
||||
|
||||
4
apps/api/dist/users/users.service.d.ts
vendored
4
apps/api/dist/users/users.service.d.ts
vendored
@@ -19,6 +19,10 @@ export declare class UsersService {
|
||||
otpWhatsappEnabled: boolean;
|
||||
otpTotpEnabled: boolean;
|
||||
otpTotpSecret: string | null;
|
||||
role: string;
|
||||
suspendedAt: Date | null;
|
||||
suspendedReason: string | null;
|
||||
lastLoginAt: Date | null;
|
||||
} | null>;
|
||||
updateProfile(userId: string, data: {
|
||||
name?: string;
|
||||
|
||||
@@ -27,11 +27,11 @@ export declare class WalletsController {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
walletId: string;
|
||||
date: Date;
|
||||
amount: import("@prisma/client/runtime/library").Decimal;
|
||||
direction: string;
|
||||
date: Date;
|
||||
memo: string | null;
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
create(req: RequestWithUser, body: {
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."User" ADD COLUMN "lastLoginAt" TIMESTAMP(3),
|
||||
ADD COLUMN "role" TEXT NOT NULL DEFAULT 'user',
|
||||
ADD COLUMN "suspendedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "suspendedReason" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Plan" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"price" DECIMAL(10,2) NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'IDR',
|
||||
"durationType" TEXT NOT NULL,
|
||||
"durationDays" INTEGER,
|
||||
"trialDays" INTEGER NOT NULL DEFAULT 7,
|
||||
"features" JSONB NOT NULL,
|
||||
"badge" TEXT,
|
||||
"badgeColor" TEXT,
|
||||
"highlightColor" TEXT,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isVisible" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isFeatured" BOOLEAN NOT NULL DEFAULT false,
|
||||
"maxWallets" INTEGER,
|
||||
"maxGoals" INTEGER,
|
||||
"maxTeamMembers" INTEGER,
|
||||
"apiEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"apiRateLimit" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Plan_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Subscription" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"planId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL,
|
||||
"startDate" TIMESTAMP(3) NOT NULL,
|
||||
"endDate" TIMESTAMP(3) NOT NULL,
|
||||
"isTrialUsed" BOOLEAN NOT NULL DEFAULT false,
|
||||
"trialEndDate" TIMESTAMP(3),
|
||||
"cancelledAt" TIMESTAMP(3),
|
||||
"cancellationReason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Subscription_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Payment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT,
|
||||
"invoiceNumber" TEXT NOT NULL,
|
||||
"amount" DECIMAL(10,2) NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'IDR',
|
||||
"method" TEXT NOT NULL,
|
||||
"tripayReference" TEXT,
|
||||
"tripayFee" DECIMAL(10,2),
|
||||
"totalAmount" DECIMAL(10,2) NOT NULL,
|
||||
"paymentChannel" TEXT,
|
||||
"paymentUrl" TEXT,
|
||||
"qrUrl" TEXT,
|
||||
"status" TEXT NOT NULL,
|
||||
"proofImageUrl" TEXT,
|
||||
"transferDate" TIMESTAMP(3),
|
||||
"verifiedBy" TEXT,
|
||||
"verifiedAt" TIMESTAMP(3),
|
||||
"rejectionReason" TEXT,
|
||||
"couponId" TEXT,
|
||||
"discountAmount" DECIMAL(10,2),
|
||||
"notes" TEXT,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"paidAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Payment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."PaymentMethod" (
|
||||
"id" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"provider" TEXT NOT NULL,
|
||||
"accountName" TEXT NOT NULL,
|
||||
"accountNumber" TEXT NOT NULL,
|
||||
"displayName" TEXT NOT NULL,
|
||||
"logoUrl" TEXT,
|
||||
"instructions" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "PaymentMethod_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Coupon" (
|
||||
"id" TEXT NOT NULL,
|
||||
"code" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"discountType" TEXT NOT NULL,
|
||||
"discountValue" DECIMAL(10,2) NOT NULL,
|
||||
"maxDiscount" DECIMAL(10,2),
|
||||
"validFrom" TIMESTAMP(3) NOT NULL,
|
||||
"validUntil" TIMESTAMP(3) NOT NULL,
|
||||
"maxUses" INTEGER,
|
||||
"usedCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"minPurchase" DECIMAL(10,2),
|
||||
"applicablePlans" TEXT[],
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Coupon_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."AppConfig" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"type" TEXT NOT NULL,
|
||||
"isSecret" BOOLEAN NOT NULL DEFAULT false,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"updatedBy" TEXT,
|
||||
|
||||
CONSTRAINT "AppConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."ApiKey" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"keyHash" TEXT NOT NULL,
|
||||
"prefix" TEXT NOT NULL,
|
||||
"scopes" TEXT[],
|
||||
"lastUsedAt" TIMESTAMP(3),
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"revokedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."ApiKeyUsage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"apiKeyId" TEXT NOT NULL,
|
||||
"endpoint" TEXT NOT NULL,
|
||||
"method" TEXT NOT NULL,
|
||||
"statusCode" INTEGER NOT NULL,
|
||||
"responseTime" INTEGER NOT NULL,
|
||||
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ApiKeyUsage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Webhook" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"events" TEXT[],
|
||||
"secret" TEXT NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"lastTriggeredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Webhook_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."WebhookDelivery" (
|
||||
"id" TEXT NOT NULL,
|
||||
"webhookId" TEXT NOT NULL,
|
||||
"event" TEXT NOT NULL,
|
||||
"payload" JSONB NOT NULL,
|
||||
"status" TEXT NOT NULL,
|
||||
"statusCode" INTEGER,
|
||||
"response" TEXT,
|
||||
"attempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"nextRetryAt" TIMESTAMP(3),
|
||||
"deliveredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "WebhookDelivery_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Plan_slug_key" ON "public"."Plan"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Plan_slug_idx" ON "public"."Plan"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Plan_isActive_idx" ON "public"."Plan"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Plan_isVisible_idx" ON "public"."Plan"("isVisible");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Plan_sortOrder_idx" ON "public"."Plan"("sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Subscription_userId_key" ON "public"."Subscription"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Subscription_userId_idx" ON "public"."Subscription"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Subscription_status_idx" ON "public"."Subscription"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Subscription_endDate_idx" ON "public"."Subscription"("endDate");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Payment_invoiceNumber_key" ON "public"."Payment"("invoiceNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Payment_tripayReference_key" ON "public"."Payment"("tripayReference");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Payment_userId_idx" ON "public"."Payment"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Payment_status_idx" ON "public"."Payment"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Payment_invoiceNumber_idx" ON "public"."Payment"("invoiceNumber");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Payment_tripayReference_idx" ON "public"."Payment"("tripayReference");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PaymentMethod_isActive_idx" ON "public"."PaymentMethod"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "PaymentMethod_sortOrder_idx" ON "public"."PaymentMethod"("sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Coupon_code_key" ON "public"."Coupon"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Coupon_code_idx" ON "public"."Coupon"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Coupon_isActive_idx" ON "public"."Coupon"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AppConfig_key_key" ON "public"."AppConfig"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AppConfig_category_idx" ON "public"."AppConfig"("category");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ApiKey_keyHash_key" ON "public"."ApiKey"("keyHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ApiKey_userId_idx" ON "public"."ApiKey"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ApiKey_keyHash_idx" ON "public"."ApiKey"("keyHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ApiKeyUsage_apiKeyId_timestamp_idx" ON "public"."ApiKeyUsage"("apiKeyId", "timestamp");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Webhook_userId_idx" ON "public"."Webhook"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "WebhookDelivery_webhookId_idx" ON "public"."WebhookDelivery"("webhookId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "WebhookDelivery_status_idx" ON "public"."WebhookDelivery"("status");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Subscription" ADD CONSTRAINT "Subscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Subscription" ADD CONSTRAINT "Subscription_planId_fkey" FOREIGN KEY ("planId") REFERENCES "public"."Plan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Payment" ADD CONSTRAINT "Payment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Payment" ADD CONSTRAINT "Payment_subscriptionId_fkey" FOREIGN KEY ("subscriptionId") REFERENCES "public"."Subscription"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Payment" ADD CONSTRAINT "Payment_couponId_fkey" FOREIGN KEY ("couponId") REFERENCES "public"."Coupon"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."ApiKey" ADD CONSTRAINT "ApiKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."ApiKeyUsage" ADD CONSTRAINT "ApiKeyUsage_apiKeyId_fkey" FOREIGN KEY ("apiKeyId") REFERENCES "public"."ApiKey"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Webhook" ADD CONSTRAINT "Webhook_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."WebhookDelivery" ADD CONSTRAINT "WebhookDelivery_webhookId_fkey" FOREIGN KEY ("webhookId") REFERENCES "public"."Webhook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -26,12 +26,22 @@ model User {
|
||||
otpWhatsappEnabled Boolean @default(false)
|
||||
otpTotpEnabled Boolean @default(false)
|
||||
otpTotpSecret String?
|
||||
// Admin fields
|
||||
role String @default("user") // "user" | "admin"
|
||||
suspendedAt DateTime?
|
||||
suspendedReason String?
|
||||
lastLoginAt DateTime?
|
||||
// Relations
|
||||
authAccounts AuthAccount[]
|
||||
categories Category[]
|
||||
Recurrence Recurrence[]
|
||||
sessions Session[]
|
||||
transactions Transaction[]
|
||||
wallets Wallet[]
|
||||
subscriptions Subscription[]
|
||||
payments Payment[]
|
||||
apiKeys ApiKey[]
|
||||
webhooks Webhook[]
|
||||
}
|
||||
|
||||
model AuthAccount {
|
||||
@@ -129,3 +139,224 @@ model CurrencyRate {
|
||||
@@unique([base, quote, at])
|
||||
@@index([base, quote])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// SUBSCRIPTION & PAYMENT MODELS
|
||||
// ============================================
|
||||
|
||||
model Plan {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
slug String @unique
|
||||
description String?
|
||||
price Decimal @db.Decimal(10, 2)
|
||||
currency String @default("IDR")
|
||||
durationType String // "monthly" | "yearly" | "lifetime" | "custom"
|
||||
durationDays Int?
|
||||
trialDays Int @default(7)
|
||||
features Json
|
||||
badge String?
|
||||
badgeColor String?
|
||||
highlightColor String?
|
||||
sortOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
isVisible Boolean @default(true)
|
||||
isFeatured Boolean @default(false)
|
||||
maxWallets Int?
|
||||
maxGoals Int?
|
||||
maxTeamMembers Int?
|
||||
apiEnabled Boolean @default(false)
|
||||
apiRateLimit Int?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
subscriptions Subscription[]
|
||||
|
||||
@@index([slug])
|
||||
@@index([isActive])
|
||||
@@index([isVisible])
|
||||
@@index([sortOrder])
|
||||
}
|
||||
|
||||
model Subscription {
|
||||
id String @id @default(uuid())
|
||||
userId String @unique
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
planId String
|
||||
plan Plan @relation(fields: [planId], references: [id])
|
||||
status String // "active" | "expired" | "cancelled" | "grace_period"
|
||||
startDate DateTime
|
||||
endDate DateTime
|
||||
isTrialUsed Boolean @default(false)
|
||||
trialEndDate DateTime?
|
||||
cancelledAt DateTime?
|
||||
cancellationReason String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
payments Payment[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@index([endDate])
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
subscriptionId String?
|
||||
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
|
||||
invoiceNumber String @unique
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
currency String @default("IDR")
|
||||
method String
|
||||
tripayReference String? @unique
|
||||
tripayFee Decimal? @db.Decimal(10, 2)
|
||||
totalAmount Decimal @db.Decimal(10, 2)
|
||||
paymentChannel String?
|
||||
paymentUrl String?
|
||||
qrUrl String?
|
||||
status String
|
||||
proofImageUrl String?
|
||||
transferDate DateTime?
|
||||
verifiedBy String?
|
||||
verifiedAt DateTime?
|
||||
rejectionReason String?
|
||||
couponId String?
|
||||
coupon Coupon? @relation(fields: [couponId], references: [id])
|
||||
discountAmount Decimal? @db.Decimal(10, 2)
|
||||
notes String?
|
||||
expiresAt DateTime?
|
||||
paidAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@index([invoiceNumber])
|
||||
@@index([tripayReference])
|
||||
}
|
||||
|
||||
model PaymentMethod {
|
||||
id String @id @default(uuid())
|
||||
type String
|
||||
provider String
|
||||
accountName String
|
||||
accountNumber String
|
||||
displayName String
|
||||
logoUrl String?
|
||||
instructions String?
|
||||
isActive Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([isActive])
|
||||
@@index([sortOrder])
|
||||
}
|
||||
|
||||
model Coupon {
|
||||
id String @id @default(uuid())
|
||||
code String @unique
|
||||
name String
|
||||
description String?
|
||||
discountType String
|
||||
discountValue Decimal @db.Decimal(10, 2)
|
||||
maxDiscount Decimal? @db.Decimal(10, 2)
|
||||
validFrom DateTime
|
||||
validUntil DateTime
|
||||
maxUses Int?
|
||||
usedCount Int @default(0)
|
||||
minPurchase Decimal? @db.Decimal(10, 2)
|
||||
applicablePlans String[]
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
payments Payment[]
|
||||
|
||||
@@index([code])
|
||||
@@index([isActive])
|
||||
}
|
||||
|
||||
model AppConfig {
|
||||
id String @id @default(uuid())
|
||||
key String @unique
|
||||
value String
|
||||
category String
|
||||
label String
|
||||
description String?
|
||||
type String
|
||||
isSecret Boolean @default(false)
|
||||
updatedAt DateTime @updatedAt
|
||||
updatedBy String?
|
||||
|
||||
@@index([category])
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// API & WEBHOOK MODELS
|
||||
// ============================================
|
||||
|
||||
model ApiKey {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
name String
|
||||
keyHash String @unique
|
||||
prefix String
|
||||
scopes String[]
|
||||
lastUsedAt DateTime?
|
||||
expiresAt DateTime?
|
||||
revokedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
usage ApiKeyUsage[]
|
||||
|
||||
@@index([userId])
|
||||
@@index([keyHash])
|
||||
}
|
||||
|
||||
model ApiKeyUsage {
|
||||
id String @id @default(uuid())
|
||||
apiKeyId String
|
||||
apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
|
||||
endpoint String
|
||||
method String
|
||||
statusCode Int
|
||||
responseTime Int
|
||||
timestamp DateTime @default(now())
|
||||
|
||||
@@index([apiKeyId, timestamp])
|
||||
}
|
||||
|
||||
model Webhook {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
url String
|
||||
events String[]
|
||||
secret String
|
||||
isActive Boolean @default(true)
|
||||
lastTriggeredAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deliveries WebhookDelivery[]
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model WebhookDelivery {
|
||||
id String @id @default(uuid())
|
||||
webhookId String
|
||||
webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade)
|
||||
event String
|
||||
payload Json
|
||||
status String
|
||||
statusCode Int?
|
||||
response String?
|
||||
attempts Int @default(0)
|
||||
nextRetryAt DateTime?
|
||||
deliveredAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([webhookId])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
@@ -1,15 +1,237 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
const adminSeeder = {
|
||||
email: 'dwindi.ramadhana@gmail.com',
|
||||
password: 'tabungin2k25!@#',
|
||||
}
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
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...');
|
||||
|
||||
// ============================================
|
||||
// 1. CREATE ADMIN USER
|
||||
// ============================================
|
||||
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);
|
||||
|
||||
// ============================================
|
||||
// 2. CREATE DEFAULT PLANS
|
||||
// ============================================
|
||||
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]);
|
||||
|
||||
// ============================================
|
||||
// 3. CREATE DEFAULT PAYMENT METHODS
|
||||
// ============================================
|
||||
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]);
|
||||
|
||||
// ============================================
|
||||
// 4. CREATE APP CONFIG (Optional)
|
||||
// ============================================
|
||||
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');
|
||||
|
||||
// ============================================
|
||||
// 5. CREATE TEMP USER & WALLET (Legacy)
|
||||
// ============================================
|
||||
console.log('\n🔧 Creating temp user (legacy)...');
|
||||
|
||||
const user = await prisma.user.upsert({
|
||||
where: { id: TEMP_USER_ID },
|
||||
update: {},
|
||||
@@ -19,9 +241,7 @@ async function main() {
|
||||
},
|
||||
});
|
||||
|
||||
// create a sample money wallet if none
|
||||
const existing = await prisma.wallet.findFirst({});
|
||||
|
||||
if (!existing) {
|
||||
await prisma.wallet.create({
|
||||
data: {
|
||||
@@ -32,8 +252,20 @@ async function main() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ Temp user created:', user.id);
|
||||
|
||||
console.log('Seed complete. TEMP_USER_ID=', user.id);
|
||||
// ============================================
|
||||
// SUMMARY
|
||||
// ============================================
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user