mirror of
https://github.com/Dvorinka/SEEN.git
synced 2026-06-04 12:33:02 +00:00
228 lines
5.6 KiB
Go
228 lines
5.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/tdvorak/seen/backend/internal/domain"
|
|
"github.com/tdvorak/seen/backend/internal/services/auth"
|
|
"github.com/tdvorak/seen/backend/internal/services/catalog"
|
|
"github.com/tdvorak/seen/backend/pkg/httpx"
|
|
)
|
|
|
|
type CatalogHandler struct {
|
|
service *catalog.Service
|
|
authService *auth.Service
|
|
}
|
|
|
|
func NewCatalogHandler(service *catalog.Service, authService *auth.Service) *CatalogHandler {
|
|
return &CatalogHandler{service: service, authService: authService}
|
|
}
|
|
|
|
type watchLaterAddRequest struct {
|
|
MediaID int `json:"mediaId"`
|
|
}
|
|
|
|
type progressUpdateRequest struct {
|
|
MediaID int `json:"mediaId"`
|
|
SeasonNumber int `json:"seasonNumber"`
|
|
EpisodeNumber int `json:"episodeNumber"`
|
|
ProgressPercent int `json:"progressPercent"`
|
|
}
|
|
|
|
func (h *CatalogHandler) Dashboard(c *gin.Context) {
|
|
httpx.JSON(c, http.StatusOK, h.service.Dashboard())
|
|
}
|
|
|
|
func (h *CatalogHandler) ContinueWatching(c *gin.Context) {
|
|
user, ok := h.resolveUser(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
items, err := h.service.ContinueWatching(user.ID)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, catalog.ErrInvalidInput):
|
|
httpx.JSONError(c, http.StatusBadRequest, err.Error())
|
|
default:
|
|
httpx.JSONError(c, http.StatusInternalServerError, "failed to load continue watching")
|
|
}
|
|
return
|
|
}
|
|
|
|
httpx.JSON(c, http.StatusOK, items)
|
|
}
|
|
|
|
func (h *CatalogHandler) Discover(c *gin.Context) {
|
|
page := parseInt(c.Query("page"), 1)
|
|
pageSize := parseInt(c.Query("pageSize"), 6)
|
|
|
|
httpx.JSON(c, http.StatusOK, h.service.Discover(catalog.DiscoverParams{
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
Query: c.Query("query"),
|
|
Genre: c.Query("genre"),
|
|
MediaType: c.Query("mediaType"),
|
|
}))
|
|
}
|
|
|
|
func (h *CatalogHandler) Games(c *gin.Context) {
|
|
page := parseInt(c.Query("page"), 1)
|
|
pageSize := parseInt(c.Query("pageSize"), 6)
|
|
|
|
httpx.JSON(c, http.StatusOK, h.service.Discover(catalog.DiscoverParams{
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
Query: c.Query("query"),
|
|
Genre: c.Query("genre"),
|
|
MediaType: string(catalog.MediaTypeGame),
|
|
}))
|
|
}
|
|
|
|
func (h *CatalogHandler) Search(c *gin.Context) {
|
|
httpx.JSON(c, http.StatusOK, h.service.Search(catalog.SearchParams{
|
|
Query: c.Query("query"),
|
|
Genre: c.Query("genre"),
|
|
MediaType: c.Query("mediaType"),
|
|
}))
|
|
}
|
|
|
|
func (h *CatalogHandler) WatchLater(c *gin.Context) {
|
|
user, ok := h.resolveUser(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
items, err := h.service.WatchLater(user.ID)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, catalog.ErrInvalidInput):
|
|
httpx.JSONError(c, http.StatusBadRequest, err.Error())
|
|
default:
|
|
httpx.JSONError(c, http.StatusInternalServerError, "failed to load watch later")
|
|
}
|
|
return
|
|
}
|
|
|
|
httpx.JSON(c, http.StatusOK, items)
|
|
}
|
|
|
|
func (h *CatalogHandler) AddWatchLater(c *gin.Context) {
|
|
user, ok := h.resolveUser(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var request watchLaterAddRequest
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
httpx.JSONError(c, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
|
|
items, err := h.service.AddWatchLater(user.ID, request.MediaID)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, catalog.ErrInvalidInput):
|
|
httpx.JSONError(c, http.StatusBadRequest, err.Error())
|
|
case errors.Is(err, catalog.ErrMediaNotFound):
|
|
httpx.JSONError(c, http.StatusNotFound, err.Error())
|
|
default:
|
|
httpx.JSONError(c, http.StatusInternalServerError, "failed to add watch later item")
|
|
}
|
|
return
|
|
}
|
|
|
|
httpx.JSON(c, http.StatusOK, items)
|
|
}
|
|
|
|
func (h *CatalogHandler) RemoveWatchLater(c *gin.Context) {
|
|
user, ok := h.resolveUser(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
mediaID := parseInt(c.Param("mediaId"), 0)
|
|
items, err := h.service.RemoveWatchLater(user.ID, mediaID)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, catalog.ErrInvalidInput):
|
|
httpx.JSONError(c, http.StatusBadRequest, err.Error())
|
|
default:
|
|
httpx.JSONError(c, http.StatusInternalServerError, "failed to remove watch later item")
|
|
}
|
|
return
|
|
}
|
|
|
|
httpx.JSON(c, http.StatusOK, items)
|
|
}
|
|
|
|
func (h *CatalogHandler) UpdateProgress(c *gin.Context) {
|
|
user, ok := h.resolveUser(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var request progressUpdateRequest
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
httpx.JSONError(c, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
|
|
items, err := h.service.UpdateProgress(user.ID, catalog.ProgressUpdateInput{
|
|
MediaID: request.MediaID,
|
|
SeasonNumber: request.SeasonNumber,
|
|
EpisodeNumber: request.EpisodeNumber,
|
|
ProgressPercent: request.ProgressPercent,
|
|
})
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, catalog.ErrInvalidInput):
|
|
httpx.JSONError(c, http.StatusBadRequest, err.Error())
|
|
case errors.Is(err, catalog.ErrMediaNotFound):
|
|
httpx.JSONError(c, http.StatusNotFound, err.Error())
|
|
default:
|
|
httpx.JSONError(c, http.StatusInternalServerError, "failed to update progress")
|
|
}
|
|
return
|
|
}
|
|
|
|
httpx.JSON(c, http.StatusOK, items)
|
|
}
|
|
|
|
func (h *CatalogHandler) resolveUser(c *gin.Context) (*domain.User, bool) {
|
|
accessToken, ok := bearerToken(c.GetHeader("Authorization"))
|
|
if !ok {
|
|
httpx.JSONError(c, http.StatusUnauthorized, "missing bearer token")
|
|
return nil, false
|
|
}
|
|
|
|
user, err := h.authService.UserFromAccessToken(c.Request.Context(), accessToken)
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, auth.ErrInvalidToken):
|
|
httpx.JSONError(c, http.StatusUnauthorized, err.Error())
|
|
default:
|
|
httpx.JSONError(c, http.StatusInternalServerError, "failed to resolve user")
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
return user, true
|
|
}
|
|
|
|
func parseInt(raw string, fallback int) int {
|
|
if raw == "" {
|
|
return fallback
|
|
}
|
|
|
|
parsed, err := strconv.Atoi(raw)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
|
|
return parsed
|
|
}
|