feat(api): 初始化项目基础结构与API定义

新增 `.gitignore` 文件,忽略编译输出、生成代码及数据库文件。
新增 `README.md`,包含 Gin 框架和 Swagger 工具的安装与使用说明。
新增 `config/api.yaml`,定义 bookmarks 相关的文件夹与书签操作的 OpenAPI 3.0 接口规范。
新增 `config/cfg.yaml`,配置 oapi-codegen 工具生成 Gin 服务和模型代码。
新增 `go.mod` 和 `go.sum` 文件,初始化 Go 模块并引入 Gin、GORM、SQLite 及 oapi-codegen 等依赖。
```
This commit is contained in:
zzy
2025-09-21 00:20:29 +08:00
commit 7ff8591be8
11 changed files with 2033 additions and 0 deletions

View File

@ -0,0 +1,449 @@
// internal/handlers/note_link.go
package handlers
import (
"net/http"
"git.zzyxyz.com/zzy/zzyxyz_go_api/gen/api"
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type BookMarksImpl struct {
db *gorm.DB
}
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.First(&rootFolder, 1)
if result.Error != nil {
// 根文件夹不存在,创建它
rootFolder = models.Folder{
ID: 1,
Name: "Root",
ParentPathID: 1, // 根目录指向自己
}
db.Create(&rootFolder)
}
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
}
// 设置默认父文件夹ID为根目录(1)
parentID := int64(1)
if req.ParentPathId != nil && *req.ParentPathId != 0 {
parentID = *req.ParentPathId
}
// 检查父文件夹是否存在
var parentFolder models.Folder
if err := db.First(&parentFolder, parentID).Error; err != nil {
c.JSON(http.StatusNotFound, api.Error{
Errtype: "NotFoundError",
Message: "Parent folder not found",
})
return
}
// 创建书签
bookmark := models.Bookmark{
Name: req.Name,
Detail: *req.Detail,
Description: *req.Description,
ParentPathID: parentID,
}
if err := db.Create(&bookmark).Error; err != nil {
c.JSON(http.StatusInternalServerError, api.Error{
Errtype: "DatabaseError",
Message: "Failed to create bookmark",
})
return
}
// 构造响应
response := api.BookmarkResponse{
Id: bookmark.ID,
Name: bookmark.Name,
Detail: &bookmark.Detail,
Description: &bookmark.Description,
ParentPathId: bookmark.ParentPathID,
CreatedAt: bookmark.CreatedAt,
UpdatedAt: bookmark.UpdatedAt,
}
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
}
// 设置默认父文件夹ID为根目录(1)
parentID := int64(1)
if req.ParentPathId != nil && *req.ParentPathId != 0 {
parentID = *req.ParentPathId
}
// 检查父文件夹是否存在
var parentFolder models.Folder
if err := db.First(&parentFolder, parentID).Error; err != nil {
c.JSON(http.StatusNotFound, api.Error{
Errtype: "NotFoundError",
Message: "Parent folder not found",
})
return
}
// 创建文件夹
folder := models.Folder{
Name: req.Name,
ParentPathID: parentID,
}
if err := db.Create(&folder).Error; err != nil {
c.JSON(http.StatusInternalServerError, api.Error{
Errtype: "DatabaseError",
Message: "Failed to create folder",
})
return
}
// 构造响应
response := api.FolderResponse{
Id: folder.ID,
Name: folder.Name,
ParentPathId: folder.ParentPathID,
CreatedAt: folder.CreatedAt,
UpdatedAt: folder.UpdatedAt,
}
c.JSON(http.StatusCreated, response)
}
// DeleteBookmark implements api.ServerInterface.
func (b *BookMarksImpl) DeleteBookmark(c *gin.Context, id int64) {
panic("unimplemented")
}
// DeleteFolder implements api.ServerInterface.
func (b *BookMarksImpl) DeleteFolder(c *gin.Context, id int64) {
panic("unimplemented")
}
// DeleteFolderContent implements api.ServerInterface.
func (b *BookMarksImpl) DeleteFolderContent(c *gin.Context, id int64, params api.DeleteFolderContentParams) {
panic("unimplemented")
}
// 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 := api.BookmarkResponse{
Id: bookmark.ID,
Name: bookmark.Name,
Link: &bookmark.Link,
Detail: &bookmark.Detail,
Description: &bookmark.Description,
ParentPathId: bookmark.ParentPathID,
CreatedAt: bookmark.CreatedAt,
UpdatedAt: bookmark.UpdatedAt,
}
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,
ParentPathId: folder.ParentPathID,
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.ParentPathId != nil && *req.ParentPathId != 0 {
var parentFolder models.Folder
if err := db.First(&parentFolder, *req.ParentPathId).Error; err != nil {
c.JSON(http.StatusNotFound, api.Error{
Errtype: "NotFoundError",
Message: "Parent folder not found",
})
return
}
bookmark.ParentPathID = *req.ParentPathId
}
// 保存更新
if err := db.Save(&bookmark).Error; err != nil {
c.JSON(http.StatusInternalServerError, api.Error{
Errtype: "DatabaseError",
Message: "Failed to update bookmark",
})
return
}
// 构造响应
response := api.BookmarkResponse{
Id: bookmark.ID,
Name: bookmark.Name,
Link: &bookmark.Link,
Detail: &bookmark.Detail,
Description: &bookmark.Description,
ParentPathId: bookmark.ParentPathID,
CreatedAt: bookmark.CreatedAt,
UpdatedAt: bookmark.UpdatedAt,
}
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.ParentPathId != nil && *req.ParentPathId != 0 {
// 不能将文件夹设置为自己的子文件夹
if *req.ParentPathId == 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.ParentPathId).Error; err != nil {
c.JSON(http.StatusNotFound, api.Error{
Errtype: "NotFoundError",
Message: "Parent folder not found",
})
return
}
folder.ParentPathID = *req.ParentPathId
}
// 保存更新
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 := api.FolderResponse{
Id: folder.ID,
Name: folder.Name,
ParentPathId: folder.ParentPathID,
CreatedAt: folder.CreatedAt,
UpdatedAt: folder.UpdatedAt,
SubFolderCount: int(subFolderCount),
BookmarkCount: int(bookmarkCount),
}
c.JSON(http.StatusOK, response)
}
// Make sure we conform to ServerInterface
var _ api.ServerInterface = (*BookMarksImpl)(nil)