Initial commit

This commit is contained in:
Michilis
2025-12-14 23:08:45 -03:00
commit 1e1753dff3
58 changed files with 18294 additions and 0 deletions

View 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
View 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();
});