feat: 定义数据库 Schema
- 用户表 (users) - 内容表 (content) - 版本历史表 (content_versions) - 站点配置表 (site_config) - 操作日志表 (audit_logs)
This commit is contained in:
@@ -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
@@ -17,7 +17,12 @@
|
|||||||
"audit:accessibility": "node scripts/accessibility-test.js",
|
"audit:accessibility": "node scripts/accessibility-test.js",
|
||||||
"audit:forms": "node scripts/form-validation.js",
|
"audit:forms": "node scripts/form-validation.js",
|
||||||
"audit:all": "./scripts/run-all-tests.sh",
|
"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": {
|
"dependencies": {
|
||||||
"@antv/g2": "^5.4.8",
|
"@antv/g2": "^5.4.8",
|
||||||
|
|||||||
@@ -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;
|
||||||
Reference in New Issue
Block a user