123 lines
3.1 KiB
Go
123 lines
3.1 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
"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)
|
|
})
|
|
return r
|
|
}
|