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.
115 lines
5.2 KiB
115 lines
5.2 KiB
/**
|
|
* @param { import("knex").Knex } knex
|
|
* @returns { Promise<void> }
|
|
*/
|
|
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<void> }
|
|
*/
|
|
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")
|
|
}
|
|
|