147 lines
4.0 KiB
Go
147 lines
4.0 KiB
Go
package handlers
|
|
|
|
// folders.go — Chat folder CRUD (v0.23.1)
|
|
//
|
|
// Folders are user-scoped groupings for personal (direct) chats.
|
|
// Routes:
|
|
// GET /api/v1/folders
|
|
// POST /api/v1/folders
|
|
// PUT /api/v1/folders/:id
|
|
// DELETE /api/v1/folders/:id
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"git.gobha.me/xcaliber/chat-switchboard/database"
|
|
)
|
|
|
|
type FolderHandler struct{}
|
|
|
|
func NewFolderHandler() *FolderHandler { return &FolderHandler{} }
|
|
|
|
type folderRow struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
ParentID *string `json:"parent_id,omitempty"`
|
|
SortOrder int `json:"sort_order"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
func (h *FolderHandler) List(c *gin.Context) {
|
|
userID := getUserID(c)
|
|
rows, err := database.DB.QueryContext(c.Request.Context(), database.Q(`
|
|
SELECT id, name, parent_id, sort_order, created_at, updated_at
|
|
FROM folders
|
|
WHERE user_id = $1
|
|
ORDER BY sort_order, name
|
|
`), userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list folders"})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var folders []folderRow
|
|
for rows.Next() {
|
|
var f folderRow
|
|
if err := rows.Scan(&f.ID, &f.Name, &f.ParentID, &f.SortOrder, &f.CreatedAt, &f.UpdatedAt); err != nil {
|
|
continue
|
|
}
|
|
folders = append(folders, f)
|
|
}
|
|
if folders == nil {
|
|
folders = []folderRow{}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"folders": folders})
|
|
}
|
|
|
|
func (h *FolderHandler) Create(c *gin.Context) {
|
|
userID := getUserID(c)
|
|
var req struct {
|
|
Name string `json:"name" binding:"required"`
|
|
SortOrder int `json:"sort_order"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
req.Name = strings.TrimSpace(req.Name)
|
|
if req.Name == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"})
|
|
return
|
|
}
|
|
|
|
var f folderRow
|
|
err := database.DB.QueryRowContext(c.Request.Context(), database.Q(`
|
|
INSERT INTO folders (user_id, name, sort_order)
|
|
VALUES ($1, $2, $3)
|
|
RETURNING id, name, parent_id, sort_order, created_at, updated_at
|
|
`), userID, req.Name, req.SortOrder).Scan(
|
|
&f.ID, &f.Name, &f.ParentID, &f.SortOrder, &f.CreatedAt, &f.UpdatedAt)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create folder"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, gin.H{"folder": f})
|
|
}
|
|
|
|
func (h *FolderHandler) Update(c *gin.Context) {
|
|
userID := getUserID(c)
|
|
folderID := c.Param("id")
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
SortOrder *int `json:"sort_order"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if req.Name != "" {
|
|
req.Name = strings.TrimSpace(req.Name)
|
|
}
|
|
|
|
res, err := database.DB.ExecContext(c.Request.Context(), database.Q(`
|
|
UPDATE folders
|
|
SET name = COALESCE(NULLIF($3, ''), name),
|
|
sort_order = COALESCE($4, sort_order)
|
|
WHERE id = $1 AND user_id = $2
|
|
`), folderID, userID, req.Name, req.SortOrder)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update folder"})
|
|
return
|
|
}
|
|
if n, _ := res.RowsAffected(); n == 0 {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "folder not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
|
}
|
|
|
|
func (h *FolderHandler) Delete(c *gin.Context) {
|
|
userID := getUserID(c)
|
|
folderID := c.Param("id")
|
|
|
|
// Unassign chats before deleting folder
|
|
_, _ = database.DB.ExecContext(c.Request.Context(), database.Q(`
|
|
UPDATE channels SET folder_id = NULL WHERE folder_id = $1 AND user_id = $2
|
|
`), folderID, userID)
|
|
|
|
res, err := database.DB.ExecContext(c.Request.Context(), database.Q(`
|
|
DELETE FROM folders WHERE id = $1 AND user_id = $2
|
|
`), folderID, userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete folder"})
|
|
return
|
|
}
|
|
if n, _ := res.RowsAffected(); n == 0 {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "folder not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
|
}
|