Refactor episode service and handlers to use UseCases interface

This commit is contained in:
Nik Afiq 2025-12-02 20:56:45 +09:00
parent f8837fed9f
commit ab7f919d0e
4 changed files with 29 additions and 26 deletions

View File

@ -0,0 +1,12 @@
package episode
import "context"
// UseCases captures the inbound port for episode-related interactions.
type UseCases interface {
GetCurrent(ctx context.Context) (Episode, error)
SetCurrent(ctx context.Context, id int64, start string) (Episode, error)
MoveToArchive(ctx context.Context, ids []int64) (MoveResult, error)
ListAll(ctx context.Context) ([]Episode, error)
Delete(ctx context.Context, id int64) error
}

View File

@ -7,12 +7,11 @@ import (
"time" "time"
"watch-party-backend/internal/core/episode" "watch-party-backend/internal/core/episode"
"watch-party-backend/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func NewRouter(svc service.EpisodeService) *gin.Engine { func NewRouter(svc episode.UseCases) *gin.Engine {
r := gin.New() r := gin.New()
r.Use(gin.Logger(), gin.Recovery()) r.Use(gin.Logger(), gin.Recovery())
@ -54,7 +53,7 @@ func timeHandler(c *gin.Context) {
// @Failure 404 {object} HTTPError "no current row found" // @Failure 404 {object} HTTPError "no current row found"
// @Failure 500 {object} HTTPError "query failed" // @Failure 500 {object} HTTPError "query failed"
// @Router /api/v1/current [get] // @Router /api/v1/current [get]
func getCurrentHandler(svc service.EpisodeService) gin.HandlerFunc { func getCurrentHandler(svc episode.UseCases) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
cur, err := svc.GetCurrent(c.Request.Context()) cur, err := svc.GetCurrent(c.Request.Context())
if err != nil { if err != nil {
@ -81,7 +80,7 @@ func getCurrentHandler(svc service.EpisodeService) gin.HandlerFunc {
// @Failure 404 {object} HTTPError "id not found" // @Failure 404 {object} HTTPError "id not found"
// @Failure 500 {object} HTTPError "update failed" // @Failure 500 {object} HTTPError "update failed"
// @Router /api/v1/current [post] // @Router /api/v1/current [post]
func setCurrentHandler(svc service.EpisodeService) gin.HandlerFunc { func setCurrentHandler(svc episode.UseCases) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
var req SetCurrentReq var req SetCurrentReq
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
@ -117,7 +116,7 @@ func setCurrentHandler(svc service.EpisodeService) gin.HandlerFunc {
// @Failure 400 {object} HTTPError "empty ids" // @Failure 400 {object} HTTPError "empty ids"
// @Failure 500 {object} HTTPError "move failed" // @Failure 500 {object} HTTPError "move failed"
// @Router /api/v1/archive [post] // @Router /api/v1/archive [post]
func moveToArchiveHandler(svc service.EpisodeService) gin.HandlerFunc { func moveToArchiveHandler(svc episode.UseCases) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
var req MoveReq var req MoveReq
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
@ -158,7 +157,7 @@ func moveToArchiveHandler(svc service.EpisodeService) gin.HandlerFunc {
// @Success 200 {array} CurrentResponse // @Success 200 {array} CurrentResponse
// @Failure 500 {object} HTTPError "list failed" // @Failure 500 {object} HTTPError "list failed"
// @Router /api/v1/shows [get] // @Router /api/v1/shows [get]
func listShowsHandler(svc service.EpisodeService) gin.HandlerFunc { func listShowsHandler(svc episode.UseCases) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
items, err := svc.ListAll(c.Request.Context()) items, err := svc.ListAll(c.Request.Context())
if err != nil { if err != nil {
@ -180,7 +179,7 @@ func listShowsHandler(svc service.EpisodeService) gin.HandlerFunc {
// @Failure 404 {object} HTTPError "id not found" // @Failure 404 {object} HTTPError "id not found"
// @Failure 500 {object} HTTPError "delete failed" // @Failure 500 {object} HTTPError "delete failed"
// @Router /api/v1/shows [delete] // @Router /api/v1/shows [delete]
func deleteShowsHandler(svc service.EpisodeService) gin.HandlerFunc { func deleteShowsHandler(svc episode.UseCases) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
idStr := c.Query("id") idStr := c.Query("id")
id, err := strconv.ParseInt(idStr, 10, 64) id, err := strconv.ParseInt(idStr, 10, 64)

View File

@ -11,12 +11,11 @@ import (
"watch-party-backend/internal/core/episode" "watch-party-backend/internal/core/episode"
httpapi "watch-party-backend/internal/http" httpapi "watch-party-backend/internal/http"
"watch-party-backend/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// ---- fake service implementing service.EpisodeService ---- // ---- fake service implementing episode.UseCases ----
type fakeSvc struct { type fakeSvc struct {
cur episode.Episode cur episode.Episode
@ -55,7 +54,7 @@ func (f *fakeSvc) Delete(ctx context.Context, id int64) error {
// ---- helpers ---- // ---- helpers ----
func newRouterWithSvc(svc service.EpisodeService) *gin.Engine { func newRouterWithSvc(svc episode.UseCases) *gin.Engine {
gin.SetMode(gin.TestMode) gin.SetMode(gin.TestMode)
return httpapi.NewRouter(svc) return httpapi.NewRouter(svc)
} }

View File

@ -8,29 +8,22 @@ import (
"watch-party-backend/internal/core/episode" "watch-party-backend/internal/core/episode"
) )
type EpisodeService interface { // Service implements the episode.UseCases port.
GetCurrent(ctx context.Context) (episode.Episode, error) type Service struct {
SetCurrent(ctx context.Context, id int64, start string) (episode.Episode, error)
MoveToArchive(ctx context.Context, ids []int64) (episode.MoveResult, error)
ListAll(ctx context.Context) ([]episode.Episode, error)
Delete(ctx context.Context, id int64) error
}
type episodeService struct {
repo episode.Repository repo episode.Repository
} }
func NewEpisodeService(r episode.Repository) EpisodeService { func NewEpisodeService(r episode.Repository) *Service {
return &episodeService{repo: r} return &Service{repo: r}
} }
func (s *episodeService) ListAll(ctx context.Context) ([]episode.Episode, error) { func (s *Service) ListAll(ctx context.Context) ([]episode.Episode, error) {
c, cancel := context.WithTimeout(ctx, 5*time.Second) c, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
return s.repo.ListAll(c) return s.repo.ListAll(c)
} }
func (s *episodeService) GetCurrent(ctx context.Context) (episode.Episode, error) { func (s *Service) GetCurrent(ctx context.Context) (episode.Episode, error) {
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)
@ -38,7 +31,7 @@ func (s *episodeService) GetCurrent(ctx context.Context) (episode.Episode, error
var hhmmss = regexp.MustCompile(`^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$`) var hhmmss = regexp.MustCompile(`^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$`)
func (s *episodeService) SetCurrent(ctx context.Context, id int64, start string) (episode.Episode, error) { func (s *Service) SetCurrent(ctx context.Context, id int64, start string) (episode.Episode, error) {
if !hhmmss.MatchString(start) { if !hhmmss.MatchString(start) {
return episode.Episode{}, episode.ErrInvalidStartTime return episode.Episode{}, episode.ErrInvalidStartTime
} }
@ -50,7 +43,7 @@ func (s *episodeService) SetCurrent(ctx context.Context, id int64, start string)
return s.repo.GetCurrent(c) return s.repo.GetCurrent(c)
} }
func (s *episodeService) MoveToArchive(ctx context.Context, ids []int64) (episode.MoveResult, error) { func (s *Service) MoveToArchive(ctx context.Context, ids []int64) (episode.MoveResult, error) {
uniq := make([]int64, 0, len(ids)) uniq := make([]int64, 0, len(ids))
seen := make(map[int64]struct{}, len(ids)) seen := make(map[int64]struct{}, len(ids))
for _, id := range ids { for _, id := range ids {
@ -68,7 +61,7 @@ func (s *episodeService) MoveToArchive(ctx context.Context, ids []int64) (episod
return s.repo.MoveToArchive(c, uniq) return s.repo.MoveToArchive(c, uniq)
} }
func (s *episodeService) Delete(ctx context.Context, id int64) error { func (s *Service) Delete(ctx context.Context, id int64) error {
c, cancel := context.WithTimeout(ctx, 5*time.Second) c, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() defer cancel()
return s.repo.Delete(c, id) return s.repo.Delete(c, id)