feat: 多租户账号体系 + 前台收藏按钮
- 新增 users 表(user_id 数据隔离,bcrypt 密码) - 认证: 注册/登录(用户名+密码)/会话绑定用户, 首个用户成为管理员并接管旧数据 - 数据隔离: 笔记/分类/标签/回收站/版本/草稿/图谱/FTS 全部按用户隔离 - 前台: 登录/注册弹窗, 登录后★收藏自己的笔记, 游客只读公开笔记 - 后台: 用户名+密码登录, 每人管理自己的工作区, 越权访问返回404 - 冒烟测试重构+新增多租户隔离用例(78/78)
This commit is contained in:
+122
-70
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"note-manager/middleware"
|
||||
"note-manager/model"
|
||||
"note-manager/service"
|
||||
)
|
||||
@@ -53,15 +54,16 @@ func fail(c *gin.Context, status int, msg string) {
|
||||
c.JSON(status, Response{Code: -1, Message: msg})
|
||||
}
|
||||
|
||||
// CreateNote 创建笔记
|
||||
// CreateNote 创建笔记(归属当前登录用户)
|
||||
func (h *NoteHandler) CreateNote(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
var req model.NoteCreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
fail(c, http.StatusBadRequest, "请求参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.svc.CreateNote(req)
|
||||
note, err := h.svc.CreateNote(userID, req)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -70,9 +72,8 @@ func (h *NoteHandler) CreateNote(c *gin.Context) {
|
||||
success(c, note)
|
||||
}
|
||||
|
||||
// GetNote 获取笔记详情(公开只读接口)
|
||||
// 安全策略:仅允许返回「公开且无密码」的笔记。有密码或未公开的笔记一律返回需授权提示,
|
||||
// 防止通过该接口绕过密码保护读取内容。
|
||||
// GetNote 获取笔记详情(公开只读接口,游客只看公开笔记)
|
||||
// 安全策略:游客仅允许访问「公开且无密码」的笔记;登录用户访问自己的任意笔记。
|
||||
func (h *NoteHandler) GetNote(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -80,22 +81,27 @@ func (h *NoteHandler) GetNote(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.svc.GetNote(uint(id))
|
||||
if middleware.IsLoggedIn(c) {
|
||||
userID := middleware.GetUserID(c)
|
||||
note, err := h.svc.GetNote(userID, uint(id))
|
||||
if err != nil {
|
||||
fail(c, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
success(c, note)
|
||||
return
|
||||
}
|
||||
|
||||
// 游客:仅公开且无密码的笔记
|
||||
note, err := h.svc.GetNotePublic(uint(id))
|
||||
if err != nil {
|
||||
fail(c, http.StatusNotFound, err.Error())
|
||||
fail(c, http.StatusForbidden, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 公开接口白名单:仅公开且无密码的笔记返回完整内容
|
||||
if !note.IsPublic || note.Password != "" {
|
||||
fail(c, http.StatusForbidden, "该笔记受保护,无法直接访问")
|
||||
return
|
||||
}
|
||||
|
||||
success(c, note)
|
||||
}
|
||||
|
||||
// GetAdminNote 获取笔记详情(管理后台用,返回完整内容)
|
||||
// GetAdminNote 获取笔记详情(个人管理用,返回完整内容,校验归属)
|
||||
func (h *NoteHandler) GetAdminNote(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
@@ -103,7 +109,8 @@ func (h *NoteHandler) GetAdminNote(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.svc.GetNote(uint(id))
|
||||
userID := middleware.GetUserID(c)
|
||||
note, err := h.svc.GetNote(userID, uint(id))
|
||||
if err != nil {
|
||||
fail(c, http.StatusNotFound, err.Error())
|
||||
return
|
||||
@@ -146,8 +153,9 @@ func (h *NoteHandler) AccessNote(c *gin.Context) {
|
||||
success(c, note)
|
||||
}
|
||||
|
||||
// UpdateNote 更新笔记
|
||||
// UpdateNote 更新笔记(校验归属)
|
||||
func (h *NoteHandler) UpdateNote(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
@@ -160,8 +168,12 @@ func (h *NoteHandler) UpdateNote(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.svc.UpdateNote(uint(id), req)
|
||||
note, err := h.svc.UpdateNote(userID, uint(id), req)
|
||||
if err != nil {
|
||||
if err == service.ErrNotFound {
|
||||
fail(c, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -169,15 +181,20 @@ func (h *NoteHandler) UpdateNote(c *gin.Context) {
|
||||
success(c, note)
|
||||
}
|
||||
|
||||
// DeleteNote 删除笔记(软删除,进入回收站)
|
||||
// DeleteNote 删除笔记(软删除,进入回收站,校验归属)
|
||||
func (h *NoteHandler) DeleteNote(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.DeleteNote(uint(id)); err != nil {
|
||||
if err := h.svc.DeleteNote(userID, uint(id)); err != nil {
|
||||
if err == service.ErrNotFound {
|
||||
fail(c, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -185,7 +202,7 @@ func (h *NoteHandler) DeleteNote(c *gin.Context) {
|
||||
success(c, nil)
|
||||
}
|
||||
|
||||
// ListNotes 获取笔记列表
|
||||
// ListNotes 获取笔记列表(登录用户看自己的;游客看公开笔记)
|
||||
func (h *NoteHandler) ListNotes(c *gin.Context) {
|
||||
var pinned *bool
|
||||
if v := c.Query("pinned"); v != "" {
|
||||
@@ -198,7 +215,12 @@ func (h *NoteHandler) ListNotes(c *gin.Context) {
|
||||
favorite = &b
|
||||
}
|
||||
|
||||
loggedIn := middleware.IsLoggedIn(c)
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
items, total, totalPages, err := h.svc.ListNotes(
|
||||
userID,
|
||||
loggedIn,
|
||||
c.DefaultQuery("page", "1"),
|
||||
c.DefaultQuery("page_size", ""),
|
||||
c.Query("category"),
|
||||
@@ -225,10 +247,14 @@ func (h *NoteHandler) ListNotes(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// SearchNotes 搜索笔记
|
||||
// SearchNotes 搜索笔记(登录用户搜自己的;游客搜公开笔记)
|
||||
func (h *NoteHandler) SearchNotes(c *gin.Context) {
|
||||
keyword := c.Query("q")
|
||||
loggedIn := middleware.IsLoggedIn(c)
|
||||
userID := middleware.GetUserID(c)
|
||||
items, total, totalPages, err := h.svc.SearchNotes(
|
||||
userID,
|
||||
loggedIn,
|
||||
keyword,
|
||||
c.DefaultQuery("page", "1"),
|
||||
c.DefaultQuery("page_size", ""),
|
||||
@@ -252,9 +278,11 @@ func (h *NoteHandler) SearchNotes(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetCategories 获取分类列表
|
||||
// GetCategories 获取分类列表(登录用户看自己的;游客看公开笔记分类)
|
||||
func (h *NoteHandler) GetCategories(c *gin.Context) {
|
||||
categories, err := h.svc.GetCategories()
|
||||
loggedIn := middleware.IsLoggedIn(c)
|
||||
userID := middleware.GetUserID(c)
|
||||
categories, err := h.svc.GetCategories(userID, loggedIn)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -262,9 +290,11 @@ func (h *NoteHandler) GetCategories(c *gin.Context) {
|
||||
success(c, categories)
|
||||
}
|
||||
|
||||
// GetTags 获取所有标签
|
||||
// GetTags 获取所有标签(登录用户看自己的;游客看公开笔记标签)
|
||||
func (h *NoteHandler) GetTags(c *gin.Context) {
|
||||
tags, err := h.svc.GetTags()
|
||||
loggedIn := middleware.IsLoggedIn(c)
|
||||
userID := middleware.GetUserID(c)
|
||||
tags, err := h.svc.GetTags(userID, loggedIn)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -272,9 +302,10 @@ func (h *NoteHandler) GetTags(c *gin.Context) {
|
||||
success(c, tags)
|
||||
}
|
||||
|
||||
// GetTree 获取树形结构(管理后台用)
|
||||
// GetTree 获取树形结构(个人管理用)
|
||||
func (h *NoteHandler) GetTree(c *gin.Context) {
|
||||
tree, err := h.svc.GetAllTree()
|
||||
userID := middleware.GetUserID(c)
|
||||
tree, err := h.svc.GetAllTree(userID)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -282,9 +313,11 @@ func (h *NoteHandler) GetTree(c *gin.Context) {
|
||||
success(c, tree)
|
||||
}
|
||||
|
||||
// GetPublicTree 获取公开树形结构(前台用)
|
||||
// GetPublicTree 获取树形结构(登录用户看自己的全部;游客看所有公开笔记)
|
||||
func (h *NoteHandler) GetPublicTree(c *gin.Context) {
|
||||
tree, err := h.svc.GetPublicTree()
|
||||
loggedIn := middleware.IsLoggedIn(c)
|
||||
userID := middleware.GetUserID(c)
|
||||
tree, err := h.svc.GetPublicTree(userID, loggedIn)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -302,9 +335,10 @@ func parseIntDefault(s string, defaultVal int) int {
|
||||
|
||||
// ─────────────── 回收站 ───────────────
|
||||
|
||||
// ListTrash 回收站列表
|
||||
// ListTrash 回收站列表(当前用户)
|
||||
func (h *NoteHandler) ListTrash(c *gin.Context) {
|
||||
items, err := h.svc.ListTrash()
|
||||
userID := middleware.GetUserID(c)
|
||||
items, err := h.svc.ListTrash(userID)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -312,37 +346,40 @@ func (h *NoteHandler) ListTrash(c *gin.Context) {
|
||||
success(c, items)
|
||||
}
|
||||
|
||||
// RestoreNote 恢复笔记
|
||||
// RestoreNote 恢复笔记(校验归属)
|
||||
func (h *NoteHandler) RestoreNote(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
if err := h.svc.RestoreNote(uint(id)); err != nil {
|
||||
if err := h.svc.RestoreNote(userID, uint(id)); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
success(c, nil)
|
||||
}
|
||||
|
||||
// PurgeNote 彻底删除笔记
|
||||
// PurgeNote 彻底删除笔记(校验归属)
|
||||
func (h *NoteHandler) PurgeNote(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
if err := h.svc.PurgeNote(uint(id)); err != nil {
|
||||
if err := h.svc.PurgeNote(userID, uint(id)); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
success(c, nil)
|
||||
}
|
||||
|
||||
// EmptyTrash 清空回收站
|
||||
// EmptyTrash 清空回收站(当前用户)
|
||||
func (h *NoteHandler) EmptyTrash(c *gin.Context) {
|
||||
if err := h.svc.EmptyTrash(); err != nil {
|
||||
userID := middleware.GetUserID(c)
|
||||
if err := h.svc.EmptyTrash(userID); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -351,14 +388,15 @@ func (h *NoteHandler) EmptyTrash(c *gin.Context) {
|
||||
|
||||
// ─────────────── 版本历史 ───────────────
|
||||
|
||||
// ListVersions 版本列表
|
||||
// ListVersions 版本列表(校验归属)
|
||||
func (h *NoteHandler) ListVersions(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
versions, err := h.svc.ListVersions(uint(id))
|
||||
versions, err := h.svc.ListVersions(userID, uint(id))
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -366,8 +404,9 @@ func (h *NoteHandler) ListVersions(c *gin.Context) {
|
||||
success(c, versions)
|
||||
}
|
||||
|
||||
// RestoreVersion 恢复到指定版本
|
||||
// RestoreVersion 恢复到指定版本(校验归属)
|
||||
func (h *NoteHandler) RestoreVersion(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
@@ -378,7 +417,7 @@ func (h *NoteHandler) RestoreVersion(c *gin.Context) {
|
||||
fail(c, http.StatusBadRequest, "无效的版本 ID")
|
||||
return
|
||||
}
|
||||
note, err := h.svc.RestoreVersion(uint(id), uint(versionID))
|
||||
note, err := h.svc.RestoreVersion(userID, uint(id), uint(versionID))
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -388,35 +427,37 @@ func (h *NoteHandler) RestoreVersion(c *gin.Context) {
|
||||
|
||||
// ─────────────── 分享 ───────────────
|
||||
|
||||
// CreateShare 创建分享
|
||||
// CreateShare 创建分享(校验归属)
|
||||
func (h *NoteHandler) CreateShare(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
expireHours, _ := strconv.Atoi(c.DefaultPostForm("expire_hours", "0"))
|
||||
note, err := h.svc.CreateShare(uint(id), expireHours)
|
||||
note, err := h.svc.CreateShare(userID, uint(id), expireHours)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
success(c, gin.H{
|
||||
"id": note.ID,
|
||||
"id": note.ID,
|
||||
"share_token": note.ShareToken,
|
||||
"url": "/share/" + note.ShareToken,
|
||||
"expire_at": note.ShareExpireAt,
|
||||
"url": "/share/" + note.ShareToken,
|
||||
"expire_at": note.ShareExpireAt,
|
||||
})
|
||||
}
|
||||
|
||||
// RevokeShare 撤销分享
|
||||
// RevokeShare 撤销分享(校验归属)
|
||||
func (h *NoteHandler) RevokeShare(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
if err := h.svc.RevokeShare(uint(id)); err != nil {
|
||||
if err := h.svc.RevokeShare(userID, uint(id)); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -459,15 +500,16 @@ func (h *NoteHandler) SharePage(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// ExportNote 导出笔记为 Markdown 文件
|
||||
// ExportNote 导出笔记为 Markdown 文件(校验归属)
|
||||
func (h *NoteHandler) ExportNote(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.svc.GetNote(uint(id))
|
||||
note, err := h.svc.GetNote(userID, uint(id))
|
||||
if err != nil {
|
||||
fail(c, http.StatusNotFound, err.Error())
|
||||
return
|
||||
@@ -482,9 +524,10 @@ func (h *NoteHandler) ExportNote(c *gin.Context) {
|
||||
c.String(http.StatusOK, content)
|
||||
}
|
||||
|
||||
// ExportAll 批量导出全部笔记为 zip
|
||||
// ExportAll 批量导出当前用户全部笔记为 zip
|
||||
func (h *NoteHandler) ExportAll(c *gin.Context) {
|
||||
tree, err := h.svc.GetAllTree()
|
||||
userID := middleware.GetUserID(c)
|
||||
tree, err := h.svc.GetAllTree(userID)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -527,7 +570,7 @@ func (h *NoteHandler) ExportAll(c *gin.Context) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
note, err := h.svc.GetNote(n.item.ID)
|
||||
note, err := h.svc.GetNote(userID, n.item.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -549,8 +592,9 @@ func (h *NoteHandler) ExportAll(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// ImportNotes 导入 Markdown 文件
|
||||
// ImportNotes 导入 Markdown 文件(归属当前用户)
|
||||
func (h *NoteHandler) ImportNotes(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "请选择要导入的文件")
|
||||
@@ -587,7 +631,7 @@ func (h *NoteHandler) ImportNotes(c *gin.Context) {
|
||||
Content: body,
|
||||
}
|
||||
|
||||
note, err := h.svc.CreateNote(req)
|
||||
note, err := h.svc.CreateNote(userID, req)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -664,8 +708,9 @@ func sanitizeFileName(name string) string {
|
||||
|
||||
// ─────────────── 自动保存草稿 ───────────────
|
||||
|
||||
// SaveDraft 保存笔记草稿(自动保存)
|
||||
// SaveDraft 保存笔记草稿(自动保存,校验归属)
|
||||
func (h *NoteHandler) SaveDraft(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
@@ -678,21 +723,22 @@ func (h *NoteHandler) SaveDraft(c *gin.Context) {
|
||||
fail(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
if err := h.svc.SaveDraft(uint(id), req.Content); err != nil {
|
||||
if err := h.svc.SaveDraft(userID, uint(id), req.Content); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
success(c, nil)
|
||||
}
|
||||
|
||||
// DiscardDraft 清除指定笔记的草稿
|
||||
// DiscardDraft 清除指定笔记的草稿(校验归属)
|
||||
func (h *NoteHandler) DiscardDraft(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
if err := h.svc.ClearDraft(uint(id)); err != nil {
|
||||
if err := h.svc.ClearDraft(userID, uint(id)); err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -701,14 +747,15 @@ func (h *NoteHandler) DiscardDraft(c *gin.Context) {
|
||||
|
||||
// ─────────────── 双向链接 / 知识图谱 ───────────────
|
||||
|
||||
// GetBacklinks 获取指定笔记的反向链接列表
|
||||
// GetBacklinks 获取指定笔记的反向链接列表(校验归属)
|
||||
func (h *NoteHandler) GetBacklinks(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, "无效的笔记 ID")
|
||||
return
|
||||
}
|
||||
links, err := h.svc.GetBacklinks(uint(id), "")
|
||||
links, err := h.svc.GetBacklinks(userID, uint(id), "")
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -716,9 +763,10 @@ func (h *NoteHandler) GetBacklinks(c *gin.Context) {
|
||||
success(c, links)
|
||||
}
|
||||
|
||||
// GetKnowledgeGraph 获取知识图谱数据(节点 + 边)
|
||||
// GetKnowledgeGraph 获取当前用户知识图谱数据(节点 + 边)
|
||||
func (h *NoteHandler) GetKnowledgeGraph(c *gin.Context) {
|
||||
graph, err := h.svc.GetKnowledgeGraph()
|
||||
userID := middleware.GetUserID(c)
|
||||
graph, err := h.svc.GetKnowledgeGraph(userID)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -728,9 +776,10 @@ func (h *NoteHandler) GetKnowledgeGraph(c *gin.Context) {
|
||||
|
||||
// ─────────────── 标签管理 ───────────────
|
||||
|
||||
// GetTagUsage 获取标签及使用次数
|
||||
// GetTagUsage 获取当前用户标签及使用次数
|
||||
func (h *NoteHandler) GetTagUsage(c *gin.Context) {
|
||||
usage, err := h.svc.GetTagUsage()
|
||||
userID := middleware.GetUserID(c)
|
||||
usage, err := h.svc.GetTagUsage(userID)
|
||||
if err != nil {
|
||||
fail(c, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -740,6 +789,7 @@ func (h *NoteHandler) GetTagUsage(c *gin.Context) {
|
||||
|
||||
// RenameTag 重命名标签
|
||||
func (h *NoteHandler) RenameTag(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
var req struct {
|
||||
OldName string `json:"old_name" binding:"required"`
|
||||
NewName string `json:"new_name" binding:"required"`
|
||||
@@ -748,7 +798,7 @@ func (h *NoteHandler) RenameTag(c *gin.Context) {
|
||||
fail(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
changed, err := h.svc.RenameTag(req.OldName, req.NewName)
|
||||
changed, err := h.svc.RenameTag(userID, req.OldName, req.NewName)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
@@ -758,6 +808,7 @@ func (h *NoteHandler) RenameTag(c *gin.Context) {
|
||||
|
||||
// MergeTag 合并标签(from → to)
|
||||
func (h *NoteHandler) MergeTag(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
var req struct {
|
||||
From string `json:"from" binding:"required"`
|
||||
To string `json:"to" binding:"required"`
|
||||
@@ -766,7 +817,7 @@ func (h *NoteHandler) MergeTag(c *gin.Context) {
|
||||
fail(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
changed, err := h.svc.MergeTag(req.From, req.To)
|
||||
changed, err := h.svc.MergeTag(userID, req.From, req.To)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
@@ -774,8 +825,9 @@ func (h *NoteHandler) MergeTag(c *gin.Context) {
|
||||
success(c, gin.H{"changed": changed})
|
||||
}
|
||||
|
||||
// DeleteTag 删除标签(从所有笔记中移除)
|
||||
// DeleteTag 删除标签(从当前用户所有笔记中移除)
|
||||
func (h *NoteHandler) DeleteTag(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
var req struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
}
|
||||
@@ -783,7 +835,7 @@ func (h *NoteHandler) DeleteTag(c *gin.Context) {
|
||||
fail(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
changed, err := h.svc.DeleteTag(req.Name)
|
||||
changed, err := h.svc.DeleteTag(userID, req.Name)
|
||||
if err != nil {
|
||||
fail(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user