Added api versioning
This commit is contained in:
parent
7a3a732efb
commit
57d7faa6ca
@ -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"})
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user