You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
5.7 KiB
195 lines
5.7 KiB
import {
|
|
sqliteTable,
|
|
text,
|
|
integer,
|
|
real,
|
|
primaryKey,
|
|
index,
|
|
uniqueIndex,
|
|
} from "drizzle-orm/sqlite-core";
|
|
import { users } from "./auth";
|
|
|
|
// ============ CardType ENUM ============
|
|
export const CardTypes = [
|
|
"text",
|
|
"image",
|
|
"image-text",
|
|
"portfolio",
|
|
"project",
|
|
] as const;
|
|
export type CardType = (typeof CardTypes)[number];
|
|
|
|
// ============ Category(分类表)============
|
|
export const categories = sqliteTable(
|
|
"categories",
|
|
{
|
|
id: text("id").primaryKey(), // UUID
|
|
name: text("name", { length: 100 }).notNull(),
|
|
slug: text("slug", { length: 100 }).notNull(),
|
|
image: text("image", { length: 500 }),
|
|
parentId: text("parent_id"), // 自引用 FK,NULL=顶级
|
|
sortOrder: integer("sort_order").default(0).notNull(),
|
|
count: integer("count").default(0).notNull(),
|
|
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
.defaultNow()
|
|
.notNull(),
|
|
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
.defaultNow()
|
|
.$onUpdate(() => new Date())
|
|
.notNull(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("idx_category_slug").on(table.slug),
|
|
index("idx_category_parent").on(table.parentId),
|
|
],
|
|
);
|
|
|
|
// ============ Card(卡片表)============
|
|
export const cards = sqliteTable(
|
|
"cards",
|
|
{
|
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
type: text("type", { enum: CardTypes }).notNull(),
|
|
title: text("title", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
aspectRatio: real("aspect_ratio"),
|
|
categoryId: text("category_id").references(() => categories.id),
|
|
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
.defaultNow()
|
|
.notNull(),
|
|
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
.defaultNow()
|
|
.$onUpdate(() => new Date())
|
|
.notNull(),
|
|
},
|
|
(table) => [
|
|
index("idx_card_category").on(table.categoryId),
|
|
index("idx_card_type").on(table.type),
|
|
index("idx_card_created").on(table.createdAt),
|
|
],
|
|
);
|
|
|
|
// ============ CardImage(卡片图片表)============
|
|
export const cardImages = sqliteTable(
|
|
"card_images",
|
|
{
|
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
cardId: integer("card_id")
|
|
.notNull()
|
|
.references(() => cards.id),
|
|
url: text("url", { length: 500 }).notNull(),
|
|
sortOrder: integer("sort_order").default(0).notNull(),
|
|
},
|
|
(table) => [index("idx_card_image_card").on(table.cardId)],
|
|
);
|
|
|
|
// ============ Tag(标签表)============
|
|
export const tags = sqliteTable(
|
|
"tags",
|
|
{
|
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
name: text("name", { length: 50 }).notNull(),
|
|
slug: text("slug", { length: 50 }).notNull(),
|
|
},
|
|
(table) => [uniqueIndex("idx_tag_slug").on(table.slug)],
|
|
);
|
|
|
|
// ============ CardTag(卡片-标签关联表)============
|
|
export const cardTags = sqliteTable(
|
|
"card_tags",
|
|
{
|
|
cardId: integer("card_id")
|
|
.notNull()
|
|
.references(() => cards.id),
|
|
tagId: integer("tag_id")
|
|
.notNull()
|
|
.references(() => tags.id),
|
|
},
|
|
(table) => [
|
|
primaryKey({ name: "card_tag_pk", columns: [table.cardId, table.tagId] }),
|
|
index("idx_card_tag_card").on(table.cardId),
|
|
index("idx_card_tag_tag").on(table.tagId),
|
|
],
|
|
);
|
|
|
|
// ============ Favorite(收藏表)============
|
|
export const favorites = sqliteTable(
|
|
"favorites",
|
|
{
|
|
userId: integer("user_id")
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
cardId: integer("card_id")
|
|
.notNull()
|
|
.references(() => cards.id, { onDelete: "cascade" }),
|
|
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
.defaultNow()
|
|
.notNull(),
|
|
},
|
|
(table) => [
|
|
primaryKey({ name: "favorite_pk", columns: [table.userId, table.cardId] }),
|
|
index("idx_favorite_user").on(table.userId),
|
|
index("idx_favorite_card").on(table.cardId),
|
|
],
|
|
);
|
|
|
|
// ============ Tool(工具项表)============
|
|
export const tools = sqliteTable("tools", {
|
|
id: text("id").primaryKey(), // UUID
|
|
name: text("name", { length: 50 }).notNull(),
|
|
slug: text("slug", { length: 50 }).notNull(),
|
|
icon: text("icon", { length: 100 }),
|
|
sortOrder: integer("sort_order").default(0).notNull(),
|
|
});
|
|
|
|
// ============ ArticleStatus ENUM ============
|
|
export const ArticleStatuses = ["draft", "published"] as const;
|
|
export type ArticleStatus = (typeof ArticleStatuses)[number];
|
|
|
|
// ============ Article(文章表)============
|
|
export const articles = sqliteTable(
|
|
"articles",
|
|
{
|
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
title: text("title", { length: 255 }).notNull(),
|
|
content: text("content").notNull(),
|
|
summary: text("summary"),
|
|
cover: text("cover", { length: 500 }),
|
|
status: text("status", { enum: ArticleStatuses })
|
|
.default("draft")
|
|
.notNull(),
|
|
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
.defaultNow()
|
|
.notNull(),
|
|
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
.defaultNow()
|
|
.$onUpdate(() => new Date())
|
|
.notNull(),
|
|
},
|
|
(table) => [
|
|
index("idx_article_status").on(table.status),
|
|
index("idx_article_created").on(table.createdAt),
|
|
],
|
|
);
|
|
|
|
// ============ ArticleCard(文章-卡片关联表)============
|
|
export const articleCards = sqliteTable(
|
|
"article_cards",
|
|
{
|
|
articleId: integer("article_id")
|
|
.notNull()
|
|
.references(() => articles.id, { onDelete: "cascade" }),
|
|
cardId: integer("card_id")
|
|
.notNull()
|
|
.references(() => cards.id, { onDelete: "cascade" }),
|
|
sortOrder: integer("sort_order").default(0).notNull(),
|
|
},
|
|
(table) => [
|
|
primaryKey({
|
|
name: "article_card_pk",
|
|
columns: [table.articleId, table.cardId],
|
|
}),
|
|
index("idx_article_card_article").on(table.articleId),
|
|
index("idx_article_card_card").on(table.cardId),
|
|
],
|
|
);
|
|
|