Files
SEEN/backend/internal/api/handlers/download_handler.go
T
2026-04-10 12:06:24 +02:00

169 lines
4.0 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/download"
"github.com/tdvorak/seen/backend/pkg/httpx"
)
type DownloadHandler struct {
service *download.Service
authService *auth.Service
}
func NewDownloadHandler(service *download.Service, authService *auth.Service) *DownloadHandler {
return &DownloadHandler{
service: service,
authService: authService,
}
}
type createDownloadRequest struct {
SourceType string `json:"sourceType"`
Source string `json:"source"`
Title string `json:"title"`
}
func (h *DownloadHandler) Create(c *gin.Context) {
user, ok := h.resolveUser(c)
if !ok {
return
}
var request createDownloadRequest
if err := c.ShouldBindJSON(&request); err != nil {
httpx.JSONError(c, http.StatusBadRequest, "invalid request body")
return
}
job, err := h.service.Create(c.Request.Context(), user.ID, download.CreateInput{
SourceType: request.SourceType,
Source: request.Source,
Title: request.Title,
})
if err != nil {
switch {
case errors.Is(err, download.ErrInvalidInput):
httpx.JSONError(c, http.StatusBadRequest, err.Error())
default:
httpx.JSONError(c, http.StatusInternalServerError, "failed to create download job")
}
return
}
httpx.JSON(c, http.StatusCreated, job)
}
func (h *DownloadHandler) List(c *gin.Context) {
user, ok := h.resolveUser(c)
if !ok {
return
}
items, err := h.service.List(c.Request.Context(), user.ID, download.ListParams{
Status: c.Query("status"),
Limit: parseIntSafe(c.Query("limit"), 20),
Offset: parseIntSafe(c.Query("offset"), 0),
})
if err != nil {
switch {
case errors.Is(err, download.ErrInvalidInput):
httpx.JSONError(c, http.StatusBadRequest, err.Error())
default:
httpx.JSONError(c, http.StatusInternalServerError, "failed to list download jobs")
}
return
}
httpx.JSON(c, http.StatusOK, items)
}
func (h *DownloadHandler) Cancel(c *gin.Context) {
user, ok := h.resolveUser(c)
if !ok {
return
}
jobID := c.Param("id")
job, err := h.service.Cancel(c.Request.Context(), user.ID, jobID)
if err != nil {
switch {
case errors.Is(err, download.ErrInvalidInput):
httpx.JSONError(c, http.StatusBadRequest, err.Error())
case errors.Is(err, download.ErrNotFound):
httpx.JSONError(c, http.StatusNotFound, err.Error())
default:
httpx.JSONError(c, http.StatusInternalServerError, "failed to cancel download job")
}
return
}
httpx.JSON(c, http.StatusOK, job)
}
func (h *DownloadHandler) Events(c *gin.Context) {
user, ok := h.resolveUser(c)
if !ok {
return
}
jobID := c.Param("id")
items, err := h.service.Events(c.Request.Context(), user.ID, jobID, download.EventParams{
After: c.Query("after"),
Limit: parseIntSafe(c.Query("limit"), 100),
})
if err != nil {
switch {
case errors.Is(err, download.ErrInvalidInput):
httpx.JSONError(c, http.StatusBadRequest, err.Error())
case errors.Is(err, download.ErrNotFound):
httpx.JSONError(c, http.StatusNotFound, err.Error())
default:
httpx.JSONError(c, http.StatusInternalServerError, "failed to list download events")
}
return
}
httpx.JSON(c, http.StatusOK, items)
}
func (h *DownloadHandler) 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 parseIntSafe(raw string, fallback int) int {
if raw == "" {
return fallback
}
parsed, err := strconv.Atoi(raw)
if err != nil {
return fallback
}
return parsed
}