/** * @param { import("knex").Knex } knex * @returns { Promise } */ export const up = async knex => { // 创建分类表 await knex.schema.createTable("categories", function (table) { table.increments("id").primary() table.string("name", 100).notNullable().unique() table.string("description", 500) table.string("color", 7).defaultTo("#3B82F6") // 十六进制颜色值 table.string("icon", 50) // 图标类名或路径 table.integer("sort_order").defaultTo(0) table.boolean("is_active").defaultTo(true) table.integer("user_id").unsigned().references("id").inTable("users").onDelete("CASCADE") table.timestamp("created_at").defaultTo(knex.fn.now()) table.timestamp("updated_at").defaultTo(knex.fn.now()) }) // 创建标签表 await knex.schema.createTable("tags", function (table) { table.increments("id").primary() table.string("name", 100).notNullable() table.string("description", 500) table.string("color", 7).defaultTo("#6B7280") table.integer("user_id").unsigned().references("id").inTable("users").onDelete("CASCADE") table.timestamp("created_at").defaultTo(knex.fn.now()) table.timestamp("updated_at").defaultTo(knex.fn.now()) // 同一用户下标签名唯一 table.unique(["name", "user_id"]) }) // 创建收藏主表 await knex.schema.createTable("bookmarks", function (table) { table.increments("id").primary() table.string("title", 200).notNullable() table.text("description") table.string("url", 1000).notNullable() table.string("favicon", 500) // 网站图标 table.string("screenshot", 500) // 截图路径 table.integer("category_id").unsigned().references("id").inTable("categories").onDelete("SET NULL") table.integer("user_id").unsigned().references("id").inTable("users").onDelete("CASCADE") table.boolean("is_public").defaultTo(false) // 是否公开 table.boolean("is_favorite").defaultTo(false) // 是否特别收藏 table.integer("click_count").defaultTo(0) // 点击次数 table.integer("sort_order").defaultTo(0) table.json("metadata") // 存储额外的元数据,如网站标题、描述等 table.timestamp("last_visited").defaultTo(knex.fn.now()) // 最后访问时间 table.timestamp("created_at").defaultTo(knex.fn.now()) table.timestamp("updated_at").defaultTo(knex.fn.now()) // 索引 table.index(["user_id", "category_id"]) table.index(["user_id", "is_favorite"]) table.index(["user_id", "created_at"]) }) // 创建收藏与标签关联表 await knex.schema.createTable("bookmark_tags", function (table) { table.increments("id").primary() table.integer("bookmark_id").unsigned().notNullable().references("id").inTable("bookmarks").onDelete("CASCADE") table.integer("tag_id").unsigned().notNullable().references("id").inTable("tags").onDelete("CASCADE") table.timestamp("created_at").defaultTo(knex.fn.now()) // 唯一约束,防止重复关联 table.unique(["bookmark_id", "tag_id"]) table.index(["bookmark_id"]) table.index(["tag_id"]) }) // 创建收藏的多链接表 await knex.schema.createTable("bookmark_links", function (table) { table.increments("id").primary() table.integer("bookmark_id").unsigned().notNullable().references("id").inTable("bookmarks").onDelete("CASCADE") table.string("title", 200).notNullable() table.string("url", 1000).notNullable() table.string("description", 500) table.string("type", 50).defaultTo("link") // link, download, api, etc. table.boolean("is_active").defaultTo(true) table.integer("sort_order").defaultTo(0) table.timestamp("created_at").defaultTo(knex.fn.now()) table.timestamp("updated_at").defaultTo(knex.fn.now()) table.index(["bookmark_id"]) table.index(["type"]) }) // 创建收藏历史表(可选,用于统计和分析) await knex.schema.createTable("bookmark_history", function (table) { table.increments("id").primary() table.integer("bookmark_id").unsigned().notNullable().references("id").inTable("bookmarks").onDelete("CASCADE") table.integer("user_id").unsigned().notNullable().references("id").inTable("users").onDelete("CASCADE") table.string("action", 50).notNullable() // visit, favorite, share, etc. table.json("context") // 存储上下文信息 table.timestamp("created_at").defaultTo(knex.fn.now()) table.index(["bookmark_id"]) table.index(["user_id"]) table.index(["created_at"]) }) } /** * @param { import("knex").Knex } knex * @returns { Promise } */ export const down = async knex => { await knex.schema.dropTableIfExists("bookmark_history") await knex.schema.dropTableIfExists("bookmark_links") await knex.schema.dropTableIfExists("bookmark_tags") await knex.schema.dropTableIfExists("bookmarks") await knex.schema.dropTableIfExists("tags") await knex.schema.dropTableIfExists("categories") }