v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)

This commit is contained in:
BigCode
2026-11-01 00:15:29 +08:00
commit 5b83e71ad9
128 changed files with 20008 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
/**
* 数据库客户端:Bun.sql + Drizzle(bun-sql 适配器)。
* 用法:import { db } from "~/db";
*
* 注意:db 采用惰性 Proxy。客户端组件会经由 server 函数模块间接引入本
* 文件,顶层 `new SQL()` 在浏览器端无法解析 Bun 内置模块而崩溃;因此只在
* 服务端首次访问时才初始化连接池(见 initDb / ensureDb)。
*/
import type { BunSQLDatabase } from "drizzle-orm/bun-sql";
import * as schema from "./schema";
/** # 成员变量 惰性初始化的 Drizzle 实例缓存 **/
let _db: BunSQLDatabase<typeof schema> | null = null;
/** # 成员变量 初始化 Promise(防并发重复构建) **/
let _initing: Promise<BunSQLDatabase<typeof schema>> | null = null;
/** # 函数 构建 Drizzle 数据库实例(仅服务端执行)
* 返回值 BunSQLDatabase 实例
*/
async function build(): Promise<BunSQLDatabase<typeof schema>> {
// 变量指示符 + @vite-ignore:Vite/Rolldown 不做静态解析(客户端图不含
// "bun"),服务端运行时由 Bun 原生解析内置模块
const bunModule = "bun";
const { SQL } = await import(/* @vite-ignore */ bunModule);
const { drizzle } = await import("drizzle-orm/bun-sql");
const sql = new SQL(process.env.DATABASE_URL || "postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot");
return drizzle(sql, { schema }) as BunSQLDatabase<typeof schema>;
}
/** # 函数 初始化数据库连接(幂等;仅服务端生效,浏览器端空操作)
* 返回值 Drizzle 数据库实例(客户端返回空壳对象)
*/
export function initDb(): Promise<BunSQLDatabase<typeof schema>> {
// start.ts 同时进客户端包,浏览器端绝不能触碰 Bun 内置模块
if (typeof window !== "undefined") return Promise.resolve({} as BunSQLDatabase<typeof schema>);
if (!_initing) _initing = build().then((d) => { _db = d; return d; });
return _initing;
}
/** # 函数 确保数据库已初始化(同步兜底:已初始化返回实例,否则抛出明确错误)
* 返回值 Drizzle 数据库实例
*/
function ensureDb(): BunSQLDatabase<typeof schema> {
if (!_db) throw new Error("db 未初始化:服务端入口需先 await initDb()(客户端不应访问 db)");
return _db;
}
/** # 成员变量 数据库连接代理(客户端导入安全:不触发连接初始化) **/
export const db = new Proxy({} as BunSQLDatabase<typeof schema>, {
get(_target, key) {
const d = ensureDb();
return Reflect.get(d, key, d);
},
}) as BunSQLDatabase<typeof schema>;
export { schema };
export type Database = BunSQLDatabase<typeof schema>;
@@ -0,0 +1,385 @@
CREATE TABLE "audit_logs" (
"id" serial PRIMARY KEY NOT NULL,
"actor_id" integer,
"action" varchar(128) NOT NULL,
"entity" varchar(64) NOT NULL,
"entity_id" varchar(64),
"meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
"ip" varchar(64) DEFAULT '' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "cart_items" (
"id" serial PRIMARY KEY NOT NULL,
"cart_id" integer NOT NULL,
"product_id" integer NOT NULL,
"qty" integer DEFAULT 1 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "carts" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" integer,
"session_key" varchar(64),
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "categories" (
"id" serial PRIMARY KEY NOT NULL,
"taxonomy" varchar(64) NOT NULL,
"name" varchar(255) NOT NULL,
"slug" varchar(255) NOT NULL,
"parent_id" integer,
"description" text DEFAULT '' NOT NULL,
"image_media_id" integer,
"sort" integer DEFAULT 0 NOT NULL,
"legacy_id" integer
);
--> statement-breakpoint
CREATE TABLE "content_revisions" (
"id" serial PRIMARY KEY NOT NULL,
"content_id" integer NOT NULL,
"lang" varchar(16) DEFAULT 'en' NOT NULL,
"title" text NOT NULL,
"html" text NOT NULL,
"author_id" integer,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "content_terms" (
"content_id" integer NOT NULL,
"category_id" integer NOT NULL,
CONSTRAINT "content_terms_content_id_category_id_pk" PRIMARY KEY("content_id","category_id")
);
--> statement-breakpoint
CREATE TABLE "content_translations" (
"id" serial PRIMARY KEY NOT NULL,
"content_id" integer NOT NULL,
"lang" varchar(16) NOT NULL,
"slug" varchar(255) NOT NULL,
"title" text NOT NULL,
"excerpt" text DEFAULT '' NOT NULL,
"html" text DEFAULT '' NOT NULL,
"status" varchar(16) DEFAULT 'draft' NOT NULL,
"seo_title" text,
"seo_description" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "contents" (
"id" serial PRIMARY KEY NOT NULL,
"type" varchar(32) NOT NULL,
"slug" varchar(255) NOT NULL,
"title" text NOT NULL,
"excerpt" text DEFAULT '' NOT NULL,
"html" text DEFAULT '' NOT NULL,
"status" varchar(16) DEFAULT 'published' NOT NULL,
"author_id" integer,
"featured_media_id" integer,
"template" varchar(64) DEFAULT 'default' NOT NULL,
"menu_order" integer DEFAULT 0 NOT NULL,
"views" integer DEFAULT 0 NOT NULL,
"sticky" boolean DEFAULT false NOT NULL,
"seo_title" text,
"seo_description" text,
"meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
"legacy_id" integer,
"published_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "filters" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(128) NOT NULL,
"slug" varchar(128) NOT NULL,
"type" varchar(24) DEFAULT 'attribute' NOT NULL,
"attribute_id" integer,
"taxonomy" varchar(64),
"position" integer DEFAULT 0 NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"legacy_id" integer
);
--> statement-breakpoint
CREATE TABLE "inquiries" (
"id" serial PRIMARY KEY NOT NULL,
"form_name" varchar(255) DEFAULT '' NOT NULL,
"form_id" integer,
"name" varchar(255) DEFAULT '' NOT NULL,
"email" varchar(255) DEFAULT '' NOT NULL,
"phone" varchar(128) DEFAULT '' NOT NULL,
"company" varchar(255) DEFAULT '' NOT NULL,
"country" varchar(128) DEFAULT '' NOT NULL,
"message" text DEFAULT '' NOT NULL,
"product_interest" varchar(255) DEFAULT '' NOT NULL,
"fields" jsonb DEFAULT '{}'::jsonb NOT NULL,
"status" varchar(16) DEFAULT 'new' NOT NULL,
"channel" text DEFAULT '' NOT NULL,
"source_page" text DEFAULT '' NOT NULL,
"ip" varchar(64) DEFAULT '' NOT NULL,
"utm" jsonb DEFAULT '{}'::jsonb NOT NULL,
"legacy_id" integer,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "inquiry_forms" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(255) NOT NULL,
"form_key" varchar(128) NOT NULL,
"fields" jsonb DEFAULT '[]'::jsonb NOT NULL,
"mail_to" varchar(255),
"enabled" boolean DEFAULT true NOT NULL,
"legacy_id" integer
);
--> statement-breakpoint
CREATE TABLE "jobs" (
"id" serial PRIMARY KEY NOT NULL,
"type" varchar(64) NOT NULL,
"payload" jsonb DEFAULT '{}'::jsonb NOT NULL,
"status" varchar(16) DEFAULT 'pending' NOT NULL,
"attempts" integer DEFAULT 0 NOT NULL,
"max_attempts" integer DEFAULT 3 NOT NULL,
"run_at" timestamp with time zone DEFAULT now() NOT NULL,
"last_error" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "languages" (
"code" varchar(16) PRIMARY KEY NOT NULL,
"name" varchar(64) NOT NULL,
"is_default" boolean DEFAULT false NOT NULL,
"enabled" boolean DEFAULT false NOT NULL,
"machine" boolean DEFAULT false NOT NULL,
"sort" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "legacy_id_mappings" (
"id" serial PRIMARY KEY NOT NULL,
"entity" varchar(64) NOT NULL,
"legacy_id" integer NOT NULL,
"new_id" integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE "media" (
"id" serial PRIMARY KEY NOT NULL,
"path" varchar(512) NOT NULL,
"url" text NOT NULL,
"mime_type" varchar(128) DEFAULT '' NOT NULL,
"width" integer,
"height" integer,
"size" bigint,
"alt_text" text DEFAULT '' NOT NULL,
"title" text DEFAULT '' NOT NULL,
"ref_count" integer DEFAULT 0 NOT NULL,
"legacy_id" integer,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "menu_items" (
"id" serial PRIMARY KEY NOT NULL,
"menu_id" integer NOT NULL,
"parent_id" integer,
"title" varchar(255) NOT NULL,
"url" text NOT NULL,
"target" varchar(16) DEFAULT '_self' NOT NULL,
"sort" integer DEFAULT 0 NOT NULL,
"legacy_id" integer
);
--> statement-breakpoint
CREATE TABLE "menus" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(128) NOT NULL,
"location" varchar(64) NOT NULL,
"legacy_id" integer
);
--> statement-breakpoint
CREATE TABLE "order_items" (
"id" serial PRIMARY KEY NOT NULL,
"order_id" integer NOT NULL,
"product_id" integer,
"name" text NOT NULL,
"sku" varchar(128),
"qty" integer DEFAULT 1 NOT NULL,
"price" numeric(12, 2) DEFAULT '0' NOT NULL,
"total" numeric(12, 2) DEFAULT '0' NOT NULL
);
--> statement-breakpoint
CREATE TABLE "orders" (
"id" serial PRIMARY KEY NOT NULL,
"order_number" varchar(32) NOT NULL,
"user_id" integer,
"status" varchar(24) DEFAULT 'pending' NOT NULL,
"currency" varchar(8) DEFAULT 'USD' NOT NULL,
"subtotal" numeric(12, 2) DEFAULT '0' NOT NULL,
"shipping_total" numeric(12, 2) DEFAULT '0' NOT NULL,
"tax_total" numeric(12, 2) DEFAULT '0' NOT NULL,
"total" numeric(12, 2) DEFAULT '0' NOT NULL,
"payment_method" varchar(64),
"payment_status" varchar(24) DEFAULT 'unpaid' NOT NULL,
"transaction_id" varchar(128),
"billing" jsonb DEFAULT '{}'::jsonb NOT NULL,
"shipping" jsonb DEFAULT '{}'::jsonb NOT NULL,
"customer_note" text DEFAULT '' NOT NULL,
"legacy_id" integer,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "payments" (
"id" serial PRIMARY KEY NOT NULL,
"order_id" integer NOT NULL,
"method" varchar(64) NOT NULL,
"status" varchar(24) DEFAULT 'pending' NOT NULL,
"transaction_id" varchar(128),
"amount" numeric(12, 2) DEFAULT '0' NOT NULL,
"raw" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "product_attribute_map" (
"product_id" integer NOT NULL,
"value_id" integer NOT NULL,
CONSTRAINT "product_attribute_map_product_id_value_id_pk" PRIMARY KEY("product_id","value_id")
);
--> statement-breakpoint
CREATE TABLE "product_attribute_values" (
"id" serial PRIMARY KEY NOT NULL,
"attribute_id" integer NOT NULL,
"value" varchar(255) NOT NULL,
"slug" varchar(255) NOT NULL,
"sort" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "product_attributes" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(128) NOT NULL,
"slug" varchar(128) NOT NULL,
"filterable" boolean DEFAULT true NOT NULL,
"sort" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "product_terms" (
"product_id" integer NOT NULL,
"category_id" integer NOT NULL,
CONSTRAINT "product_terms_product_id_category_id_pk" PRIMARY KEY("product_id","category_id")
);
--> statement-breakpoint
CREATE TABLE "product_translations" (
"id" serial PRIMARY KEY NOT NULL,
"product_id" integer NOT NULL,
"lang" varchar(16) NOT NULL,
"slug" varchar(255) NOT NULL,
"title" text NOT NULL,
"short_description" text DEFAULT '' NOT NULL,
"description" text DEFAULT '' NOT NULL,
"status" varchar(16) DEFAULT 'draft' NOT NULL,
"seo_title" text,
"seo_description" text,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "products" (
"id" serial PRIMARY KEY NOT NULL,
"sku" varchar(128),
"slug" varchar(255) NOT NULL,
"title" text NOT NULL,
"short_description" text DEFAULT '' NOT NULL,
"description" text DEFAULT '' NOT NULL,
"price" numeric(12, 2),
"regular_price" numeric(12, 2),
"stock" integer,
"stock_status" varchar(32) DEFAULT 'instock' NOT NULL,
"featured_media_id" integer,
"gallery" jsonb DEFAULT '[]'::jsonb NOT NULL,
"status" varchar(16) DEFAULT 'published' NOT NULL,
"featured" boolean DEFAULT false NOT NULL,
"views" integer DEFAULT 0 NOT NULL,
"seo_title" text,
"seo_description" text,
"spec_url" text,
"meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
"legacy_id" integer,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "redirects" (
"id" serial PRIMARY KEY NOT NULL,
"source" text NOT NULL,
"target" text NOT NULL,
"code" integer DEFAULT 301 NOT NULL,
"is_regex" boolean DEFAULT false NOT NULL,
"hits" integer DEFAULT 0 NOT NULL,
"enabled" boolean DEFAULT true NOT NULL,
"legacy_id" integer
);
--> statement-breakpoint
CREATE TABLE "sessions" (
"token" varchar(64) PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "settings" (
"key" varchar(128) PRIMARY KEY NOT NULL,
"value" jsonb DEFAULT '{}'::jsonb NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" serial PRIMARY KEY NOT NULL,
"email" varchar(255) NOT NULL,
"username" varchar(128),
"display_name" varchar(255) DEFAULT '' NOT NULL,
"password_hash" varchar(255),
"legacy_password_hash" varchar(255),
"role" varchar(32) DEFAULT 'customer' NOT NULL,
"billing" jsonb DEFAULT '{}'::jsonb NOT NULL,
"shipping" jsonb DEFAULT '{}'::jsonb NOT NULL,
"legacy_id" integer,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE INDEX "audit_logs_time_idx" ON "audit_logs" USING btree ("created_at");--> statement-breakpoint
CREATE INDEX "cart_items_cart_idx" ON "cart_items" USING btree ("cart_id");--> statement-breakpoint
CREATE UNIQUE INDEX "categories_taxonomy_slug_idx" ON "categories" USING btree ("taxonomy","slug");--> statement-breakpoint
CREATE INDEX "categories_parent_idx" ON "categories" USING btree ("parent_id");--> statement-breakpoint
CREATE INDEX "content_terms_category_idx" ON "content_terms" USING btree ("category_id");--> statement-breakpoint
CREATE UNIQUE INDEX "content_translations_content_lang_idx" ON "content_translations" USING btree ("content_id","lang");--> statement-breakpoint
CREATE INDEX "content_translations_lang_status_idx" ON "content_translations" USING btree ("lang","status");--> statement-breakpoint
CREATE UNIQUE INDEX "contents_slug_type_idx" ON "contents" USING btree ("slug","type");--> statement-breakpoint
CREATE INDEX "contents_type_status_idx" ON "contents" USING btree ("type","status");--> statement-breakpoint
CREATE INDEX "contents_legacy_idx" ON "contents" USING btree ("legacy_id");--> statement-breakpoint
CREATE UNIQUE INDEX "filters_slug_idx" ON "filters" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "inquiries_status_idx" ON "inquiries" USING btree ("status","created_at");--> statement-breakpoint
CREATE INDEX "inquiries_email_idx" ON "inquiries" USING btree ("email");--> statement-breakpoint
CREATE INDEX "inquiries_created_idx" ON "inquiries" USING btree ("created_at");--> statement-breakpoint
CREATE UNIQUE INDEX "inquiry_forms_key_idx" ON "inquiry_forms" USING btree ("form_key");--> statement-breakpoint
CREATE INDEX "jobs_status_runat_idx" ON "jobs" USING btree ("status","run_at");--> statement-breakpoint
CREATE UNIQUE INDEX "legacy_map_entity_id_idx" ON "legacy_id_mappings" USING btree ("entity","legacy_id");--> statement-breakpoint
CREATE UNIQUE INDEX "media_path_idx" ON "media" USING btree ("path");--> statement-breakpoint
CREATE INDEX "media_legacy_idx" ON "media" USING btree ("legacy_id");--> statement-breakpoint
CREATE INDEX "menu_items_menu_idx" ON "menu_items" USING btree ("menu_id","sort");--> statement-breakpoint
CREATE INDEX "order_items_order_idx" ON "order_items" USING btree ("order_id");--> statement-breakpoint
CREATE UNIQUE INDEX "orders_number_idx" ON "orders" USING btree ("order_number");--> statement-breakpoint
CREATE INDEX "orders_status_idx" ON "orders" USING btree ("status");--> statement-breakpoint
CREATE INDEX "orders_user_idx" ON "orders" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "payments_order_idx" ON "payments" USING btree ("order_id");--> statement-breakpoint
CREATE UNIQUE INDEX "payments_txn_idx" ON "payments" USING btree ("transaction_id");--> statement-breakpoint
CREATE INDEX "product_attr_map_value_idx" ON "product_attribute_map" USING btree ("value_id","product_id");--> statement-breakpoint
CREATE UNIQUE INDEX "product_attr_values_attr_slug_idx" ON "product_attribute_values" USING btree ("attribute_id","slug");--> statement-breakpoint
CREATE INDEX "product_attr_values_attr_idx" ON "product_attribute_values" USING btree ("attribute_id");--> statement-breakpoint
CREATE UNIQUE INDEX "product_attributes_slug_idx" ON "product_attributes" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "product_terms_category_idx" ON "product_terms" USING btree ("category_id");--> statement-breakpoint
CREATE UNIQUE INDEX "product_translations_product_lang_idx" ON "product_translations" USING btree ("product_id","lang");--> statement-breakpoint
CREATE UNIQUE INDEX "products_slug_idx" ON "products" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "products_status_idx" ON "products" USING btree ("status");--> statement-breakpoint
CREATE INDEX "products_sku_idx" ON "products" USING btree ("sku");--> statement-breakpoint
CREATE INDEX "products_legacy_idx" ON "products" USING btree ("legacy_id");--> statement-breakpoint
CREATE INDEX "redirects_source_idx" ON "redirects" USING btree ("source");--> statement-breakpoint
CREATE INDEX "sessions_user_idx" ON "sessions" USING btree ("user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email");--> statement-breakpoint
CREATE INDEX "users_legacy_idx" ON "users" USING btree ("legacy_id");
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1793295647791,
"tag": "0000_worried_bill_hollister",
"breakpoints": true
}
]
}
+863
View File
@@ -0,0 +1,863 @@
/**
* DusunIoT 重建版 PostgreSQL 数据模型(Drizzle ORM)
*
* 按业务域组织:content / products / commerce / inquiries / ops。
* 每张表保留 legacy_id 以便对照原 WordPress 数据并支持增量同步。
*/
import {
pgTable,
serial,
varchar,
text,
integer,
bigint,
boolean,
timestamp,
numeric,
jsonb,
index,
uniqueIndex,
primaryKey,
} from "drizzle-orm/pg-core";
/* ============================= 内容域 ============================= */
/** # 数据库表
* 内容主表(页面 page / 文章 post / 落地页 landing),默认语言(英文)的正本。
* Elementor 原始 JSON 存于 meta.elementor,供 1:1 还原渲染器使用。
*/
export const contents = pgTable(
"contents",
{
/** # 数据库字段 内容 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 内容类型:page | post | landing */
type: varchar("type", { length: 32 }).notNull(),
/** # 数据库字段 URL 别名(默认语言) */
slug: varchar("slug", { length: 255 }).notNull(),
/** # 数据库字段 标题 */
title: text("title").notNull(),
/** # 数据库字段 摘要 */
excerpt: text("excerpt").default("").notNull(),
/** # 数据库字段 正文 HTML */
html: text("html").default("").notNull(),
/** # 数据库字段 状态:draft | review | published */
status: varchar("status", { length: 16 }).default("published").notNull(),
/** # 数据库字段 作者 */
authorId: integer("author_id"),
/** # 数据库字段 特色图片 */
featuredMediaId: integer("featured_media_id"),
/** # 数据库字段 页面模板名(home / product / blog / blank …) */
template: varchar("template", { length: 64 }).default("default").notNull(),
/** # 数据库字段 菜单排序 */
menuOrder: integer("menu_order").default(0).notNull(),
/** # 数据库字段 浏览次数 */
views: integer("views").default(0).notNull(),
/** # 数据库字段 是否置顶 */
sticky: boolean("sticky").default(false).notNull(),
/** # 数据库字段 SEO 标题 */
seoTitle: text("seo_title"),
/** # 数据库字段 SEO 描述 */
seoDescription: text("seo_description"),
/** # 数据库字段 扩展数据(elementor JSON 等) */
meta: jsonb("meta").default({}).notNull(),
/** # 数据库字段 原 WordPress post ID */
legacyId: integer("legacy_id"),
/** # 数据库字段 发布时间 */
publishedAt: timestamp("published_at", { withTimezone: true }),
/** # 数据库字段 创建时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
/** # 数据库字段 更新时间 */
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [
uniqueIndex("contents_slug_type_idx").on(t.slug, t.type),
index("contents_type_status_idx").on(t.type, t.status),
index("contents_legacy_idx").on(t.legacyId),
],
);
/** # 数据库表
* 内容多语言翻译版本(每种语言独立 slug/标题/正文/SEO;缺翻译时不生成伪语言页面)。
*/
export const contentTranslations = pgTable(
"content_translations",
{
/** # 数据库字段 翻译 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 内容 ID */
contentId: integer("content_id").notNull(),
/** # 数据库字段 语言代码(BCP47,如 fr / de) */
lang: varchar("lang", { length: 16 }).notNull(),
/** # 数据库字段 该语言的 URL 别名 */
slug: varchar("slug", { length: 255 }).notNull(),
/** # 数据库字段 标题 */
title: text("title").notNull(),
/** # 数据库字段 摘要 */
excerpt: text("excerpt").default("").notNull(),
/** # 数据库字段 正文 HTML */
html: text("html").default("").notNull(),
/** # 数据库字段 状态:draft | review | published */
status: varchar("status", { length: 16 }).default("draft").notNull(),
/** # 数据库字段 SEO 标题 */
seoTitle: text("seo_title"),
/** # 数据库字段 SEO 描述 */
seoDescription: text("seo_description"),
/** # 数据库字段 翻译时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
/** # 数据库字段 更新时间 */
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [
uniqueIndex("content_translations_content_lang_idx").on(t.contentId, t.lang),
index("content_translations_lang_status_idx").on(t.lang, t.status),
],
);
/** # 数据库表
* 内容修订快照(发布前留档,支持回滚)。
*/
export const contentRevisions = pgTable("content_revisions", {
/** # 数据库字段 修订 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 内容 ID */
contentId: integer("content_id").notNull(),
/** # 数据库字段 语言(缺省为默认语言) */
lang: varchar("lang", { length: 16 }).default("en").notNull(),
/** # 数据库字段 标题快照 */
title: text("title").notNull(),
/** # 数据库字段 正文 HTML 快照 */
html: text("html").notNull(),
/** # 数据库字段 修订人 */
authorId: integer("author_id"),
/** # 数据库字段 修订时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
});
/** # 数据库表
* 分类/标签(taxonomy:category / post_tag / product_cat / product_tag / 属性 pa_*)。
*/
export const categories = pgTable(
"categories",
{
/** # 数据库字段 分类 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 taxonomy 名称 */
taxonomy: varchar("taxonomy", { length: 64 }).notNull(),
/** # 数据库字段 名称 */
name: varchar("name", { length: 255 }).notNull(),
/** # 数据库字段 URL 别名 */
slug: varchar("slug", { length: 255 }).notNull(),
/** # 数据库字段 父级分类 */
parentId: integer("parent_id"),
/** # 数据库字段 描述 */
description: text("description").default("").notNull(),
/** # 数据库字段 分类图片 */
imageMediaId: integer("image_media_id"),
/** # 数据库字段 排序 */
sort: integer("sort").default(0).notNull(),
/** # 数据库字段 原 WordPress term_id */
legacyId: integer("legacy_id"),
},
(t) => [
uniqueIndex("categories_taxonomy_slug_idx").on(t.taxonomy, t.slug),
index("categories_parent_idx").on(t.parentId),
],
);
/** # 数据库表
* 内容与分类/标签的关联。
*/
export const contentTerms = pgTable(
"content_terms",
{
/** # 数据库字段 内容 ID */
contentId: integer("content_id").notNull(),
/** # 数据库字段 分类 ID */
categoryId: integer("category_id").notNull(),
},
(t) => [
primaryKey({ columns: [t.contentId, t.categoryId] }),
index("content_terms_category_idx").on(t.categoryId),
],
);
/** # 数据库表
* 导航菜单定义(header / footer / account 等位置)。
*/
export const menus = pgTable("menus", {
/** # 数据库字段 菜单 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 菜单名称 */
name: varchar("name", { length: 128 }).notNull(),
/** # 数据库字段 位置别名(menu-1 / menu-2 / max_mega_menu_1 …) */
location: varchar("location", { length: 64 }).notNull(),
/** # 数据库字段 原菜单 term_id */
legacyId: integer("legacy_id"),
});
/** # 数据库表
* 菜单项(支持多级树)。
*/
export const menuItems = pgTable(
"menu_items",
{
/** # 数据库字段 菜单项 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 所属菜单 */
menuId: integer("menu_id").notNull(),
/** # 数据库字段 父菜单项 */
parentId: integer("parent_id"),
/** # 数据库字段 显示文字 */
title: varchar("title", { length: 255 }).notNull(),
/** # 数据库字段 链接地址 */
url: text("url").notNull(),
/** # 数据库字段 打开方式:_self | _blank */
target: varchar("target", { length: 16 }).default("_self").notNull(),
/** # 数据库字段 排序 */
sort: integer("sort").default(0).notNull(),
/** # 数据库字段 原 menu_item post_id */
legacyId: integer("legacy_id"),
},
(t) => [index("menu_items_menu_idx").on(t.menuId, t.sort)],
);
/* ============================= 产品域 ============================= */
/** # 数据库表
* 产品主表(WooCommerce 迁移)。
*/
export const products = pgTable(
"products",
{
/** # 数据库字段 产品 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 SKU */
sku: varchar("sku", { length: 128 }),
/** # 数据库字段 URL 别名 */
slug: varchar("slug", { length: 255 }).notNull(),
/** # 数据库字段 名称 */
title: text("title").notNull(),
/** # 数据库字段 短描述 */
shortDescription: text("short_description").default("").notNull(),
/** # 数据库字段 详细描述 HTML */
description: text("description").default("").notNull(),
/** # 数据库字段 现价 */
price: numeric("price", { precision: 12, scale: 2 }),
/** # 数据库字段 原价 */
regularPrice: numeric("regular_price", { precision: 12, scale: 2 }),
/** # 数据库字段 库存数量 */
stock: integer("stock"),
/** # 数据库字段 库存状态:instock | outofstock | onbackorder */
stockStatus: varchar("stock_status", { length: 32 }).default("instock").notNull(),
/** # 数据库字段 特色图片 */
featuredMediaId: integer("featured_media_id"),
/** # 数据库字段 图廊(媒体 ID 数组) */
gallery: jsonb("gallery").default([]).notNull(),
/** # 数据库字段 状态:draft | published */
status: varchar("status", { length: 16 }).default("published").notNull(),
/** # 数据库字段 是否主推产品 */
featured: boolean("featured").default(false).notNull(),
/** # 数据库字段 浏览次数 */
views: integer("views").default(0).notNull(),
/** # 数据库字段 SEO 标题 */
seoTitle: text("seo_title"),
/** # 数据库字段 SEO 描述 */
seoDescription: text("seo_description"),
/** # 数据库字段 规格书下载地址 */
specUrl: text("spec_url"),
/** # 数据库字段 Elementor 原始 JSON */
meta: jsonb("meta").default({}).notNull(),
/** # 数据库字段 原 WordPress post ID */
legacyId: integer("legacy_id"),
/** # 数据库字段 创建时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
/** # 数据库字段 更新时间 */
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [
uniqueIndex("products_slug_idx").on(t.slug),
index("products_status_idx").on(t.status),
index("products_sku_idx").on(t.sku),
index("products_legacy_idx").on(t.legacyId),
],
);
/** # 数据库表
* 产品多语言翻译。
*/
export const productTranslations = pgTable(
"product_translations",
{
/** # 数据库字段 翻译 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 产品 ID */
productId: integer("product_id").notNull(),
/** # 数据库字段 语言代码 */
lang: varchar("lang", { length: 16 }).notNull(),
/** # 数据库字段 该语言 slug */
slug: varchar("slug", { length: 255 }).notNull(),
/** # 数据库字段 名称 */
title: text("title").notNull(),
/** # 数据库字段 短描述 */
shortDescription: text("short_description").default("").notNull(),
/** # 数据库字段 详细描述 */
description: text("description").default("").notNull(),
/** # 数据库字段 状态:draft | review | published */
status: varchar("status", { length: 16 }).default("draft").notNull(),
/** # 数据库字段 SEO 标题 */
seoTitle: text("seo_title"),
/** # 数据库字段 SEO 描述 */
seoDescription: text("seo_description"),
/** # 数据库字段 更新时间 */
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [uniqueIndex("product_translations_product_lang_idx").on(t.productId, t.lang)],
);
/** # 数据库表
* 产品属性定义(RAM / Flash / Chip Platform / Downlink 等,原 WooCommerce 全局属性)。
*/
export const productAttributes = pgTable(
"product_attributes",
{
/** # 数据库字段 属性 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 属性名 */
name: varchar("name", { length: 128 }).notNull(),
/** # 数据库字段 属性 slug(attribute_id,如 ram / chip-platform) */
slug: varchar("slug", { length: 128 }).notNull(),
/** # 数据库字段 是否作为筛选条件 */
filterable: boolean("filterable").default(true).notNull(),
/** # 数据库字段 排序 */
sort: integer("sort").default(0).notNull(),
},
(t) => [uniqueIndex("product_attributes_slug_idx").on(t.slug)],
);
/** # 数据库表
* 产品属性可选值。
*/
export const productAttributeValues = pgTable(
"product_attribute_values",
{
/** # 数据库字段 值 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 所属属性 */
attributeId: integer("attribute_id").notNull(),
/** # 数据库字段 显示值 */
value: varchar("value", { length: 255 }).notNull(),
/** # 数据库字段 值 slug */
slug: varchar("slug", { length: 255 }).notNull(),
/** # 数据库字段 排序 */
sort: integer("sort").default(0).notNull(),
},
(t) => [
uniqueIndex("product_attr_values_attr_slug_idx").on(t.attributeId, t.slug),
index("product_attr_values_attr_idx").on(t.attributeId),
],
);
/** # 数据库表
* 产品 ↔ 属性值 关联(筛选查询主路径,建立复合索引)。
*/
export const productAttributeMap = pgTable(
"product_attribute_map",
{
/** # 数据库字段 产品 ID */
productId: integer("product_id").notNull(),
/** # 数据库字段 属性值 ID */
valueId: integer("value_id").notNull(),
},
(t) => [
primaryKey({ columns: [t.productId, t.valueId] }),
index("product_attr_map_value_idx").on(t.valueId, t.productId),
],
);
/** # 数据库表
* 产品 ↔ 产品分类 关联。
*/
export const productTerms = pgTable(
"product_terms",
{
/** # 数据库字段 产品 ID */
productId: integer("product_id").notNull(),
/** # 数据库字段 分类 ID(taxonomy=product_cat) */
categoryId: integer("category_id").notNull(),
},
(t) => [
primaryKey({ columns: [t.productId, t.categoryId] }),
index("product_terms_category_idx").on(t.categoryId),
],
);
/* ============================= 商城域 ============================= */
/** # 数据库表
* 用户(后台账号 + 商城注册客户)。保留 WordPress phpass 密码哈希用于存量客户登录兼容。
*/
export const users = pgTable(
"users",
{
/** # 数据库字段 用户 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 邮箱(登录名) */
email: varchar("email", { length: 255 }).notNull(),
/** # 数据库字段 用户名 */
username: varchar("username", { length: 128 }),
/** # 数据库字段 显示名称 */
displayName: varchar("display_name", { length: 255 }).default("").notNull(),
/** # 数据库字段 密码哈希(新账号 bcrypt) */
passwordHash: varchar("password_hash", { length: 255 }),
/** # 数据库字段 WordPress 旧密码哈希(phpass $P$) */
legacyPasswordHash: varchar("legacy_password_hash", { length: 255 }),
/** # 数据库字段 角色:admin | editor | manager | customer */
role: varchar("role", { length: 32 }).default("customer").notNull(),
/** # 数据库字段 账单地址 */
billing: jsonb("billing").default({}).notNull(),
/** # 数据库字段 收货地址 */
shipping: jsonb("shipping").default({}).notNull(),
/** # 数据库字段 原 WP user ID */
legacyId: integer("legacy_id"),
/** # 数据库字段 创建时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [
uniqueIndex("users_email_idx").on(t.email),
index("users_legacy_idx").on(t.legacyId),
],
);
/** # 数据库表
* 服务端会话(HttpOnly Cookie → 会话记录)。
*/
export const sessions = pgTable(
"sessions",
{
/** # 数据库字段 会话令牌 */
token: varchar("token", { length: 64 }).primaryKey(),
/** # 数据库字段 用户 ID */
userId: integer("user_id").notNull(),
/** # 数据库字段 过期时间 */
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
/** # 数据库字段 创建时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [index("sessions_user_idx").on(t.userId)],
);
/** # 数据库表
* 购物车(登录用户或匿名 session_key 维度)。
*/
export const carts = pgTable("carts", {
/** # 数据库字段 购物车 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 用户 ID(登录用户) */
userId: integer("user_id"),
/** # 数据库字段 匿名会话键 */
sessionKey: varchar("session_key", { length: 64 }),
/** # 数据库字段 创建时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
/** # 数据库字段 更新时间 */
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
});
/** # 数据库表
* 购物车明细。
*/
export const cartItems = pgTable(
"cart_items",
{
/** # 数据库字段 明细 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 购物车 ID */
cartId: integer("cart_id").notNull(),
/** # 数据库字段 产品 ID */
productId: integer("product_id").notNull(),
/** # 数据库字段 数量 */
qty: integer("qty").default(1).notNull(),
},
(t) => [index("cart_items_cart_idx").on(t.cartId)],
);
/** # 数据库表
* 订单主表。
*/
export const orders = pgTable(
"orders",
{
/** # 数据库字段 订单 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 订单号(对外展示) */
orderNumber: varchar("order_number", { length: 32 }).notNull(),
/** # 数据库字段 客户 ID */
userId: integer("user_id"),
/** # 数据库字段 状态:pending | on-hold | processing | completed | cancelled | refunded */
status: varchar("status", { length: 24 }).default("pending").notNull(),
/** # 数据库字段 币种 */
currency: varchar("currency", { length: 8 }).default("USD").notNull(),
/** # 数据库字段 商品小计 */
subtotal: numeric("subtotal", { precision: 12, scale: 2 }).default("0").notNull(),
/** # 数据库字段 运费 */
shippingTotal: numeric("shipping_total", { precision: 12, scale: 2 }).default("0").notNull(),
/** # 数据库字段 税费 */
taxTotal: numeric("tax_total", { precision: 12, scale: 2 }).default("0").notNull(),
/** # 数据库字段 订单总额 */
total: numeric("total", { precision: 12, scale: 2 }).default("0").notNull(),
/** # 数据库字段 支付方式 */
paymentMethod: varchar("payment_method", { length: 64 }),
/** # 数据库字段 支付状态 */
paymentStatus: varchar("payment_status", { length: 24 }).default("unpaid").notNull(),
/** # 数据库字段 支付交易号 */
transactionId: varchar("transaction_id", { length: 128 }),
/** # 数据库字段 账单信息 */
billing: jsonb("billing").default({}).notNull(),
/** # 数据库字段 收货信息 */
shipping: jsonb("shipping").default({}).notNull(),
/** # 数据库字段 客户备注 */
customerNote: text("customer_note").default("").notNull(),
/** # 数据库字段 原 WP order ID */
legacyId: integer("legacy_id"),
/** # 数据库字段 下单时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
/** # 数据库字段 更新时间 */
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [
uniqueIndex("orders_number_idx").on(t.orderNumber),
index("orders_status_idx").on(t.status),
index("orders_user_idx").on(t.userId),
],
);
/** # 数据库表
* 订单明细(下单时快照商品名/价格)。
*/
export const orderItems = pgTable(
"order_items",
{
/** # 数据库字段 明细 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 订单 ID */
orderId: integer("order_id").notNull(),
/** # 数据库字段 产品 ID */
productId: integer("product_id"),
/** # 数据库字段 商品名快照 */
name: text("name").notNull(),
/** # 数据库字段 SKU 快照 */
sku: varchar("sku", { length: 128 }),
/** # 数据库字段 数量 */
qty: integer("qty").default(1).notNull(),
/** # 数据库字段 单价 */
price: numeric("price", { precision: 12, scale: 2 }).default("0").notNull(),
/** # 数据库字段 小计 */
total: numeric("total", { precision: 12, scale: 2 }).default("0").notNull(),
},
(t) => [index("order_items_order_idx").on(t.orderId)],
);
/** # 数据库表
* 支付流水(含第三方回调原始报文,幂等键 transaction_id)。
*/
export const payments = pgTable(
"payments",
{
/** # 数据库字段 支付 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 订单 ID */
orderId: integer("order_id").notNull(),
/** # 数据库字段 支付方式 */
method: varchar("method", { length: 64 }).notNull(),
/** # 数据库字段 状态:succeeded | failed | refunded | pending */
status: varchar("status", { length: 24 }).default("pending").notNull(),
/** # 数据库字段 交易号(唯一,回调幂等) */
transactionId: varchar("transaction_id", { length: 128 }),
/** # 数据库字段 金额 */
amount: numeric("amount", { precision: 12, scale: 2 }).default("0").notNull(),
/** # 数据库字段 回调原始数据 */
raw: jsonb("raw").default({}).notNull(),
/** # 数据库字段 创建时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [
index("payments_order_idx").on(t.orderId),
uniqueIndex("payments_txn_idx").on(t.transactionId),
],
);
/* ============================= 询盘域 ============================= */
/** # 数据库表
* 询盘表单定义(Contact Form 7 迁移,字段结构存 JSON)。
*/
export const inquiryForms = pgTable(
"inquiry_forms",
{
/** # 数据库字段 表单 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 表单名称 */
name: varchar("name", { length: 255 }).notNull(),
/** # 数据库字段 表单 key(slug) */
formKey: varchar("form_key", { length: 128 }).notNull(),
/** # 数据库字段 字段定义 JSON */
fields: jsonb("fields").default([]).notNull(),
/** # 数据库字段 收件邮箱 */
mailTo: varchar("mail_to", { length: 255 }),
/** # 数据库字段 是否启用 */
enabled: boolean("enabled").default(true).notNull(),
/** # 数据库字段 原 CF7 form_id */
legacyId: integer("legacy_id"),
},
(t) => [uniqueIndex("inquiry_forms_key_idx").on(t.formKey)],
);
/** # 数据库表
* 询盘记录(CF7/Flamingo 与 Elementor Form 提交合并归一)。
*/
export const inquiries = pgTable(
"inquiries",
{
/** # 数据库字段 询盘 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 来源表单名 */
formName: varchar("form_name", { length: 255 }).default("").notNull(),
/** # 数据库字段 表单 ID */
formId: integer("form_id"),
/** # 数据库字段 姓名 */
name: varchar("name", { length: 255 }).default("").notNull(),
/** # 数据库字段 邮箱 */
email: varchar("email", { length: 255 }).default("").notNull(),
/** # 数据库字段 电话 */
phone: varchar("phone", { length: 128 }).default("").notNull(),
/** # 数据库字段 公司 */
company: varchar("company", { length: 255 }).default("").notNull(),
/** # 数据库字段 国家/地区 */
country: varchar("country", { length: 128 }).default("").notNull(),
/** # 数据库字段 询盘内容 */
message: text("message").default("").notNull(),
/** # 数据库字段 感兴趣的产品 */
productInterest: varchar("product_interest", { length: 255 }).default("").notNull(),
/** # 数据库字段 完整原始字段(含 annualdemand、utm 等) */
fields: jsonb("fields").default({}).notNull(),
/** # 数据库字段 状态:new | read | replied | spam | archived */
status: varchar("status", { length: 16 }).default("new").notNull(),
/** # 数据库字段 来源渠道(channel / subject,可能很长用 text) */
channel: text("channel").default("").notNull(),
/** # 数据库字段 来源页面 */
sourcePage: text("source_page").default("").notNull(),
/** # 数据库字段 来源 IP */
ip: varchar("ip", { length: 64 }).default("").notNull(),
/** # 数据库字段 UTM 参数 */
utm: jsonb("utm").default({}).notNull(),
/** # 数据库字段 原 Flamingo/Elementor 记录 ID */
legacyId: integer("legacy_id"),
/** # 数据库字段 提交时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [
index("inquiries_status_idx").on(t.status, t.createdAt),
index("inquiries_email_idx").on(t.email),
index("inquiries_created_idx").on(t.createdAt),
],
);
/* ============================= 运营域 ============================= */
/** # 数据库表
* 媒体库(原 wp-content/uploads 附件)。
*/
export const media = pgTable(
"media",
{
/** # 数据库字段 媒体 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 相对路径(uploads 下) */
path: varchar("path", { length: 512 }).notNull(),
/** # 数据库字段 访问 URL */
url: text("url").notNull(),
/** # 数据库字段 MIME 类型 */
mimeType: varchar("mime_type", { length: 128 }).default("").notNull(),
/** # 数据库字段 宽 */
width: integer("width"),
/** # 数据库字段 高 */
height: integer("height"),
/** # 数据库字段 文件大小 */
size: bigint("size", { mode: "number" }),
/** # 数据库字段 替代文本 */
altText: text("alt_text").default("").notNull(),
/** # 数据库字段 标题 */
title: text("title").default("").notNull(),
/** # 数据库字段 引用次数 */
refCount: integer("ref_count").default(0).notNull(),
/** # 数据库字段 原 WP attachment ID */
legacyId: integer("legacy_id"),
/** # 数据库字段 上传时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [
uniqueIndex("media_path_idx").on(t.path),
index("media_legacy_idx").on(t.legacyId),
],
);
/** # 数据库表
* 301/302 重定向(Redirection 插件迁移)。
*/
export const redirects = pgTable(
"redirects",
{
/** # 数据库字段 重定向 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 源路径(支持正则) */
source: text("source").notNull(),
/** # 数据库字段 目标地址 */
target: text("target").notNull(),
/** # 数据库字段 状态码 */
code: integer("code").default(301).notNull(),
/** # 数据库字段 是否正则匹配 */
isRegex: boolean("is_regex").default(false).notNull(),
/** # 数据库字段 命中次数 */
hits: integer("hits").default(0).notNull(),
/** # 数据库字段 是否启用 */
enabled: boolean("enabled").default(true).notNull(),
/** # 数据库字段 原 redirection item id */
legacyId: integer("legacy_id"),
},
(t) => [index("redirects_source_idx").on(t.source)],
);
/** # 数据库表
* 筛选配置(Filter Everything 迁移,前台产品筛选器)。
*/
export const filters = pgTable(
"filters",
{
/** # 数据库字段 筛选 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 筛选名称 */
name: varchar("name", { length: 128 }).notNull(),
/** # 数据库字段 URL 参数名(如 chip-platform) */
slug: varchar("slug", { length: 128 }).notNull(),
/** # 数据库字段 类型:attribute | category | price */
type: varchar("type", { length: 24 }).default("attribute").notNull(),
/** # 数据库字段 关联属性 ID(type=attribute 时) */
attributeId: integer("attribute_id"),
/** # 数据库字段 关联 taxonomy */
taxonomy: varchar("taxonomy", { length: 64 }),
/** # 数据库字段 显示位置 */
position: integer("position").default(0).notNull(),
/** # 数据库字段 是否启用 */
enabled: boolean("enabled").default(true).notNull(),
/** # 数据库字段 原 filter-field post_id */
legacyId: integer("legacy_id"),
},
(t) => [uniqueIndex("filters_slug_idx").on(t.slug)],
);
/** # 数据库表
* 语言配置(GTranslate 迁移:启用的语言与默认语言)。
*/
export const languages = pgTable(
"languages",
{
/** # 数据库字段 语言代码(BCP47) */
code: varchar("code", { length: 16 }).primaryKey(),
/** # 数据库字段 显示名 */
name: varchar("name", { length: 64 }).notNull(),
/** # 数据库字段 是否默认语言 */
isDefault: boolean("is_default").default(false).notNull(),
/** # 数据库字段 是否启用(可发布真实页面) */
enabled: boolean("enabled").default(false).notNull(),
/** # 数据库字段 是否机器翻译(GTranslate 存量) */
machine: boolean("machine").default(false).notNull(),
/** # 数据库字段 排序 */
sort: integer("sort").default(0).notNull(),
},
);
/** # 数据库表
* 持久化任务队列(独立于 Web 请求的后台任务,带重试)。
*/
export const jobs = pgTable(
"jobs",
{
/** # 数据库字段 任务 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 任务类型(email.send / media.reindex …) */
type: varchar("type", { length: 64 }).notNull(),
/** # 数据库字段 任务参数 */
payload: jsonb("payload").default({}).notNull(),
/** # 数据库字段 状态:pending | running | done | failed */
status: varchar("status", { length: 16 }).default("pending").notNull(),
/** # 数据库字段 已重试次数 */
attempts: integer("attempts").default(0).notNull(),
/** # 数据库字段 最大重试次数 */
maxAttempts: integer("max_attempts").default(3).notNull(),
/** # 数据库字段 计划执行时间 */
runAt: timestamp("run_at", { withTimezone: true }).defaultNow().notNull(),
/** # 数据库字段 最近错误 */
lastError: text("last_error"),
/** # 数据库字段 创建时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
/** # 数据库字段 更新时间 */
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [index("jobs_status_runat_idx").on(t.status, t.runAt)],
);
/** # 数据库表
* 审计日志(后台敏感操作留痕)。
*/
export const auditLogs = pgTable(
"audit_logs",
{
/** # 数据库字段 日志 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 操作人 */
actorId: integer("actor_id"),
/** # 数据库字段 动作(如 product.update) */
action: varchar("action", { length: 128 }).notNull(),
/** # 数据库字段 实体类型 */
entity: varchar("entity", { length: 64 }).notNull(),
/** # 数据库字段 实体 ID */
entityId: varchar("entity_id", { length: 64 }),
/** # 数据库字段 附加数据 */
meta: jsonb("meta").default({}).notNull(),
/** # 数据库字段 操作 IP */
ip: varchar("ip", { length: 64 }).default("").notNull(),
/** # 数据库字段 操作时间 */
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
},
(t) => [index("audit_logs_time_idx").on(t.createdAt)],
);
/** # 数据库表
* 站点设置(键值 JSON:导航、邮件、支付、缓存开关等)。
*/
export const settings = pgTable("settings", {
/** # 数据库字段 设置键 */
key: varchar("key", { length: 128 }).primaryKey(),
/** # 数据库字段 设置值 */
value: jsonb("value").default({}).notNull(),
/** # 数据库字段 更新时间 */
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
});
/** # 数据库表
* 旧 ID 映射(WordPress ID → 新系统 ID,迁移校验与增量同步用)。
*/
export const legacyIdMappings = pgTable(
"legacy_id_mappings",
{
/** # 数据库字段 映射 ID */
id: serial("id").primaryKey(),
/** # 数据库字段 实体类型(post/product/user/order/media …) */
entity: varchar("entity", { length: 64 }).notNull(),
/** # 数据库字段 原 WP ID */
legacyId: integer("legacy_id").notNull(),
/** # 数据库字段 新系统 ID */
newId: integer("new_id").notNull(),
},
(t) => [uniqueIndex("legacy_map_entity_id_idx").on(t.entity, t.legacyId)],
);