// internal/handlers/note_link.go package handlers import ( "crypto/rand" "encoding/hex" "fmt" "log" "net/http" "net/url" api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models" "github.com/casbin/casbin/v2" "github.com/gin-gonic/gin" _ "github.com/mattn/go-sqlite3" "gorm.io/driver/sqlite" "gorm.io/gorm" ) type BookMarksImpl struct { db *gorm.DB } // GetFolderMounts implements api.ServerInterface. func (b *BookMarksImpl) GetFolderMounts(c *gin.Context, id int64) { panic("unimplemented") } // MountFolder implements api.ServerInterface. func (b *BookMarksImpl) MountFolder(c *gin.Context, id int64) { panic("unimplemented") } // UnmountFolder implements api.ServerInterface. func (b *BookMarksImpl) UnmountFolder(c *gin.Context, id int64) { panic("unimplemented") } const forlder_root_id = 1 var enforcer *casbin.Enforcer var adminToken *string func validateApiKey(url *url.URL, apiKey string) bool { if adminToken != nil && apiKey == *adminToken { return true } return false } func AuthMiddleware() api.MiddlewareFunc { return func(c *gin.Context) { // 检查当前请求是否需要认证 if _, exists := c.Get(api.ApiKeyAuthScopes); exists { // 提取 API Key apiKey := c.GetHeader("X-BookMark-Token") // 验证 API Key(您需要实现这个逻辑) if apiKey == "" || !validateApiKey(c.Request.URL, apiKey) { c.JSON(http.StatusUnauthorized, api.Error{ Errtype: "Unauthorized", Message: "Invalid or missing API key", }) c.Abort() return } } c.Next() } } func NewBookMarkPermission() (*api.GinServerOptions, error) { // 一行代码生成安全的随机token token := make([]byte, 16) rand.Read(token) // 忽略错误处理以简化代码(生产环境建议处理) tokenStr := hex.EncodeToString(token) adminToken = &tokenStr log.Printf("Admin API Token (for Swagger testing): %s", *adminToken) if e, err := casbin.NewEnforcer("./config/model.conf", ".data/policy.csv"); err == nil { log.Fatalf("Failed to create casbin enforcer: %v", err) } else { enforcer = e } return &api.GinServerOptions{ Middlewares: []api.MiddlewareFunc{AuthMiddleware()}, }, nil } func (b *BookMarksImpl) GetFolderDefaultRoot(folderID *int64) (*models.Folder, error) { var db *gorm.DB = b.db var real_root_id int64 = forlder_root_id // 设置默认父文件夹ID为根目录 parentID := real_root_id if folderID != nil && *folderID != 0 { parentID = *folderID } // 检查文件夹是否存在(Find 不会在找不到记录时返回错误) var parentFolder models.Folder result := db.Limit(1).Find(&parentFolder, parentID) if result.Error != nil { return nil, result.Error } // 检查是否找到了记录 if result.RowsAffected == 0 { return nil, nil } return &parentFolder, nil } func bookmarkReq2Model(req api.BookmarkRequest, parentID int64) models.Bookmark { return models.Bookmark{ Name: req.Name, Link: req.Link, Detail: req.Detail, Description: req.Description, ParentID: parentID, } } func bookmarkModel2Res(bookmark models.Bookmark) api.BookmarkResponse { return api.BookmarkResponse{ Id: bookmark.ID, Name: bookmark.Name, Link: bookmark.Link, Detail: bookmark.Detail, Description: bookmark.Description, ParentId: bookmark.ParentID, CreatedAt: bookmark.CreatedAt, } } func folderReq2Model(req api.FolderRequest, parentID *int64) models.Folder { return models.Folder{ Name: req.Name, ParentID: parentID, } } func folderModel2Res(folder models.Folder) api.FolderResponse { return api.FolderResponse{ Id: folder.ID, Name: folder.Name, ParentId: folder.ParentID, CreatedAt: folder.CreatedAt, UpdatedAt: folder.UpdatedAt, } } func NewBookMarks(dbPath string) (*BookMarksImpl, error) { var err error var db *gorm.DB db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { return nil, err } // 自动迁移表结构 err = db.AutoMigrate(&models.Folder{}, &models.Bookmark{}) if err != nil { return nil, err } // 创建根文件夹(如果不存在) var rootFolder models.Folder result := db.Limit(1).Find(&rootFolder, forlder_root_id) if result.Error != nil { return nil, result.Error } if result.RowsAffected == 0 { // 根文件夹不存在,创建它 rootFolder = models.Folder{ ID: forlder_root_id, Name: "Root", ParentID: nil, // 根目录指向NULL } if err := db.Create(&rootFolder).Error; err != nil { return nil, fmt.Errorf("failed to create root folder: %w", err) } } return &BookMarksImpl{db: db}, nil } // CreateBookmark implements api.ServerInterface. func (b *BookMarksImpl) CreateBookmark(c *gin.Context) { var db = b.db var req api.BookmarkRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Invalid request parameters", }) return } var parentID int64 if folder, err := b.GetFolderDefaultRoot(req.ParentId); err != nil || folder == nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Parent folder not found", }) return } else { parentID = folder.ID } bookmark := bookmarkReq2Model(req, parentID) if err := db.Create(&bookmark).Error; err != nil { c.JSON(http.StatusInternalServerError, api.Error{ Errtype: "DatabaseError", Message: "Failed to create bookmark", }) return } response := bookmarkModel2Res(bookmark) c.JSON(http.StatusCreated, response) } // CreateFolder implements api.ServerInterface. func (b *BookMarksImpl) CreateFolder(c *gin.Context) { var db = b.db var req api.FolderRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Invalid request parameters", }) return } var parentID int64 if folder, err := b.GetFolderDefaultRoot(req.ParentId); err != nil || folder == nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Parent folder not found", }) return } else { parentID = folder.ID } // 创建文件夹 folder := folderReq2Model(req, &parentID) if err := db.Create(&folder).Error; err != nil { c.JSON(http.StatusInternalServerError, api.Error{ Errtype: "DatabaseError", Message: "Failed to create folder", }) return } response := folderModel2Res(folder) c.JSON(http.StatusCreated, response) } // DeleteBookmark implements api.ServerInterface. func (b *BookMarksImpl) DeleteBookmark(c *gin.Context, id int64) { var db = b.db var bookmark models.Bookmark // 查询书签是否存在 if err := db.First(&bookmark, id).Error; err != nil { // FIXME maybe use 204 means already deleted status is same as delete c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Bookmark not found", }) return } // 删除书签 if err := db.Delete(&bookmark).Error; err != nil { c.JSON(http.StatusInternalServerError, api.Error{ Errtype: "DatabaseError", Message: "Failed to delete bookmark", }) return } c.Status(http.StatusNoContent) } // DeleteFolder implements api.ServerInterface. func (b *BookMarksImpl) DeleteFolder(c *gin.Context, id int64) { var db = b.db var folder models.Folder // 查询文件夹是否存在 if err := db.First(&folder, id).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Folder not found", }) return } if folder.ParentID == nil { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Cannot delete root folder", }) return } // 检查文件夹是否为空 var subFolderCount int64 db.Model(&models.Folder{}).Where("parent_path_id = ?", id).Count(&subFolderCount) var bookmarkCount int64 db.Model(&models.Bookmark{}).Where("parent_path_id = ?", id).Count(&bookmarkCount) // 如果文件夹不为空,拒绝删除 if subFolderCount > 0 || bookmarkCount > 0 { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Cannot delete non-empty folder, please delete contents first", }) return } // 删除空文件夹 if err := db.Delete(&folder).Error; err != nil { c.JSON(http.StatusInternalServerError, api.Error{ Errtype: "DatabaseError", Message: "Failed to delete folder", }) return } c.Status(http.StatusNoContent) } // DeleteFolderContent implements api.ServerInterface. func (b *BookMarksImpl) DeleteFolderContent(c *gin.Context, id int64, params api.DeleteFolderContentParams) { c.JSON(http.StatusNotImplemented, api.Error{ Errtype: "error", Message: "Not implemented", }) } // GetBookmark implements api.ServerInterface. func (b *BookMarksImpl) GetBookmark(c *gin.Context, id int64) { var db = b.db var bookmark models.Bookmark // 查询书签 if err := db.First(&bookmark, id).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Bookmark not found", }) return } // 构造响应 response := bookmarkModel2Res(bookmark) c.JSON(http.StatusOK, response) } // GetFolderContent implements api.ServerInterface. func (b *BookMarksImpl) GetFolderContent(c *gin.Context, id int64) { var db = b.db // 检查文件夹是否存在 var folder models.Folder if err := db.First(&folder, id).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Folder not found", }) return } // 查询子文件夹 var subFolders []models.Folder db.Where("parent_path_id = ?", id).Find(&subFolders) // 查询书签 var bookmarks []models.Bookmark db.Where("parent_path_id = ?", id).Find(&bookmarks) // 构造子文件夹响应列表 var subFolderResponses []api.FolderBriefResponse for _, f := range subFolders { subFolderResponses = append(subFolderResponses, api.FolderBriefResponse{ Id: f.ID, Name: f.Name, }) } // 构造书签响应列表 var bookmarkResponses []api.BookmarkBriefResponse for _, bm := range bookmarks { bookmarkResponses = append(bookmarkResponses, api.BookmarkBriefResponse{ Id: bm.ID, Name: bm.Name, }) } // 构造响应 response := api.FolderContentResponse{ SubFolders: subFolderResponses, Bookmarks: bookmarkResponses, } c.JSON(http.StatusOK, response) } // GetFolderInfo implements api.ServerInterface. func (b *BookMarksImpl) GetFolderInfo(c *gin.Context, id int64) { var db = b.db var folder models.Folder // 查询文件夹 if err := db.First(&folder, id).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Folder not found", }) return } // 统计子文件夹数量 var subFolderCount int64 db.Model(&models.Folder{}).Where("parent_path_id = ?", id).Count(&subFolderCount) // 统计书签数量 var bookmarkCount int64 db.Model(&models.Bookmark{}).Where("parent_path_id = ?", id).Count(&bookmarkCount) // 构造响应 response := api.FolderResponse{ Id: folder.ID, Name: folder.Name, ParentId: folder.ParentID, CreatedAt: folder.CreatedAt, UpdatedAt: folder.UpdatedAt, SubFolderCount: int(subFolderCount), BookmarkCount: int(bookmarkCount), } c.JSON(http.StatusOK, response) } // UpdateBookmark implements api.ServerInterface. func (b *BookMarksImpl) UpdateBookmark(c *gin.Context, id int64) { var db = b.db var req api.BookmarkRequest // 绑定请求参数 if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Invalid request parameters", }) return } // 查找要更新的书签 var bookmark models.Bookmark if err := db.First(&bookmark, id).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Bookmark not found", }) return } // 更新书签字段 if req.Name != "" { bookmark.Name = req.Name } if req.Link != nil { bookmark.Link = req.Link } if req.Detail != nil { bookmark.Detail = req.Detail } if req.Description != nil { bookmark.Description = req.Description } // 更新父文件夹ID(如果提供且有效) if req.ParentId != nil && *req.ParentId != 0 { var parentFolder models.Folder if err := db.First(&parentFolder, *req.ParentId).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Parent folder not found", }) return } bookmark.ParentID = *req.ParentId } // 保存更新 if err := db.Save(&bookmark).Error; err != nil { c.JSON(http.StatusInternalServerError, api.Error{ Errtype: "DatabaseError", Message: "Failed to update bookmark", }) return } // 构造响应 response := bookmarkModel2Res(bookmark) c.JSON(http.StatusOK, response) } // UpdateFolder implements api.ServerInterface. func (b *BookMarksImpl) UpdateFolder(c *gin.Context, id int64) { var db = b.db var req api.FolderRequest // 绑定请求参数 if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Invalid request parameters", }) return } // 查找要更新的文件夹 var folder models.Folder if err := db.First(&folder, id).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Folder not found", }) return } // 更新文件夹名称(如果提供) if req.Name != "" { folder.Name = req.Name } // 更新父文件夹ID(如果提供且有效) if req.ParentId != nil && *req.ParentId != 0 { // 不能将文件夹设置为自己的子文件夹 if *req.ParentId == id { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Cannot set folder as its own parent", }) return } var parentFolder models.Folder if err := db.First(&parentFolder, *req.ParentId).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Parent folder not found", }) return } folder.ParentID = req.ParentId } // 保存更新 if err := db.Save(&folder).Error; err != nil { c.JSON(http.StatusInternalServerError, api.Error{ Errtype: "DatabaseError", Message: "Failed to update folder", }) return } // 统计子文件夹数量 var subFolderCount int64 db.Model(&models.Folder{}).Where("parent_path_id = ?", id).Count(&subFolderCount) // 统计书签数量 var bookmarkCount int64 db.Model(&models.Bookmark{}).Where("parent_path_id = ?", id).Count(&bookmarkCount) // 构造响应 response := folderModel2Res(folder) c.JSON(http.StatusOK, response) } // ExportFolderTree implements api.ServerInterface. func (b *BookMarksImpl) ExportFolderTree(c *gin.Context) { panic("unimplemented") } // ImportFolderTree implements api.ServerInterface. func (b *BookMarksImpl) ImportFolderTree(c *gin.Context) { panic("unimplemented") } // Make sure we conform to ServerInterface var _ api.ServerInterface = (*BookMarksImpl)(nil)