179 lines
5.5 KiB
TypeScript
179 lines
5.5 KiB
TypeScript
/**
|
|
* 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 });
|
|
* });
|
|
*/
|