Initial commit
This commit is contained in:
197
backend/prisma/schema.prisma
Normal file
197
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,197 @@
|
||||
// Prisma schema for LNPaywall
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
status String @default("ACTIVE") // ACTIVE, DISABLED
|
||||
role String @default("CREATOR") // CREATOR, ADMIN
|
||||
displayName String
|
||||
email String? @unique
|
||||
emailVerified Boolean @default(false)
|
||||
passwordHash String?
|
||||
nostrPubkey String? @unique
|
||||
avatarUrl String?
|
||||
defaultCurrency String @default("sats")
|
||||
|
||||
// OAuth providers
|
||||
googleId String? @unique
|
||||
githubId String? @unique
|
||||
|
||||
// Payout configuration
|
||||
lightningAddress String?
|
||||
lnurlp String?
|
||||
|
||||
// Subscription
|
||||
subscriptionTier String @default("FREE") // FREE, PRO
|
||||
subscriptionExpiry DateTime?
|
||||
|
||||
// Relations
|
||||
paywalls Paywall[]
|
||||
sales Sale[]
|
||||
sessions Session[]
|
||||
auditLogs AuditLog[]
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
refreshToken String @unique
|
||||
userAgent String?
|
||||
ipAddress String?
|
||||
createdAt DateTime @default(now())
|
||||
expiresAt DateTime
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Paywall {
|
||||
id String @id @default(uuid())
|
||||
creatorId String
|
||||
status String @default("ACTIVE") // ACTIVE, ARCHIVED, DISABLED
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Content
|
||||
title String
|
||||
description String?
|
||||
coverImageUrl String?
|
||||
originalUrl String
|
||||
originalUrlType String @default("URL") // URL, YOUTUBE, NOTION, PDF, LOOM, GDOCS, GITHUB, OTHER
|
||||
|
||||
// Preview
|
||||
previewMode String @default("NONE") // NONE, TEXT_PREVIEW, IMAGE_PREVIEW
|
||||
previewContent String?
|
||||
|
||||
// Pricing
|
||||
priceSats Int
|
||||
|
||||
// Access rules
|
||||
accessExpirySeconds Int?
|
||||
maxDevices Int @default(3)
|
||||
maxSessions Int @default(5)
|
||||
|
||||
// Embed settings
|
||||
allowEmbed Boolean @default(true)
|
||||
allowedEmbedOrigins String @default("[]") // JSON array as string for SQLite
|
||||
|
||||
// Customization
|
||||
requireEmailReceipt Boolean @default(false)
|
||||
customSuccessMessage String?
|
||||
customBranding String? // JSON as string
|
||||
|
||||
// URL
|
||||
slug String? @unique
|
||||
|
||||
// Relations
|
||||
creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade)
|
||||
checkoutSessions CheckoutSession[]
|
||||
accessGrants AccessGrant[]
|
||||
sales Sale[]
|
||||
}
|
||||
|
||||
model CheckoutSession {
|
||||
id String @id @default(uuid())
|
||||
paywallId String
|
||||
createdAt DateTime @default(now())
|
||||
status String @default("PENDING") // PENDING, PAID, EXPIRED, CANCELED
|
||||
amountSats Int
|
||||
paymentProvider String @default("lnbits")
|
||||
paymentRequest String? // Lightning invoice
|
||||
paymentHash String? @unique
|
||||
expiresAt DateTime
|
||||
buyerHint String? // IP hash + user agent hash
|
||||
buyerEmail String?
|
||||
|
||||
// Relations
|
||||
paywall Paywall @relation(fields: [paywallId], references: [id], onDelete: Cascade)
|
||||
accessGrant AccessGrant?
|
||||
sale Sale?
|
||||
}
|
||||
|
||||
model Buyer {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
email String?
|
||||
emailVerified Boolean @default(false)
|
||||
nostrPubkey String?
|
||||
notes String?
|
||||
|
||||
// Relations
|
||||
accessGrants AccessGrant[]
|
||||
sales Sale[]
|
||||
}
|
||||
|
||||
model AccessGrant {
|
||||
id String @id @default(uuid())
|
||||
paywallId String
|
||||
checkoutSessionId String? @unique
|
||||
buyerId String?
|
||||
createdAt DateTime @default(now())
|
||||
expiresAt DateTime?
|
||||
status String @default("ACTIVE") // ACTIVE, REVOKED
|
||||
tokenId String @unique @default(uuid())
|
||||
lastUsedAt DateTime?
|
||||
usageCount Int @default(0)
|
||||
deviceFingerprints String @default("[]") // JSON array as string
|
||||
|
||||
// Relations
|
||||
paywall Paywall @relation(fields: [paywallId], references: [id], onDelete: Cascade)
|
||||
checkoutSession CheckoutSession? @relation(fields: [checkoutSessionId], references: [id])
|
||||
buyer Buyer? @relation(fields: [buyerId], references: [id])
|
||||
}
|
||||
|
||||
model Sale {
|
||||
id String @id @default(uuid())
|
||||
paywallId String
|
||||
creatorId String
|
||||
checkoutSessionId String? @unique
|
||||
buyerId String?
|
||||
createdAt DateTime @default(now())
|
||||
amountSats Int
|
||||
platformFeeSats Int
|
||||
netSats Int
|
||||
paymentProvider String
|
||||
providerReference String?
|
||||
status String @default("CONFIRMED") // CONFIRMED, REFUNDED, CHARGEBACK, DISPUTED
|
||||
|
||||
// Relations
|
||||
paywall Paywall @relation(fields: [paywallId], references: [id])
|
||||
creator User @relation(fields: [creatorId], references: [id])
|
||||
checkoutSession CheckoutSession? @relation(fields: [checkoutSessionId], references: [id])
|
||||
buyer Buyer? @relation(fields: [buyerId], references: [id])
|
||||
}
|
||||
|
||||
model WebhookEvent {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
provider String
|
||||
eventType String
|
||||
rawPayload String // JSON as string
|
||||
processedAt DateTime?
|
||||
status String @default("pending")
|
||||
error String?
|
||||
}
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
actorId String?
|
||||
actorType String // creator, admin, system
|
||||
action String
|
||||
resourceType String
|
||||
resourceId String?
|
||||
ipAddress String?
|
||||
metadata String? // JSON as string
|
||||
|
||||
actor User? @relation(fields: [actorId], references: [id])
|
||||
}
|
||||
97
backend/prisma/seed.js
Normal file
97
backend/prisma/seed.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Seeding database...');
|
||||
|
||||
// Create admin user
|
||||
const adminPassword = await bcrypt.hash('admin123', 12);
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { email: 'admin@lnpaywall.com' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'admin@lnpaywall.com',
|
||||
passwordHash: adminPassword,
|
||||
displayName: 'Admin',
|
||||
role: 'ADMIN',
|
||||
emailVerified: true,
|
||||
},
|
||||
});
|
||||
console.log('✅ Created admin user:', admin.email);
|
||||
|
||||
// Create demo creator
|
||||
const creatorPassword = await bcrypt.hash('demo123', 12);
|
||||
const creator = await prisma.user.upsert({
|
||||
where: { email: 'creator@demo.com' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'creator@demo.com',
|
||||
passwordHash: creatorPassword,
|
||||
displayName: 'Demo Creator',
|
||||
role: 'CREATOR',
|
||||
emailVerified: true,
|
||||
lightningAddress: 'demo@getalby.com',
|
||||
},
|
||||
});
|
||||
console.log('✅ Created demo creator:', creator.email);
|
||||
|
||||
// Create sample paywalls
|
||||
const paywalls = [
|
||||
{
|
||||
title: 'Complete Bitcoin Development Course',
|
||||
description: 'Learn to build on Bitcoin and Lightning Network from scratch. 10+ hours of video content.',
|
||||
originalUrl: 'https://example.com/bitcoin-course',
|
||||
originalUrlType: 'URL',
|
||||
priceSats: 5000,
|
||||
slug: 'bitcoin-course',
|
||||
coverImageUrl: 'https://images.unsplash.com/photo-1621761191319-c6fb62004040?w=800',
|
||||
},
|
||||
{
|
||||
title: 'Exclusive Trading Strategy PDF',
|
||||
description: 'My personal trading strategy that generates consistent returns. Includes spreadsheet templates.',
|
||||
originalUrl: 'https://example.com/trading-guide.pdf',
|
||||
originalUrlType: 'PDF',
|
||||
priceSats: 2100,
|
||||
slug: 'trading-strategy',
|
||||
coverImageUrl: 'https://images.unsplash.com/photo-1611974789855-9c2a0a7236a3?w=800',
|
||||
},
|
||||
{
|
||||
title: 'Private Notion Template Library',
|
||||
description: 'Access my complete collection of 50+ Notion templates for productivity and business.',
|
||||
originalUrl: 'https://notion.so/template-library',
|
||||
originalUrlType: 'NOTION',
|
||||
priceSats: 1000,
|
||||
slug: 'notion-templates',
|
||||
coverImageUrl: 'https://images.unsplash.com/photo-1484480974693-6ca0a78fb36b?w=800',
|
||||
},
|
||||
];
|
||||
|
||||
for (const paywallData of paywalls) {
|
||||
const paywall = await prisma.paywall.upsert({
|
||||
where: { slug: paywallData.slug },
|
||||
update: {},
|
||||
create: {
|
||||
...paywallData,
|
||||
creatorId: creator.id,
|
||||
},
|
||||
});
|
||||
console.log('✅ Created paywall:', paywall.title);
|
||||
}
|
||||
|
||||
console.log('\n🎉 Seeding complete!');
|
||||
console.log('\n📝 Test accounts:');
|
||||
console.log(' Admin: admin@lnpaywall.com / admin123');
|
||||
console.log(' Creator: creator@demo.com / demo123');
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('❌ Seed error:', e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user