Initial commit
This commit is contained in:
231
prisma/migrations/20260204030939_init/migration.sql
Normal file
231
prisma/migrations/20260204030939_init/migration.sql
Normal file
@@ -0,0 +1,231 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('ADMIN', 'USER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "RegistrationStatus" AS ENUM ('CONFIRMED', 'PAYMENT_PENDING', 'PAID', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WebinarVisibility" AS ENUM ('PUBLIC', 'PRIVATE');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"image" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"role" "Role" NOT NULL DEFAULT 'USER',
|
||||
"firstName" TEXT,
|
||||
"lastName" TEXT,
|
||||
"gender" TEXT,
|
||||
"dob" TIMESTAMP(3),
|
||||
"address" TEXT,
|
||||
"forcePasswordReset" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Account" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL DEFAULT 'oauth',
|
||||
"provider" TEXT NOT NULL,
|
||||
"providerAccountId" TEXT NOT NULL,
|
||||
"refreshToken" TEXT,
|
||||
"accessToken" TEXT,
|
||||
"expiresAt" INTEGER,
|
||||
"tokenType" TEXT,
|
||||
"scope" TEXT,
|
||||
"idToken" TEXT,
|
||||
"sessionState" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Credential" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Credential_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Session" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionToken" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"expires" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Verification" (
|
||||
"id" TEXT NOT NULL,
|
||||
"identifier" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"userId" TEXT,
|
||||
|
||||
CONSTRAINT "Verification_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SystemConfig" (
|
||||
"id" INTEGER NOT NULL,
|
||||
"data" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SystemConfig_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AppSetup" (
|
||||
"id" INTEGER NOT NULL,
|
||||
"googleAuthEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"googleClientId" TEXT,
|
||||
"googleClientSecret" TEXT,
|
||||
"socials" JSONB NOT NULL DEFAULT '{}',
|
||||
"categories" JSONB NOT NULL DEFAULT '[]',
|
||||
"paginationItemsPerPage" INTEGER NOT NULL DEFAULT 10,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AppSetup_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Webinar" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"speaker" TEXT NOT NULL,
|
||||
"startAt" TIMESTAMP(3) NOT NULL,
|
||||
"duration" INTEGER NOT NULL,
|
||||
"bannerUrl" TEXT,
|
||||
"category" TEXT NOT NULL,
|
||||
"visibility" "WebinarVisibility" NOT NULL DEFAULT 'PUBLIC',
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"capacity" INTEGER NOT NULL,
|
||||
"priceCents" INTEGER NOT NULL DEFAULT 0,
|
||||
"meetingInfo" JSONB NOT NULL DEFAULT '{}',
|
||||
"learningPoints" JSONB NOT NULL DEFAULT '[]',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Webinar_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "WebinarRegistration" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"webinarId" TEXT NOT NULL,
|
||||
"status" "RegistrationStatus" NOT NULL DEFAULT 'CONFIRMED',
|
||||
"stripeCheckoutSessionId" TEXT,
|
||||
"stripePaymentIntentId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "WebinarRegistration_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ContactMessage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"subject" TEXT NOT NULL,
|
||||
"message" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'NEW',
|
||||
"adminNote" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"authorId" TEXT,
|
||||
|
||||
CONSTRAINT "ContactMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Account_userId_idx" ON "Account"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Credential_userId_key" ON "Credential"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Credential_userId_idx" ON "Credential"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Verification_userId_idx" ON "Verification"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Verification_identifier_value_key" ON "Verification"("identifier", "value");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Webinar_visibility_idx" ON "Webinar"("visibility");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Webinar_isActive_idx" ON "Webinar"("isActive");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "WebinarRegistration_userId_idx" ON "WebinarRegistration"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "WebinarRegistration_webinarId_idx" ON "WebinarRegistration"("webinarId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "WebinarRegistration_userId_webinarId_key" ON "WebinarRegistration"("userId", "webinarId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ContactMessage_status_idx" ON "ContactMessage"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ContactMessage_createdAt_idx" ON "ContactMessage"("createdAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Credential" ADD CONSTRAINT "Credential_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Verification" ADD CONSTRAINT "Verification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "WebinarRegistration" ADD CONSTRAINT "WebinarRegistration_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "WebinarRegistration" ADD CONSTRAINT "WebinarRegistration_webinarId_fkey" FOREIGN KEY ("webinarId") REFERENCES "Webinar"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ContactMessage" ADD CONSTRAINT "ContactMessage_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
202
prisma/schema.prisma
Normal file
202
prisma/schema.prisma
Normal file
@@ -0,0 +1,202 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ADMIN
|
||||
USER
|
||||
}
|
||||
|
||||
enum RegistrationStatus {
|
||||
CONFIRMED
|
||||
PAYMENT_PENDING
|
||||
PAID
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum WebinarVisibility {
|
||||
PUBLIC
|
||||
PRIVATE
|
||||
}
|
||||
|
||||
// BetterAuth User model
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String @unique
|
||||
emailVerified Boolean @default(false)
|
||||
image String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Custom fields
|
||||
role Role @default(USER)
|
||||
firstName String?
|
||||
lastName String?
|
||||
gender String?
|
||||
dob DateTime?
|
||||
address String?
|
||||
forcePasswordReset Boolean @default(false)
|
||||
isActive Boolean @default(true)
|
||||
|
||||
// BetterAuth relations
|
||||
accounts Account[]
|
||||
credential Credential?
|
||||
sessions Session[]
|
||||
verifications Verification[]
|
||||
|
||||
// Custom relations
|
||||
registrations WebinarRegistration[]
|
||||
contactMessages ContactMessage[] @relation("messageAuthor")
|
||||
}
|
||||
|
||||
// BetterAuth Account model (OAuth and email/password accounts)
|
||||
// Note: For email/password, credentials are stored differently by BetterAuth
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
type String @default("oauth")
|
||||
provider String
|
||||
providerAccountId String
|
||||
refreshToken String?
|
||||
accessToken String?
|
||||
expiresAt Int?
|
||||
tokenType String?
|
||||
scope String?
|
||||
idToken String?
|
||||
sessionState String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// Credential table for email/password authentication
|
||||
model Credential {
|
||||
id String @id @default(cuid())
|
||||
userId String @unique
|
||||
password String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// BetterAuth Session model
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
sessionToken String @unique
|
||||
userId String
|
||||
expires DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// BetterAuth Verification model
|
||||
model Verification {
|
||||
id String @id @default(cuid())
|
||||
identifier String
|
||||
value String
|
||||
expiresAt DateTime
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String?
|
||||
|
||||
@@unique([identifier, value])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model SystemConfig {
|
||||
id Int @id
|
||||
data Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model AppSetup {
|
||||
id Int @id
|
||||
googleAuthEnabled Boolean @default(false)
|
||||
googleClientId String?
|
||||
googleClientSecret String?
|
||||
socials Json @default("{}")
|
||||
categories Json @default("[]")
|
||||
paginationItemsPerPage Int @default(10)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Webinar {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
description String
|
||||
speaker String
|
||||
startAt DateTime
|
||||
duration Int
|
||||
bannerUrl String?
|
||||
category String
|
||||
visibility WebinarVisibility @default(PUBLIC)
|
||||
isActive Boolean @default(true)
|
||||
capacity Int
|
||||
priceCents Int @default(0)
|
||||
meetingInfo Json @default("{}")
|
||||
learningPoints Json @default("[]")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
registrations WebinarRegistration[]
|
||||
|
||||
@@index([visibility])
|
||||
@@index([isActive])
|
||||
}
|
||||
|
||||
model WebinarRegistration {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
webinarId String
|
||||
status RegistrationStatus @default(CONFIRMED)
|
||||
stripeCheckoutSessionId String?
|
||||
stripePaymentIntentId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
webinar Webinar @relation(fields: [webinarId], references: [id])
|
||||
|
||||
@@unique([userId, webinarId])
|
||||
@@index([userId])
|
||||
@@index([webinarId])
|
||||
}
|
||||
|
||||
model ContactMessage {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
email String
|
||||
subject String
|
||||
message String
|
||||
status String @default("NEW") // NEW, READ, REPLIED
|
||||
adminNote String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
authorId String?
|
||||
|
||||
author User? @relation("messageAuthor", fields: [authorId], references: [id])
|
||||
|
||||
@@index([status])
|
||||
@@index([createdAt])
|
||||
}
|
||||
171
prisma/seed.mjs
Normal file
171
prisma/seed.mjs
Normal file
@@ -0,0 +1,171 @@
|
||||
import bcrypt from "bcryptjs";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const adminEmail = "admin@ywyw.com";
|
||||
const customerEmail = "cust@ywyw.com";
|
||||
|
||||
const passwordHash = await bcrypt.hash("Dev1234#", 10);
|
||||
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { email: adminEmail },
|
||||
update: {},
|
||||
create: {
|
||||
email: adminEmail,
|
||||
name: "Admin User",
|
||||
role: "ADMIN",
|
||||
firstName: "Admin",
|
||||
lastName: "User",
|
||||
gender: "OTHER",
|
||||
emailVerified: true,
|
||||
forcePasswordReset: false,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.credential.upsert({
|
||||
where: { userId: admin.id },
|
||||
update: { password: passwordHash },
|
||||
create: { userId: admin.id, password: passwordHash },
|
||||
});
|
||||
|
||||
console.log("✅ Admin user created:", adminEmail);
|
||||
|
||||
const customer = await prisma.user.upsert({
|
||||
where: { email: customerEmail },
|
||||
update: {},
|
||||
create: {
|
||||
email: customerEmail,
|
||||
name: "Customer User",
|
||||
role: "USER",
|
||||
firstName: "Customer",
|
||||
lastName: "User",
|
||||
gender: "OTHER",
|
||||
emailVerified: true,
|
||||
forcePasswordReset: false,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.credential.upsert({
|
||||
where: { userId: customer.id },
|
||||
update: { password: passwordHash },
|
||||
create: { userId: customer.id, password: passwordHash },
|
||||
});
|
||||
|
||||
console.log("✅ Customer user created:", customerEmail);
|
||||
|
||||
const existingWebinars = await prisma.webinar.count();
|
||||
if (existingWebinars === 0) {
|
||||
const now = Date.now();
|
||||
const sample = [
|
||||
{
|
||||
title: "Estate Planning Fundamentals",
|
||||
description: "Learn the foundations of estate planning, wills, and trusts.",
|
||||
speaker: "Emily Roberts",
|
||||
startAt: new Date(now + 7 * 86400000),
|
||||
duration: 90,
|
||||
bannerUrl: null,
|
||||
category: "Basics",
|
||||
visibility: "PUBLIC",
|
||||
isActive: true,
|
||||
capacity: 50,
|
||||
priceCents: 0,
|
||||
learningPoints: [
|
||||
"Understanding the basics of wills and trusts",
|
||||
"Key differences between revocable and irrevocable trusts",
|
||||
"Common estate planning mistakes to avoid",
|
||||
"When to update your estate plan",
|
||||
],
|
||||
meetingInfo: {},
|
||||
},
|
||||
{
|
||||
title: "Avoiding Probate: Strategies & Solutions",
|
||||
description: "Practical strategies to reduce or avoid probate delays.",
|
||||
speaker: "David Martinez",
|
||||
startAt: new Date(now + 10 * 86400000),
|
||||
duration: 75,
|
||||
bannerUrl: null,
|
||||
category: "Planning",
|
||||
visibility: "PUBLIC",
|
||||
isActive: true,
|
||||
capacity: 60,
|
||||
priceCents: 0,
|
||||
learningPoints: [
|
||||
"How probate works and why it can be costly",
|
||||
"Living trusts as probate avoidance tools",
|
||||
"Joint ownership strategies",
|
||||
"Beneficiary designations and their importance",
|
||||
],
|
||||
meetingInfo: {},
|
||||
},
|
||||
{
|
||||
title: "Tax-Efficient Wealth Transfer",
|
||||
description: "Minimize taxes when transferring assets to heirs.",
|
||||
speaker: "Susan Chen",
|
||||
startAt: new Date(now + 14 * 86400000),
|
||||
duration: 60,
|
||||
bannerUrl: null,
|
||||
category: "Advanced",
|
||||
visibility: "PUBLIC",
|
||||
isActive: true,
|
||||
capacity: 40,
|
||||
priceCents: 0,
|
||||
learningPoints: [
|
||||
"Federal and state estate tax basics",
|
||||
"Gift tax exclusions and lifetime exemptions",
|
||||
"Charitable giving strategies",
|
||||
"Generation-skipping transfer tax considerations",
|
||||
],
|
||||
meetingInfo: {},
|
||||
},
|
||||
];
|
||||
|
||||
for (const w of sample) {
|
||||
await prisma.webinar.create({ data: w });
|
||||
}
|
||||
|
||||
console.log(`✅ Created ${sample.length} sample webinars`);
|
||||
} else {
|
||||
console.log(`ℹ️ Skipped webinar seeding - ${existingWebinars} already exist`);
|
||||
}
|
||||
|
||||
const existingRegistrations = await prisma.webinarRegistration.count();
|
||||
if (existingRegistrations === 0) {
|
||||
const webinars = await prisma.webinar.findMany({ take: 2 });
|
||||
if (webinars.length >= 2) {
|
||||
await prisma.webinarRegistration.create({
|
||||
data: {
|
||||
userId: customer.id,
|
||||
webinarId: webinars[0].id,
|
||||
status: "CONFIRMED",
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.webinarRegistration.create({
|
||||
data: {
|
||||
userId: customer.id,
|
||||
webinarId: webinars[1].id,
|
||||
status: "CONFIRMED",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("✅ Created sample registrations for customer");
|
||||
}
|
||||
} else {
|
||||
console.log(`ℹ️ Skipped registration seeding - ${existingRegistrations} already exist`);
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
console.log("✅ Seeding completed successfully");
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error("❌ Seeding failed:", e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
191
prisma/seed.ts
Normal file
191
prisma/seed.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import bcrypt from "bcryptjs";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const adminEmail = "admin@ywyw.com";
|
||||
const customerEmail = "cust@ywyw.com";
|
||||
|
||||
const passwordHash = await bcrypt.hash("Dev1234#", 10);
|
||||
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { email: adminEmail },
|
||||
update: {},
|
||||
create: {
|
||||
email: adminEmail,
|
||||
name: "Admin User",
|
||||
role: "ADMIN",
|
||||
firstName: "Admin",
|
||||
lastName: "User",
|
||||
gender: "OTHER",
|
||||
emailVerified: true,
|
||||
forcePasswordReset: false,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.credential.upsert({
|
||||
where: { userId: admin.id },
|
||||
update: { password: passwordHash },
|
||||
create: { userId: admin.id, password: passwordHash },
|
||||
});
|
||||
|
||||
console.log("✅ Admin user created:", adminEmail);
|
||||
|
||||
const customer = await prisma.user.upsert({
|
||||
where: { email: customerEmail },
|
||||
update: {},
|
||||
create: {
|
||||
email: customerEmail,
|
||||
name: "Customer User",
|
||||
role: "USER",
|
||||
firstName: "Customer",
|
||||
lastName: "User",
|
||||
gender: "OTHER",
|
||||
emailVerified: true,
|
||||
forcePasswordReset: false,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.credential.upsert({
|
||||
where: { userId: customer.id },
|
||||
update: { password: passwordHash },
|
||||
create: { userId: customer.id, password: passwordHash },
|
||||
});
|
||||
|
||||
console.log("✅ Customer user created:", customerEmail);
|
||||
|
||||
const existingWebinars = await prisma.webinar.count();
|
||||
if (existingWebinars === 0) {
|
||||
const now = Date.now();
|
||||
const sample = [
|
||||
{
|
||||
title: "Estate Planning Fundamentals",
|
||||
description: "Learn the foundations of estate planning, wills, and trusts.",
|
||||
speaker: "Emily Roberts",
|
||||
startAt: new Date(now + 7 * 86400000),
|
||||
duration: 90,
|
||||
bannerUrl: null,
|
||||
category: "Basics",
|
||||
visibility: "PUBLIC",
|
||||
isActive: true,
|
||||
capacity: 50,
|
||||
priceCents: 0,
|
||||
learningPoints: [
|
||||
"Understanding the basics of wills and trusts",
|
||||
"Key differences between revocable and irrevocable trusts",
|
||||
"Common estate planning mistakes to avoid",
|
||||
"When to update your estate plan",
|
||||
],
|
||||
meetingInfo: {},
|
||||
},
|
||||
{
|
||||
title: "Avoiding Probate: Strategies & Solutions",
|
||||
description: "Practical strategies to reduce or avoid probate delays.",
|
||||
speaker: "David Martinez",
|
||||
startAt: new Date(now + 10 * 86400000),
|
||||
duration: 75,
|
||||
bannerUrl: null,
|
||||
category: "Planning",
|
||||
visibility: "PUBLIC",
|
||||
isActive: true,
|
||||
capacity: 60,
|
||||
priceCents: 0,
|
||||
learningPoints: [
|
||||
"How probate works and why it can be costly",
|
||||
"Living trusts as probate avoidance tools",
|
||||
"Joint ownership strategies",
|
||||
"Beneficiary designations and their importance",
|
||||
],
|
||||
meetingInfo: {},
|
||||
},
|
||||
{
|
||||
title: "Tax-Efficient Estate Planning",
|
||||
description: "Minimize taxes and preserve wealth across generations.",
|
||||
speaker: "Jennifer Thompson",
|
||||
startAt: new Date(now + 14 * 86400000),
|
||||
duration: 90,
|
||||
bannerUrl: null,
|
||||
category: "Tax",
|
||||
visibility: "PUBLIC",
|
||||
isActive: true,
|
||||
capacity: 40,
|
||||
priceCents: 4900,
|
||||
learningPoints: [
|
||||
"Current federal and state estate tax exemptions",
|
||||
"Gift tax strategies and annual exclusions",
|
||||
"Charitable giving techniques for tax benefits",
|
||||
"Generation-skipping transfer tax planning",
|
||||
],
|
||||
meetingInfo: {},
|
||||
},
|
||||
{
|
||||
title: "Healthcare Directives & Powers of Attorney",
|
||||
description: "Understand advanced directives and medical decision-making.",
|
||||
speaker: "Lisa Patterson",
|
||||
startAt: new Date(now + 17 * 86400000),
|
||||
duration: 60,
|
||||
bannerUrl: null,
|
||||
category: "Healthcare",
|
||||
visibility: "PUBLIC",
|
||||
isActive: true,
|
||||
capacity: 80,
|
||||
priceCents: 0,
|
||||
learningPoints: [
|
||||
"Types of healthcare directives and their purposes",
|
||||
"Choosing the right healthcare proxy",
|
||||
"Living wills vs. healthcare powers of attorney",
|
||||
"HIPAA authorizations and medical records access",
|
||||
],
|
||||
meetingInfo: {},
|
||||
},
|
||||
{
|
||||
title: "Family Wealth Transfer (Private Session)",
|
||||
description: "Invite-only workshop for complex family asset structures.",
|
||||
speaker: "Michael Chen",
|
||||
startAt: new Date(now + 21 * 86400000),
|
||||
duration: 120,
|
||||
bannerUrl: null,
|
||||
category: "Advanced",
|
||||
visibility: "PRIVATE",
|
||||
isActive: true,
|
||||
capacity: 20,
|
||||
priceCents: 9900,
|
||||
learningPoints: [
|
||||
"Advanced trust structures for wealth preservation",
|
||||
"Family limited partnerships and LLCs",
|
||||
"Succession planning for family businesses",
|
||||
"Coordinating estate plans across multiple jurisdictions",
|
||||
],
|
||||
meetingInfo: {},
|
||||
},
|
||||
];
|
||||
|
||||
await prisma.webinar.createMany({ data: sample as any });
|
||||
console.log("✅ Sample webinars created");
|
||||
}
|
||||
|
||||
const appSetup = await prisma.appSetup.findUnique({ where: { id: 1 } });
|
||||
if (!appSetup) {
|
||||
await prisma.appSetup.create({
|
||||
data: {
|
||||
id: 1,
|
||||
googleAuthEnabled: false,
|
||||
socials: {},
|
||||
categories: ["Basics", "Planning", "Tax", "Healthcare", "Advanced"],
|
||||
},
|
||||
});
|
||||
console.log("✅ App setup row created");
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user