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
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 }
|
|
|