feat: 初始化 kenaito-notebook 桌面记事本项目
This commit is contained in:
@@ -0,0 +1,338 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"kenaito-notebook-server/services"
|
||||
)
|
||||
|
||||
func isValidEmail(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type AuthHandler struct {
|
||||
db *services.Database
|
||||
auth *services.AuthService
|
||||
email *services.EmailService
|
||||
verify *services.VerificationService
|
||||
}
|
||||
|
||||
func NewAuthHandler(db *services.Database, auth *services.AuthService, email *services.EmailService, verify *services.VerificationService) *AuthHandler {
|
||||
return &AuthHandler{db: db, auth: auth, email: email, verify: verify}
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type SendCodeRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Purpose string `json:"purpose" binding:"required"`
|
||||
}
|
||||
|
||||
type VerifyCodeRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
Purpose string `json:"purpose" binding:"required"`
|
||||
}
|
||||
|
||||
type ResetPasswordRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Code string `json:"code" binding:"required"`
|
||||
NewPassword string `json:"newPassword" binding:"required"`
|
||||
}
|
||||
|
||||
// SendCode 发送验证码
|
||||
func (h *AuthHandler) SendCode(c *gin.Context) {
|
||||
var req SendCodeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] SendCode bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(req.Email) {
|
||||
log.Printf("[Auth] SendCode invalid email: %s", req.Email)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Purpose != "register" && req.Purpose != "reset" {
|
||||
log.Printf("[Auth] SendCode invalid purpose: %s", req.Purpose)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求类型"})
|
||||
return
|
||||
}
|
||||
|
||||
// 注册时检查邮箱是否已注册
|
||||
if req.Purpose == "register" {
|
||||
existing, _ := h.db.GetUserByEmail(req.Email)
|
||||
if existing != nil {
|
||||
log.Printf("[Auth] SendCode email already registered: %s", req.Email)
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 重置密码时检查邮箱是否存在
|
||||
if req.Purpose == "reset" {
|
||||
existing, _ := h.db.GetUserByEmail(req.Email)
|
||||
if existing == nil {
|
||||
log.Printf("[Auth] SendCode email not found for reset: %s", req.Email)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "邮箱未注册"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 限频检查
|
||||
if !h.verify.CheckRateLimit(req.Email, req.Purpose) {
|
||||
log.Printf("[Auth] SendCode rate limit exceeded: %s, purpose: %s", req.Email, req.Purpose)
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{"error": "发送过于频繁,请 60 秒后重试"})
|
||||
return
|
||||
}
|
||||
|
||||
code := h.verify.GenerateCode(req.Email, req.Purpose)
|
||||
if err := h.email.SendVerificationCode(req.Email, code, req.Purpose); err != nil {
|
||||
log.Printf("[Auth] SendCode send email failed: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "发送验证码失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "验证码已发送"})
|
||||
}
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func (h *AuthHandler) VerifyCode(c *gin.Context) {
|
||||
var req VerifyCodeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] VerifyCode bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.verify.VerifyCode(req.Email, req.Code, req.Purpose); err != nil {
|
||||
log.Printf("[Auth] VerifyCode failed: email=%s, purpose=%s, err=%v", req.Email, req.Purpose, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "验证码正确"})
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] Register bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(req.Email) {
|
||||
log.Printf("[Auth] Register invalid email: %s", req.Email)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查邮箱是否已注册
|
||||
existing, _ := h.db.GetUserByEmail(req.Email)
|
||||
if existing != nil {
|
||||
log.Printf("[Auth] Register email already registered: %s", req.Email)
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证并消耗验证码
|
||||
if err := h.verify.UseCode(req.Email, req.Code, "register"); err != nil {
|
||||
log.Printf("[Auth] Register use code failed: email=%s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 哈希密码
|
||||
hashedPassword, err := h.auth.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Register hash password failed: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user, err := h.db.CreateUser(req.Email, hashedPassword)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Register create user failed: email=%s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成 token
|
||||
token, err := h.auth.GenerateToken(user.ID, user.Email)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Register generate token failed: userID=%d, err=%v", user.ID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"token": token,
|
||||
"user": user,
|
||||
})
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] Login bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写邮箱和密码"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(req.Email) {
|
||||
log.Printf("[Auth] Login invalid email: %s", req.Email)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
user, err := h.db.GetUserByEmail(req.Email)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Login user not found: %s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if !h.auth.CheckPassword(req.Password, user.HashedPassword) {
|
||||
log.Printf("[Auth] Login password mismatch: email=%s", req.Email)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "邮箱或密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 生成 token
|
||||
token, err := h.auth.GenerateToken(user.ID, user.Email)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Login generate token failed: userID=%d, err=%v", user.ID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "登录失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"token": token,
|
||||
"user": user,
|
||||
})
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
func (h *AuthHandler) ResetPassword(c *gin.Context) {
|
||||
var req ResetPasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Printf("[Auth] ResetPassword bind error: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写完整信息"})
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidEmail(req.Email) {
|
||||
log.Printf("[Auth] ResetPassword invalid email: %s", req.Email)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "邮箱格式不正确"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证并消耗验证码
|
||||
if err := h.verify.UseCode(req.Email, req.Code, "reset"); err != nil {
|
||||
log.Printf("[Auth] ResetPassword use code failed: email=%s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
user, err := h.db.GetUserByEmail(req.Email)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] ResetPassword user not found: %s, err=%v", req.Email, err)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 哈希新密码
|
||||
hashedPassword, err := h.auth.HashPassword(req.NewPassword)
|
||||
if err != nil {
|
||||
log.Printf("[Auth] ResetPassword hash password failed: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "重置失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
if err := h.db.UpdatePassword(user.ID, hashedPassword); err != nil {
|
||||
log.Printf("[Auth] ResetPassword update password failed: userID=%d, err=%v", user.ID, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "重置失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "密码重置成功"})
|
||||
}
|
||||
|
||||
// GetMe 获取当前用户信息
|
||||
func (h *AuthHandler) GetMe(c *gin.Context) {
|
||||
userID, exists := c.Get("userID")
|
||||
if !exists {
|
||||
log.Printf("[Auth] GetMe userID not found in context")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.db.GetUserByID(userID.(int64))
|
||||
if err != nil {
|
||||
log.Printf("[Auth] GetMe user not found: userID=%v, err=%v", userID, err)
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
// AuthMiddleware JWT 认证中间件
|
||||
func AuthMiddleware(auth *services.AuthService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
log.Printf("[Auth] Middleware missing Authorization header: ip=%s", c.ClientIP())
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 提取 Bearer token
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
log.Printf("[Auth] Middleware invalid auth format: ip=%s", c.ClientIP())
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的认证格式"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
payload, err := auth.ValidateToken(parts[1])
|
||||
if err != nil {
|
||||
log.Printf("[Auth] Middleware token validation failed: ip=%s, err=%v", c.ClientIP(), err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "登录已过期,请重新登录"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息存入上下文
|
||||
c.Set("userID", payload.UserID)
|
||||
c.Set("email", payload.Email)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"kenaito-notebook-server/models"
|
||||
"kenaito-notebook-server/services"
|
||||
)
|
||||
|
||||
type ExportHandler struct {
|
||||
db *services.Database
|
||||
exportSvc *services.ExportService
|
||||
}
|
||||
|
||||
func NewExportHandler(db *services.Database, exportSvc *services.ExportService) *ExportHandler {
|
||||
return &ExportHandler{db: db, exportSvc: exportSvc}
|
||||
}
|
||||
|
||||
func (h *ExportHandler) ExportHTML(c *gin.Context) {
|
||||
var req struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
outputPath, err := h.getTempExportPath(req.Title, ".html")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.exportSvc.ExportHTML(req.Title, req.Content, outputPath); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"filePath": outputPath})
|
||||
}
|
||||
|
||||
func (h *ExportHandler) ExportMarkdown(c *gin.Context) {
|
||||
var req struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
outputPath, err := h.getTempExportPath(req.Title, ".md")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.exportSvc.ExportMarkdown(req.Title, req.Content, outputPath); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"filePath": outputPath})
|
||||
}
|
||||
|
||||
func (h *ExportHandler) ExportFolderZip(c *gin.Context) {
|
||||
userID := c.GetInt64("userID")
|
||||
|
||||
folderID, err := strconv.ParseInt(c.Param("folderId"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid folder id"})
|
||||
return
|
||||
}
|
||||
|
||||
folderTree, err := h.db.GetFolderTree(userID, folderID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
noteInfoTree := convertNoteToNoteInfo(folderTree)
|
||||
|
||||
outputPath, err := h.getTempExportPath(folderTree.Title, ".zip")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.exportSvc.ExportFolderZip(folderTree.Title, noteInfoTree.Children, outputPath); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"filePath": outputPath})
|
||||
}
|
||||
|
||||
func (h *ExportHandler) ExportAllZip(c *gin.Context) {
|
||||
userID := c.GetInt64("userID")
|
||||
|
||||
allNotesTree, err := h.db.GetAllNotesTree(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
noteInfos := make([]*services.NoteInfo, len(allNotesTree))
|
||||
for i, noteTree := range allNotesTree {
|
||||
noteInfos[i] = convertNoteTreeToNoteInfo(noteTree)
|
||||
}
|
||||
|
||||
outputPath, err := h.getTempExportPath("所有笔记", ".zip")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.exportSvc.ExportAllZip(noteInfos, outputPath); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"filePath": outputPath})
|
||||
}
|
||||
|
||||
func (h *ExportHandler) getTempExportPath(title, ext string) (string, error) {
|
||||
tmpDir := filepath.Join(os.TempDir(), "kenaito-notebook-export")
|
||||
if err := os.MkdirAll(tmpDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("failed to create temp export dir: %w", err)
|
||||
}
|
||||
|
||||
safeName := h.exportSvc.GetSafeFilename(title)
|
||||
return filepath.Join(tmpDir, fmt.Sprintf("%s%s", safeName, ext)), nil
|
||||
}
|
||||
|
||||
func convertNoteToNoteInfo(note *models.Note) *services.NoteInfo {
|
||||
if note == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
info := &services.NoteInfo{
|
||||
Title: note.Title,
|
||||
Content: note.Content,
|
||||
IsFolder: note.IsFolder,
|
||||
}
|
||||
|
||||
if note.Children != nil && len(note.Children) > 0 {
|
||||
info.Children = make([]*services.NoteInfo, len(note.Children))
|
||||
for i, child := range note.Children {
|
||||
info.Children[i] = convertNoteToNoteInfo(child)
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func convertNoteTreeToNoteInfo(noteTree *models.NoteTree) *services.NoteInfo {
|
||||
if noteTree == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
info := &services.NoteInfo{
|
||||
Title: noteTree.Title,
|
||||
Content: noteTree.Content,
|
||||
IsFolder: noteTree.IsFolder,
|
||||
}
|
||||
|
||||
if noteTree.Children != nil && len(noteTree.Children) > 0 {
|
||||
info.Children = make([]*services.NoteInfo, len(noteTree.Children))
|
||||
for i, child := range noteTree.Children {
|
||||
info.Children[i] = convertNoteTreeToNoteInfo(child)
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"kenaito-notebook-server/services"
|
||||
)
|
||||
|
||||
type FileHandler struct {
|
||||
fileSvc *services.FileService
|
||||
}
|
||||
|
||||
func NewFileHandler(fileSvc *services.FileService) *FileHandler {
|
||||
return &FileHandler{fileSvc: fileSvc}
|
||||
}
|
||||
|
||||
func (h *FileHandler) SaveImage(c *gin.Context) {
|
||||
var req struct {
|
||||
Data string `json:"data" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
url, err := h.fileSvc.SaveImage(req.Data)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"url": url})
|
||||
}
|
||||
|
||||
func (h *FileHandler) SaveFile(c *gin.Context) {
|
||||
var req struct {
|
||||
Data string `json:"data" binding:"required"`
|
||||
Filename string `json:"filename" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
url, err := h.fileSvc.SaveFile(req.Data, req.Filename)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"url": url})
|
||||
}
|
||||
|
||||
func (h *FileHandler) GetFileServerURL(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"url": h.fileSvc.GetBaseURL()})
|
||||
}
|
||||
|
||||
func (h *FileHandler) GetAssetsPath(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"path": h.fileSvc.GetAssetsPath()})
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"kenaito-notebook-server/services"
|
||||
)
|
||||
|
||||
type NoteHandler struct {
|
||||
db *services.Database
|
||||
}
|
||||
|
||||
func NewNoteHandler(db *services.Database) *NoteHandler {
|
||||
return &NoteHandler{db: db}
|
||||
}
|
||||
|
||||
func (h *NoteHandler) GetNoteTree(c *gin.Context) {
|
||||
userID := c.GetInt64("userID")
|
||||
result, err := h.db.GetNoteTree(userID)
|
||||
if err != nil {
|
||||
log.Printf("[API] GetNoteTree error: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *NoteHandler) GetNote(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.db.GetNote(id)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, note)
|
||||
}
|
||||
|
||||
func (h *NoteHandler) CreateNote(c *gin.Context) {
|
||||
userID := c.GetInt64("userID")
|
||||
|
||||
var req struct {
|
||||
ParentID int64 `json:"parentId"`
|
||||
Title string `json:"title"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.db.CreateNote(userID, req.ParentID, req.Title, req.IsFolder)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, note)
|
||||
}
|
||||
|
||||
func (h *NoteHandler) UpdateNote(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.UpdateNote(id, req.Title, req.Content, req.Tag); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "updated"})
|
||||
}
|
||||
|
||||
func (h *NoteHandler) DeleteNote(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.DeleteNote(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
||||
}
|
||||
|
||||
func (h *NoteHandler) SearchNotes(c *gin.Context) {
|
||||
userID := c.GetInt64("userID")
|
||||
|
||||
keyword := c.Query("q")
|
||||
if keyword == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "keyword is required"})
|
||||
return
|
||||
}
|
||||
|
||||
notes, err := h.db.SearchNotes(userID, keyword)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, notes)
|
||||
}
|
||||
|
||||
func (h *NoteHandler) GetTrashNotes(c *gin.Context) {
|
||||
userID := c.GetInt64("userID")
|
||||
|
||||
notes, err := h.db.GetTrashNotes(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, notes)
|
||||
}
|
||||
|
||||
func (h *NoteHandler) RestoreNote(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.RestoreNote(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "restored"})
|
||||
}
|
||||
|
||||
func (h *NoteHandler) PermanentDeleteNote(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.PermanentDeleteNote(id); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "permanently deleted"})
|
||||
}
|
||||
|
||||
func (h *NoteHandler) EmptyTrash(c *gin.Context) {
|
||||
userID := c.GetInt64("userID")
|
||||
|
||||
if err := h.db.EmptyTrash(userID); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "trash emptied"})
|
||||
}
|
||||
Reference in New Issue
Block a user