Added api versioning

This commit is contained in:
Nik Afiq 2025-11-04 23:53:32 +09:00
parent 7a3a732efb
commit 57d7faa6ca
3 changed files with 18 additions and 21 deletions

View File

@ -17,7 +17,9 @@ func NewRouter(svc service.EpisodeService) *gin.Engine {
c.JSON(http.StatusOK, gin.H{"status": "ok"}) c.JSON(http.StatusOK, gin.H{"status": "ok"})
}) })
r.GET("/current", func(c *gin.Context) { v1 := r.Group("/v1")
v1.GET("/current", func(c *gin.Context) {
cur, err := svc.GetCurrent(c.Request.Context()) cur, err := svc.GetCurrent(c.Request.Context())
if err != nil { if err != nil {
if err == repo.ErrNotFound { if err == repo.ErrNotFound {
@ -32,10 +34,10 @@ func NewRouter(svc service.EpisodeService) *gin.Engine {
type setCurrentReq struct { type setCurrentReq struct {
ID int64 `json:"id" binding:"required"` ID int64 `json:"id" binding:"required"`
StartTime string `json:"start_time" binding:"required"` // HH:MM or HH:MM:SS StartTime string `json:"start_time" binding:"required"`
} }
r.POST("/current", func(c *gin.Context) { v1.POST("/current", func(c *gin.Context) {
var req setCurrentReq var req setCurrentReq
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})

View File

@ -3,13 +3,13 @@ package service
import ( import (
"context" "context"
"errors" "errors"
"strings" "regexp"
"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)") var ErrInvalidTime = errors.New("invalid start_time (expected HH:MM:SS)")
type EpisodeService interface { type EpisodeService interface {
GetCurrent(ctx context.Context) (repo.Episode, error) GetCurrent(ctx context.Context) (repo.Episode, error)
@ -31,32 +31,27 @@ func (s *episodeService) GetCurrent(ctx context.Context) (repo.Episode, error) {
} }
func (s *episodeService) SetCurrent(ctx context.Context, id int64, start string) (repo.Episode, error) { func (s *episodeService) SetCurrent(ctx context.Context, id int64, start string) (repo.Episode, error) {
// normalize and validate time as HH:MM:SS // Strict HH:MM:SS (24h, zero-padded) — e.g., 20:07:05
normalized, err := normalizeHHMMSS(start) if !isHHMMSS(start) {
if err != nil {
return repo.Episode{}, ErrInvalidTime return repo.Episode{}, ErrInvalidTime
} }
c, cancel := context.WithTimeout(ctx, 5*time.Second) c, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
// do the transaction if err := s.repo.SetCurrent(c, id, start); err != nil {
if err := s.repo.SetCurrent(c, id, normalized); err != nil {
return repo.Episode{}, err return repo.Episode{}, err
} }
// return the new current row // return the new current row
return s.repo.GetCurrent(c) return s.repo.GetCurrent(c)
} }
func normalizeHHMMSS(s string) (string, error) { var hhmmss = regexp.MustCompile(`^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$`)
s = strings.TrimSpace(s)
if len(s) == 5 { // HH:MM → append seconds func isHHMMSS(s string) bool {
s += ":00" if !hhmmss.MatchString(s) {
return false
} }
// Strict parse/format to 24h _, err := time.Parse("15:04:05", s)
t, err := time.Parse("15:04:05", s) return err == nil
if err != nil {
return "", err
}
return t.Format("15:04:05"), nil
} }

View File

@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
// ===== Config & fallbacks ===== // ===== Config & fallbacks =====
const TIMEZONE = "Asia/Tokyo"; // JST (UTC+09) const TIMEZONE = "Asia/Tokyo"; // JST (UTC+09)
const API_URL = "/api/current"; const API_URL = "/api/v1/current";
const FALLBACK_START_HOUR = 19; const FALLBACK_START_HOUR = 19;
const FALLBACK_START_MINUTE = 25; const FALLBACK_START_MINUTE = 25;
const FALLBACK_END_SECONDS = 300; const FALLBACK_END_SECONDS = 300;