Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:46:53 +08:00
commit f2cccfe864
10 changed files with 1544 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DIRECT_URL") // Pooled connection for queries
directUrl = env("DATABASE_URL") // Direct connection for migrations
shadowDatabaseUrl = env("SHADOW_DATABASE_URL") // For migration preview
}
// User profile - links to Supabase auth.users
model Profile {
id String @id @default(uuid()) @db.Uuid
email String @unique
name String?
avatarUrl String? @map("avatar_url")
bio String? @db.Text
role UserRole @default(USER)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
posts Post[]
comments Comment[]
@@index([email])
@@map("profiles")
}
// User roles enum
enum UserRole {
USER
MODERATOR
ADMIN
}
// Blog post example
model Post {
id String @id @default(uuid()) @db.Uuid
title String
slug String @unique
content String @db.Text
excerpt String? @db.Text
published Boolean @default(false)
publishedAt DateTime? @map("published_at")
authorId String @map("author_id") @db.Uuid
author Profile @relation(fields: [authorId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// Relations
comments Comment[]
tags PostTag[]
@@index([authorId])
@@index([slug])
@@index([published, publishedAt])
@@map("posts")
}
// Comment model
model Comment {
id String @id @default(uuid()) @db.Uuid
content String @db.Text
postId String @map("post_id") @db.Uuid
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
authorId String @map("author_id") @db.Uuid
author Profile @relation(fields: [authorId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([postId])
@@index([authorId])
@@map("comments")
}
// Tag model
model Tag {
id String @id @default(uuid()) @db.Uuid
name String @unique
slug String @unique
posts PostTag[]
@@index([slug])
@@map("tags")
}
// Many-to-many relation between posts and tags
model PostTag {
postId String @map("post_id") @db.Uuid
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
tagId String @map("tag_id") @db.Uuid
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
assignedAt DateTime @default(now()) @map("assigned_at")
@@id([postId, tagId])
@@map("post_tags")
}
// Settings model (singleton pattern)
model Settings {
id String @id @default(uuid()) @db.Uuid
siteName String @map("site_name")
siteUrl String @map("site_url")
description String? @db.Text
// SEO
metaTitle String? @map("meta_title")
metaDescription String? @map("meta_description")
// Features
enableComments Boolean @default(true) @map("enable_comments")
enableRegistration Boolean @default(true) @map("enable_registration")
maintenanceMode Boolean @default(false) @map("maintenance_mode")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("settings")
}

View File

@@ -0,0 +1,62 @@
name: Database Schema Check
on:
pull_request:
paths:
- 'prisma/**'
- '.github/workflows/schema-check.yml'
jobs:
schema-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Validate Prisma schema
run: npx prisma validate
- name: Format Prisma schema
run: npx prisma format --check
- name: Generate Prisma Client
run: npx prisma generate
- name: Check for migration drift
run: |
# This checks if schema.prisma matches the current migrations
# If there are changes without migrations, this will fail
npx prisma migrate diff \
--from-schema-datamodel prisma/schema.prisma \
--to-schema-datasource prisma/schema.prisma \
--script > migration-diff.sql
if [ -s migration-diff.sql ]; then
echo "Schema changes detected without migration!"
echo "Run 'npx prisma migrate dev' to create a migration"
cat migration-diff.sql
exit 1
fi
continue-on-error: true
- name: Comment PR with schema changes
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '[WARN] Schema changes detected. Please generate a migration with `npx prisma migrate dev`'
})

View File

@@ -0,0 +1,9 @@
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

View File

@@ -0,0 +1,158 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('🌱 Starting seed...');
// Create or update settings (singleton)
const settings = await prisma.settings.upsert({
where: { id: '00000000-0000-0000-0000-000000000001' },
update: {},
create: {
id: '00000000-0000-0000-0000-000000000001',
siteName: 'My Worldbuilding App',
siteUrl: 'https://example.com',
description: 'A platform for building immersive worlds',
metaTitle: 'Worldbuilding App - Create Amazing Worlds',
metaDescription: 'Build, manage, and share your fictional worlds',
enableComments: true,
enableRegistration: true,
maintenanceMode: false,
},
});
console.log('[OK] Settings created:', settings.siteName);
// Create sample tags
const tags = await Promise.all([
prisma.tag.upsert({
where: { slug: 'worldbuilding' },
update: {},
create: {
name: 'Worldbuilding',
slug: 'worldbuilding',
},
}),
prisma.tag.upsert({
where: { slug: 'character-development' },
update: {},
create: {
name: 'Character Development',
slug: 'character-development',
},
}),
prisma.tag.upsert({
where: { slug: 'lore' },
update: {},
create: {
name: 'Lore',
slug: 'lore',
},
}),
]);
console.log(`[OK] Created ${tags.length} tags`);
// Create sample user profile (only if doesn't exist)
const sampleUser = await prisma.profile.upsert({
where: { email: 'demo@example.com' },
update: {},
create: {
id: '00000000-0000-0000-0000-000000000002',
email: 'demo@example.com',
name: 'Demo User',
bio: 'Sample user for testing',
role: 'USER',
},
});
console.log('[OK] Sample user created:', sampleUser.email);
// Create sample post
const samplePost = await prisma.post.upsert({
where: { slug: 'getting-started-with-worldbuilding' },
update: {},
create: {
title: 'Getting Started with Worldbuilding',
slug: 'getting-started-with-worldbuilding',
excerpt: 'Learn the basics of creating your own fictional world',
content: `
# Getting Started with Worldbuilding
Worldbuilding is the process of constructing an imaginary world, sometimes associated with a whole fictional universe.
## Key Elements
1. **Geography**: Define the physical layout of your world
2. **History**: Create a timeline of major events
3. **Culture**: Develop societies and their customs
4. **Magic/Technology**: Establish the rules of your world
Start small and expand gradually. You don't need to build everything at once!
`.trim(),
published: true,
publishedAt: new Date(),
authorId: sampleUser.id,
},
});
console.log('[OK] Sample post created:', samplePost.title);
// Link tags to post
await prisma.postTag.upsert({
where: {
postId_tagId: {
postId: samplePost.id,
tagId: tags[0].id,
},
},
update: {},
create: {
postId: samplePost.id,
tagId: tags[0].id,
},
});
await prisma.postTag.upsert({
where: {
postId_tagId: {
postId: samplePost.id,
tagId: tags[2].id,
},
},
update: {},
create: {
postId: samplePost.id,
tagId: tags[2].id,
},
});
console.log('[OK] Tags linked to post');
// Create sample comment
await prisma.comment.upsert({
where: { id: '00000000-0000-0000-0000-000000000003' },
update: {},
create: {
id: '00000000-0000-0000-0000-000000000003',
content: 'Great introduction! Looking forward to more posts.',
postId: samplePost.id,
authorId: sampleUser.id,
},
});
console.log('[OK] Sample comment created');
console.log('🎉 Seed completed successfully!');
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error('[ERROR] Seed failed:', e);
await prisma.$disconnect();
process.exit(1);
});