Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:25:01 +08:00
commit 45ffb77c40
14 changed files with 3275 additions and 0 deletions

178
assets/drizzle-schema.ts Normal file
View File

@@ -0,0 +1,178 @@
/**
* Complete Drizzle Schema Template for Neon/Vercel Postgres
*
* Usage:
* 1. Copy this file to your project: cp assets/drizzle-schema.ts db/schema.ts
* 2. Customize tables to match your app's data model
* 3. Generate migrations: npx drizzle-kit generate
* 4. Apply migrations: npx drizzle-kit migrate
*/
import { pgTable, serial, text, timestamp, integer, boolean, jsonb, index, unique } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
// ============================================================================
// USERS TABLE
// ============================================================================
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
avatar: text('avatar'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => ({
// Indexes for common queries
emailIdx: index('users_email_idx').on(table.email),
}));
// ============================================================================
// POSTS TABLE
// ============================================================================
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false).notNull(),
slug: text('slug').notNull().unique(),
metadata: jsonb('metadata').$type<{
views?: number;
likes?: number;
tags?: string[];
}>(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => ({
// Indexes for common queries
userIdIdx: index('posts_user_id_idx').on(table.userId),
slugIdx: index('posts_slug_idx').on(table.slug),
publishedIdx: index('posts_published_idx').on(table.published),
}));
// ============================================================================
// COMMENTS TABLE
// ============================================================================
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
postId: integer('post_id').notNull().references(() => posts.id, { onDelete: 'cascade' }),
userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
content: text('content').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => ({
// Indexes for common queries
postIdIdx: index('comments_post_id_idx').on(table.postId),
userIdIdx: index('comments_user_id_idx').on(table.userId),
}));
// ============================================================================
// RELATIONS (for Drizzle query API)
// ============================================================================
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
comments: many(comments),
}));
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.userId],
references: [users.id],
}),
comments: many(comments),
}));
export const commentsRelations = relations(comments, ({ one }) => ({
post: one(posts, {
fields: [comments.postId],
references: [posts.id],
}),
author: one(users, {
fields: [comments.userId],
references: [users.id],
}),
}));
// ============================================================================
// TYPE EXPORTS (for TypeScript)
// ============================================================================
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
export type Comment = typeof comments.$inferSelect;
export type NewComment = typeof comments.$inferInsert;
// ============================================================================
// USAGE EXAMPLE
// ============================================================================
/**
* db/index.ts:
*
* import { drizzle } from 'drizzle-orm/neon-http';
* import { neon } from '@neondatabase/serverless';
* import * as schema from './schema';
*
* const sql = neon(process.env.DATABASE_URL!);
* export const db = drizzle(sql, { schema });
*/
/**
* drizzle.config.ts:
*
* import { defineConfig } from 'drizzle-kit';
*
* export default defineConfig({
* schema: './db/schema.ts',
* out: './db/migrations',
* dialect: 'postgresql',
* dbCredentials: {
* url: process.env.DATABASE_URL!
* }
* });
*/
/**
* Query Examples:
*
* // SELECT with joins
* const postsWithAuthors = await db.query.posts.findMany({
* with: {
* author: true,
* comments: {
* with: {
* author: true
* }
* }
* }
* });
*
* // INSERT
* const newUser = await db.insert(users).values({
* email: 'alice@example.com',
* name: 'Alice'
* }).returning();
*
* // UPDATE
* await db.update(posts).set({
* published: true,
* updatedAt: new Date()
* }).where(eq(posts.id, postId));
*
* // DELETE
* await db.delete(comments).where(eq(comments.id, commentId));
*
* // Transaction
* await db.transaction(async (tx) => {
* const [user] = await tx.insert(users).values({ email, name }).returning();
* await tx.insert(posts).values({ userId: user.id, title, content });
* });
*/

View File

@@ -0,0 +1,14 @@
[TODO: Example Template File]
[TODO: This directory contains files that will be used in the OUTPUT that Claude produces.]
[TODO: Examples:]
- Templates (.html, .tsx, .md)
- Images (.png, .svg)
- Fonts (.ttf, .woff)
- Boilerplate code
- Configuration file templates
[TODO: Delete this file and add your actual assets]
These files are NOT loaded into context. They are copied or used directly in the final output.