feat: 定义数据库 Schema

- 用户表 (users)
- 内容表 (content)
- 版本历史表 (content_versions)
- 站点配置表 (site_config)
- 操作日志表 (audit_logs)
This commit is contained in:
张翔
2026-03-08 20:59:43 +08:00
parent 17747fde31
commit 55c8a0015b
3 changed files with 102 additions and 1 deletions
+10
View File
@@ -0,0 +1,10 @@
import type { Config } from 'drizzle-kit';
export default {
schema: './src/db/schema.ts',
out: './drizzle',
driver: 'libsql',
dbCredentials: {
url: process.env.DATABASE_URL || 'file:./data.db',
},
} satisfies Config;
+6 -1
View File
@@ -17,7 +17,12 @@
"audit:accessibility": "node scripts/accessibility-test.js",
"audit:forms": "node scripts/form-validation.js",
"audit:all": "./scripts/run-all-tests.sh",
"report:generate": "node scripts/generate-test-report.js"
"report:generate": "node scripts/generate-test-report.js",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"db:seed": "tsx src/db/seed.ts"
},
"dependencies": {
"@antv/g2": "^5.4.8",
+86
View File
@@ -0,0 +1,86 @@
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
email: text('email').notNull().unique(),
passwordHash: text('password_hash'),
name: text('name').notNull(),
role: text('role', { enum: ['admin', 'editor', 'viewer'] }).notNull().default('editor'),
avatar: text('avatar'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const content = sqliteTable('content', {
id: text('id').primaryKey(),
type: text('type', { enum: ['news', 'product', 'service', 'case'] }).notNull(),
title: text('title').notNull(),
slug: text('slug').notNull().unique(),
excerpt: text('excerpt'),
content: text('content').notNull(),
coverImage: text('cover_image'),
category: text('category'),
tags: text('tags', { mode: 'json' }).$type<string[]>(),
status: text('status', { enum: ['draft', 'published', 'archived'] }).notNull().default('draft'),
publishedAt: integer('published_at', { mode: 'timestamp' }),
authorId: text('author_id').notNull().references(() => users.id),
sortOrder: integer('sort_order').default(0),
metadata: text('metadata', { mode: 'json' }).$type<Record<string, any>>(),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const contentVersions = sqliteTable('content_versions', {
id: text('id').primaryKey(),
contentId: text('content_id').notNull().references(() => content.id),
version: integer('version').notNull(),
title: text('title').notNull(),
content: text('content').notNull(),
changes: text('changes', { mode: 'json' }).$type<Record<string, any>>(),
changedBy: text('changed_by').notNull().references(() => users.id),
changedAt: integer('changed_at', { mode: 'timestamp' }).notNull(),
});
export const siteConfig = sqliteTable('site_config', {
id: text('id').primaryKey(),
key: text('key').notNull().unique(),
value: text('value', { mode: 'json' }).notNull(),
category: text('category', { enum: ['feature', 'style', 'seo', 'general'] }).notNull(),
description: text('description'),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
updatedBy: text('updated_by').references(() => users.id),
});
export const auditLogs = sqliteTable('audit_logs', {
id: text('id').primaryKey(),
userId: text('user_id').references(() => users.id),
action: text('action', { enum: ['create', 'update', 'delete', 'publish', 'login'] }).notNull(),
resourceType: text('resource_type').notNull(),
resourceId: text('resource_id'),
details: text('details', { mode: 'json' }).$type<Record<string, any>>(),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
timestamp: integer('timestamp', { mode: 'timestamp' }).notNull(),
});
export const usersRelations = relations(users, ({ many }) => ({
content: many(content),
versions: many(contentVersions),
logs: many(auditLogs),
}));
export const contentRelations = relations(content, ({ one, many }) => ({
author: one(users, {
fields: [content.authorId],
references: [users.id],
}),
versions: many(contentVersions),
}));
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Content = typeof content.$inferSelect;
export type NewContent = typeof content.$inferInsert;
export type SiteConfig = typeof siteConfig.$inferSelect;
export type NewSiteConfig = typeof siteConfig.$inferInsert;