2025-11-22 19:37:06 +09:00

146 lines
3.7 KiB
Go

package httpapi
import (
"errors"
"net/http"
"strconv"
"time"
"watch-party-backend/internal/repo"
"watch-party-backend/internal/service"
"github.com/gin-gonic/gin"
)
func NewRouter(svc service.EpisodeService) *gin.Engine {
r := gin.New()
r.Use(gin.Logger(), gin.Recovery())
r.GET("/healthz", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
api := r.Group("/api")
v1 := api.Group("/v1")
v1.GET("/time", func(c *gin.Context) {
now := time.Now().UTC().UnixMilli()
c.Header("Cache-Control", "no-store, max-age=0, must-revalidate")
c.JSON(http.StatusOK, gin.H{
"now": now,
})
})
// GET /v1/current
v1.GET("/current", func(c *gin.Context) {
cur, err := svc.GetCurrent(c.Request.Context())
if err != nil {
if err == repo.ErrNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "no current row found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
return
}
c.JSON(http.StatusOK, cur)
})
// POST /v1/current (strict HH:MM:SS already enforced at service layer)
type setCurrentReq struct {
ID int64 `json:"id" binding:"required"`
StartTime string `json:"start_time" binding:"required"`
}
v1.POST("/current", func(c *gin.Context) {
var req setCurrentReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
return
}
cur, err := svc.SetCurrent(c.Request.Context(), req.ID, req.StartTime)
if err != nil {
switch err {
case service.ErrInvalidTime:
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
case repo.ErrNotFound:
c.JSON(http.StatusNotFound, gin.H{"error": "id not found"})
return
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed"})
return
}
}
c.JSON(http.StatusOK, cur)
})
// POST /v1/archive (move one or many IDs from current → current_archive)
type moveReq struct {
ID *int64 `json:"id,omitempty"`
IDs []int64 `json:"ids,omitempty"`
}
v1.POST("/archive", func(c *gin.Context) {
var req moveReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
return
}
ids := make([]int64, 0, len(req.IDs)+1)
if req.ID != nil {
ids = append(ids, *req.ID)
}
ids = append(ids, req.IDs...)
res, err := svc.MoveToArchive(c.Request.Context(), ids)
if err != nil {
if err == service.ErrEmptyIDs {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "move failed"})
return
}
c.JSON(http.StatusOK, gin.H{
"moved_ids": res.MovedIDs,
"deleted_ids": res.DeletedIDs,
"skipped_ids": res.SkippedIDs,
"inserted": len(res.MovedIDs),
"deleted": len(res.DeletedIDs),
"skipped": len(res.SkippedIDs),
})
})
// GET /v1/shows — list all rows from "current"
v1.GET("/shows", func(c *gin.Context) {
items, err := svc.ListAll(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "list failed"})
return
}
c.JSON(http.StatusOK, items)
})
// DELETE /v1/shows/:id — delete a row from "current" by id
v1.DELETE("/shows/:id", func(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
err = svc.Delete(c.Request.Context(), id)
if err != nil {
if errors.Is(err, repo.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "id not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed"})
return
}
c.Status(http.StatusNoContent)
})
return r
}