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.
 

349 lines
11 KiB

import db from "../index.js"
class BookmarkModel {
static async findAll(userId = null, options = {}) {
let query = db("bookmarks")
.select("bookmarks.*")
.select("categories.name as category_name")
.select("categories.color as category_color")
.leftJoin("categories", "bookmarks.category_id", "categories.id")
if (userId) {
query = query.where("bookmarks.user_id", userId)
}
// 分类过滤
if (options.categoryId) {
query = query.where("bookmarks.category_id", options.categoryId)
}
// 标签过滤
if (options.tagIds && options.tagIds.length > 0) {
query = query
.join("bookmark_tags", "bookmarks.id", "bookmark_tags.bookmark_id")
.whereIn("bookmark_tags.tag_id", options.tagIds)
}
// 搜索过滤
if (options.search) {
const searchTerm = `%${options.search}%`
query = query.where(function() {
this.where("bookmarks.title", "like", searchTerm)
.orWhere("bookmarks.description", "like", searchTerm)
.orWhere("bookmarks.url", "like", searchTerm)
})
}
// 排序
const orderBy = options.orderBy || "created_at"
const orderDirection = options.orderDirection || "desc"
query = query.orderBy(`bookmarks.${orderBy}`, orderDirection)
// 分页
if (options.limit) {
query = query.limit(options.limit)
}
if (options.offset) {
query = query.offset(options.offset)
}
return query
}
static async findById(id, userId = null) {
let query = db("bookmarks")
.select("bookmarks.*")
.select("categories.name as category_name")
.select("categories.color as category_color")
.leftJoin("categories", "bookmarks.category_id", "categories.id")
.where("bookmarks.id", id)
if (userId) {
query = query.where("bookmarks.user_id", userId)
}
return query.first()
}
static async create(data) {
// 提取 tags 和 links,避免插入到 bookmarks 表
const { tags, links, ...bookmarkData } = data
const bookmark = await db("bookmarks").insert({
...bookmarkData,
created_at: db.fn.now(),
updated_at: db.fn.now(),
last_visited: db.fn.now(),
}).returning("*")
// 如果有标签,创建标签关联
if (tags && tags.length > 0) {
await this.addTags(bookmark[0].id, tags, bookmarkData.user_id)
}
// 如果有多链接,创建链接记录
if (links && links.length > 0) {
await this.addLinks(bookmark[0].id, links)
}
return bookmark[0]
}
static async update(id, data, userId = null) {
// 提取 tags 和 links,避免更新到 bookmarks 表
const { tags, links, ...bookmarkData } = data
let query = db("bookmarks").where("id", id)
if (userId) {
query = query.where("user_id", userId)
}
const bookmark = await query.update({
...bookmarkData,
updated_at: db.fn.now(),
}).returning("*")
// 更新标签
if (tags !== undefined) {
await this.updateTags(id, tags, userId)
}
// 更新链接
if (links !== undefined) {
await this.updateLinks(id, links)
}
return bookmark[0]
}
static async delete(id, userId = null) {
let query = db("bookmarks").where("id", id)
if (userId) {
query = query.where("user_id", userId)
}
return query.del()
}
static async incrementClickCount(id) {
return db("bookmarks")
.where("id", id)
.increment("click_count", 1)
.update({
last_visited: db.fn.now(),
updated_at: db.fn.now(),
})
}
static async toggleFavorite(id, userId) {
const bookmark = await this.findById(id, userId)
if (!bookmark) return null
return db("bookmarks")
.where("id", id)
.where("user_id", userId)
.update({
is_favorite: !bookmark.is_favorite,
updated_at: db.fn.now(),
})
.returning("*")
}
static async getFavorites(userId, limit = 20) {
return db("bookmarks")
.select("bookmarks.*")
.select("categories.name as category_name")
.leftJoin("categories", "bookmarks.category_id", "categories.id")
.where("bookmarks.user_id", userId)
.where("bookmarks.is_favorite", true)
.orderBy("bookmarks.updated_at", "desc")
.limit(limit)
}
static async getRecentBookmarks(userId, limit = 10) {
return db("bookmarks")
.select("bookmarks.*")
.select("categories.name as category_name")
.leftJoin("categories", "bookmarks.category_id", "categories.id")
.where("bookmarks.user_id", userId)
.orderBy("bookmarks.last_visited", "desc")
.limit(limit)
}
static async getPopularBookmarks(userId, limit = 10) {
return db("bookmarks")
.select("bookmarks.*")
.select("categories.name as category_name")
.leftJoin("categories", "bookmarks.category_id", "categories.id")
.where("bookmarks.user_id", userId)
.orderBy("bookmarks.click_count", "desc")
.limit(limit)
}
static async getBookmarksByCategory(categoryId, userId, limit = 20) {
return db("bookmarks")
.select("bookmarks.*")
.select("categories.name as category_name")
.leftJoin("categories", "bookmarks.category_id", "categories.id")
.where("bookmarks.category_id", categoryId)
.where("bookmarks.user_id", userId)
.orderBy("bookmarks.sort_order", "asc")
.orderBy("bookmarks.created_at", "desc")
.limit(limit)
}
static async getBookmarksByTag(tagId, userId, limit = 20) {
return db("bookmarks")
.select("bookmarks.*")
.select("categories.name as category_name")
.leftJoin("categories", "bookmarks.category_id", "categories.id")
.join("bookmark_tags", "bookmarks.id", "bookmark_tags.bookmark_id")
.where("bookmark_tags.tag_id", tagId)
.where("bookmarks.user_id", userId)
.orderBy("bookmarks.created_at", "desc")
.limit(limit)
}
// 标签相关方法
static async addTags(bookmarkId, tagNames, userId) {
const tags = []
for (const tagName of tagNames) {
// 使用 findOrCreate 自动创建不存在的标签
const tag = await db("tags")
.where("name", tagName.trim())
.where("user_id", userId)
.first()
if (tag) {
tags.push(tag.id)
} else {
// 如果标签不存在,创建新标签
const newTag = await db("tags").insert({
name: tagName.trim(),
user_id: userId,
created_at: db.fn.now(),
updated_at: db.fn.now(),
}).returning("*")
tags.push(newTag[0].id)
}
}
if (tags.length > 0) {
const bookmarkTags = tags.map(tagId => ({
bookmark_id: bookmarkId,
tag_id: tagId,
created_at: db.fn.now(),
}))
await db("bookmark_tags").insert(bookmarkTags)
}
}
static async updateTags(bookmarkId, tagNames, userId) {
// 删除现有标签关联
await db("bookmark_tags").where("bookmark_id", bookmarkId).del()
// 添加新标签关联
if (tagNames && tagNames.length > 0) {
await this.addTags(bookmarkId, tagNames, userId)
}
}
static async getTags(bookmarkId) {
const tags = await db("tags")
.select("tags.name")
.join("bookmark_tags", "tags.id", "bookmark_tags.tag_id")
.where("bookmark_tags.bookmark_id", bookmarkId)
.orderBy("tags.name", "asc")
// 返回标签名称数组
return tags.map(tag => tag.name)
}
// 链接相关方法
static async addLinks(bookmarkId, links) {
if (links && links.length > 0) {
const bookmarkLinks = links.map((link, index) => ({
bookmark_id: bookmarkId,
title: link.title,
url: link.url,
description: link.description,
type: link.type || "link",
sort_order: link.sort_order || index,
created_at: db.fn.now(),
updated_at: db.fn.now(),
}))
await db("bookmark_links").insert(bookmarkLinks)
}
}
static async updateLinks(bookmarkId, links) {
// 删除现有链接
await db("bookmark_links").where("bookmark_id", bookmarkId).del()
// 添加新链接
if (links && links.length > 0) {
await this.addLinks(bookmarkId, links)
}
}
static async getLinks(bookmarkId) {
const links = await db("bookmark_links")
.select("title", "url", "type", "description")
.where("bookmark_id", bookmarkId)
.where("is_active", true)
.orderBy("sort_order", "asc")
.orderBy("created_at", "asc")
return links
}
// 统计方法
static async getStats(userId) {
const [totalBookmarks] = await db("bookmarks")
.where("user_id", userId)
.count("* as count")
const [favoriteBookmarks] = await db("bookmarks")
.where("user_id", userId)
.where("is_favorite", true)
.count("* as count")
const [totalClicks] = await db("bookmarks")
.where("user_id", userId)
.sum("click_count as total")
return {
total: parseInt(totalBookmarks.count),
favorites: parseInt(favoriteBookmarks.count),
totalClicks: parseInt(totalClicks.total) || 0,
}
}
static async searchBookmarks(query, userId, options = {}) {
const searchTerm = `%${query}%`
let searchQuery = db("bookmarks")
.select("bookmarks.*")
.select("categories.name as category_name")
.leftJoin("categories", "bookmarks.category_id", "categories.id")
.where("bookmarks.user_id", userId)
.where(function() {
this.where("bookmarks.title", "like", searchTerm)
.orWhere("bookmarks.description", "like", searchTerm)
.orWhere("bookmarks.url", "like", searchTerm)
})
// 标签搜索
if (options.includeTags) {
searchQuery = searchQuery
.leftJoin("bookmark_tags", "bookmarks.id", "bookmark_tags.bookmark_id")
.leftJoin("tags", "bookmark_tags.tag_id", "tags.id")
.orWhere("tags.name", "like", searchTerm)
}
return searchQuery
.orderBy("bookmarks.updated_at", "desc")
.limit(options.limit || 50)
}
}
export default BookmarkModel
export { BookmarkModel }