Added set current

This commit is contained in:
Nik Afiq 2025-11-04 23:29:34 +09:00
parent 0c4453500f
commit 7a3a732efb
3 changed files with 89 additions and 1 deletions

View File

@ -30,5 +30,33 @@ func NewRouter(svc service.EpisodeService) *gin.Engine {
c.JSON(http.StatusOK, cur) c.JSON(http.StatusOK, cur)
}) })
type setCurrentReq struct {
ID int64 `json:"id" binding:"required"`
StartTime string `json:"start_time" binding:"required"` // HH:MM or HH:MM:SS
}
r.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)
})
return r return r
} }

View File

@ -22,6 +22,7 @@ type Episode struct {
type EpisodeRepository interface { type EpisodeRepository interface {
GetCurrent(ctx context.Context) (Episode, error) GetCurrent(ctx context.Context) (Episode, error)
SetCurrent(ctx context.Context, id int64, startHHMMSS string) error
} }
type pgxEpisodeRepo struct { type pgxEpisodeRepo struct {
@ -58,3 +59,27 @@ LIMIT 1;
} }
return e, nil return e, nil
} }
func (r *pgxEpisodeRepo) SetCurrent(ctx context.Context, id int64, startHHMMSS string) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer func() { _ = tx.Rollback(ctx) }()
// 1) clear any current episode flag
if _, err := tx.Exec(ctx, `UPDATE current SET current_ep = false WHERE current_ep = true`); err != nil {
return err
}
// 2) set start_time and current_ep for the id
ct, err := tx.Exec(ctx, `UPDATE current SET start_time = $1::time, current_ep = true WHERE id = $2`, startHHMMSS, id)
if err != nil {
return err
}
if ct.RowsAffected() == 0 {
return ErrNotFound
}
return tx.Commit(ctx)
}

View File

@ -2,13 +2,18 @@ package service
import ( import (
"context" "context"
"errors"
"strings"
"time" "time"
"watch-party-backend/internal/repo" "watch-party-backend/internal/repo"
) )
var ErrInvalidTime = errors.New("invalid start_time (expected HH:MM or HH:MM:SS)")
type EpisodeService interface { type EpisodeService interface {
GetCurrent(ctx context.Context) (repo.Episode, error) GetCurrent(ctx context.Context) (repo.Episode, error)
SetCurrent(ctx context.Context, id int64, start string) (repo.Episode, error)
} }
type episodeService struct { type episodeService struct {
@ -20,8 +25,38 @@ func NewEpisodeService(r repo.EpisodeRepository) EpisodeService {
} }
func (s *episodeService) GetCurrent(ctx context.Context) (repo.Episode, error) { func (s *episodeService) GetCurrent(ctx context.Context) (repo.Episode, error) {
// Add cross-cutting concerns here (timeouts, metrics, caching, etc.)
c, cancel := context.WithTimeout(ctx, 3*time.Second) c, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() defer cancel()
return s.repo.GetCurrent(c) return s.repo.GetCurrent(c)
} }
func (s *episodeService) SetCurrent(ctx context.Context, id int64, start string) (repo.Episode, error) {
// normalize and validate time as HH:MM:SS
normalized, err := normalizeHHMMSS(start)
if err != nil {
return repo.Episode{}, ErrInvalidTime
}
c, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// do the transaction
if err := s.repo.SetCurrent(c, id, normalized); err != nil {
return repo.Episode{}, err
}
// return the new current row
return s.repo.GetCurrent(c)
}
func normalizeHHMMSS(s string) (string, error) {
s = strings.TrimSpace(s)
if len(s) == 5 { // HH:MM → append seconds
s += ":00"
}
// Strict parse/format to 24h
t, err := time.Parse("15:04:05", s)
if err != nil {
return "", err
}
return t.Format("15:04:05"), nil
}