Yimaru-BackEnd/internal/web_server/handlers/vimeo.go
2026-03-06 06:03:05 -08:00

485 lines
15 KiB
Go

package handlers
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/pkgs/vimeo"
"fmt"
"strconv"
"github.com/gofiber/fiber/v2"
)
type CreatePullUploadRequest struct {
Name string `json:"name" validate:"required"`
Description string `json:"description"`
SourceURL string `json:"source_url" validate:"required,url"`
FileSize int64 `json:"file_size" validate:"required,gt=0"`
}
type CreateTusUploadRequest struct {
Name string `json:"name" validate:"required"`
Description string `json:"description"`
FileSize int64 `json:"file_size" validate:"required,gt=0"`
}
type GetEmbedCodeRequest struct {
VideoID string `json:"video_id" validate:"required"`
Width int `json:"width"`
Height int `json:"height"`
Autoplay bool `json:"autoplay"`
Loop bool `json:"loop"`
Muted bool `json:"muted"`
Background bool `json:"background"`
}
type VimeoVideoResponse struct {
VimeoID string `json:"vimeo_id"`
URI string `json:"uri"`
Name string `json:"name"`
Description string `json:"description"`
Duration int `json:"duration"`
Width int `json:"width"`
Height int `json:"height"`
Link string `json:"link"`
EmbedURL string `json:"embed_url"`
EmbedHTML string `json:"embed_html"`
ThumbnailURL string `json:"thumbnail_url"`
Status string `json:"status"`
TranscodeStatus string `json:"transcode_status"`
}
type VimeoUploadResponse struct {
VimeoID string `json:"vimeo_id"`
URI string `json:"uri"`
Link string `json:"link"`
UploadLink string `json:"upload_link,omitempty"`
Status string `json:"status"`
}
type VimeoEmbedResponse struct {
VideoID string `json:"video_id"`
EmbedURL string `json:"embed_url"`
EmbedHTML string `json:"embed_html"`
}
// GetVimeoVideo godoc
// @Summary Get video information from Vimeo
// @Description Retrieves video details from Vimeo by video ID
// @Tags Vimeo
// @Accept json
// @Produce json
// @Param video_id path string true "Vimeo Video ID"
// @Success 200 {object} VimeoVideoResponse
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/vimeo/videos/{video_id} [get]
func (h *Handler) GetVimeoVideo(c *fiber.Ctx) error {
if h.vimeoSvc == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
Message: "Vimeo service is not configured",
Error: "Vimeo service is not enabled or missing access token",
})
}
videoID := c.Params("video_id")
if videoID == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "video_id is required",
Error: "video_id path parameter is empty",
})
}
info, err := h.vimeoSvc.GetVideoInfo(c.Context(), videoID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to get video info",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Video retrieved successfully",
Data: VimeoVideoResponse{
VimeoID: info.VimeoID,
URI: info.URI,
Name: info.Name,
Description: info.Description,
Duration: info.Duration,
Width: info.Width,
Height: info.Height,
Link: info.Link,
EmbedURL: info.EmbedURL,
EmbedHTML: info.EmbedHTML,
ThumbnailURL: info.ThumbnailURL,
Status: info.Status,
TranscodeStatus: info.TranscodeStatus,
},
Success: true,
StatusCode: fiber.StatusOK,
})
}
// CreatePullUpload godoc
// @Summary Create a pull upload to Vimeo
// @Description Initiates a pull upload where Vimeo fetches the video from a URL
// @Tags Vimeo
// @Accept json
// @Produce json
// @Param request body CreatePullUploadRequest true "Pull Upload Request"
// @Success 201 {object} VimeoUploadResponse
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/vimeo/uploads/pull [post]
func (h *Handler) CreatePullUpload(c *fiber.Ctx) error {
if h.vimeoSvc == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
Message: "Vimeo service is not configured",
Error: "Vimeo service is not configured",
})
}
var req CreatePullUploadRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid request body",
Error: err.Error(),
})
}
if valErrs, ok := h.validator.Validate(c, req); !ok {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to validate request body",
Error: fmt.Sprintf("%v", valErrs),
})
}
result, err := h.vimeoSvc.CreatePullUpload(c.Context(), req.Name, req.Description, req.SourceURL, req.FileSize)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to create Vimeo upload",
Error: err.Error(),
})
}
return c.Status(fiber.StatusCreated).JSON(domain.Response{
Message: "Vimeo upload created successfully",
Data: result,
Success: true,
StatusCode: fiber.StatusCreated,
})
}
// CreateTusUpload godoc
// @Summary Create a TUS resumable upload to Vimeo
// @Description Initiates a TUS resumable upload and returns the upload link
// @Tags Vimeo
// @Accept json
// @Produce json
// @Param request body CreateTusUploadRequest true "TUS Upload Request"
// @Success 201 {object} VimeoUploadResponse
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/vimeo/uploads/tus [post]
func (h *Handler) CreateTusUpload(c *fiber.Ctx) error {
if h.vimeoSvc == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
Message: "Vimeo service is not configured",
Error: "Vimeo service is not enabled or missing access token",
})
}
var req CreateTusUploadRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid request body",
Error: err.Error(),
})
}
if valErrs, ok := h.validator.Validate(c, req); !ok {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to validate request body",
Error: fmt.Sprintf("%v", valErrs),
})
}
result, err := h.vimeoSvc.CreateTusUpload(c.Context(), req.Name, req.Description, req.FileSize)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to create Vimeo TUS upload",
Error: err.Error(),
})
}
return c.Status(fiber.StatusCreated).JSON(domain.Response{
Message: "Vimeo TUS upload created successfully",
Data: result,
Success: true,
StatusCode: fiber.StatusCreated,
})
}
// GetEmbedCode godoc
// @Summary Get embed code for a Vimeo video
// @Description Generates an embeddable player iframe for the video
// @Tags Vimeo
// @Accept json
// @Produce json
// @Param video_id path string true "Vimeo Video ID"
// @Param width query int false "Player width" default(640)
// @Param height query int false "Player height" default(360)
// @Param autoplay query bool false "Autoplay video"
// @Param loop query bool false "Loop video"
// @Param muted query bool false "Mute video"
// @Param background query bool false "Background mode (no controls)"
// @Success 200 {object} VimeoEmbedResponse
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/vimeo/videos/{video_id}/embed [get]
func (h *Handler) GetEmbedCode(c *fiber.Ctx) error {
if h.vimeoSvc == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
Message: "Vimeo service is not configured",
Error: "Vimeo service is not configured",
})
}
videoID := c.Params("video_id")
if videoID == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "video_id is required",
Error: "video_id is empty",
})
}
width, _ := strconv.Atoi(c.Query("width", "640"))
height, _ := strconv.Atoi(c.Query("height", "360"))
autoplay := c.Query("autoplay") == "true"
loop := c.Query("loop") == "true"
muted := c.Query("muted") == "true"
background := c.Query("background") == "true"
opts := &vimeo.EmbedOptions{
Autoplay: autoplay,
Loop: loop,
Muted: muted,
Background: background,
Title: true,
Byline: true,
Portrait: true,
}
embedHTML, err := h.vimeoSvc.GetEmbedCode(c.Context(), videoID, width, height, opts)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to get embed code",
Error: err.Error(),
})
}
embedURL := h.vimeoSvc.GeneratePlayerURL(videoID, opts)
return c.JSON(domain.Response{
Message: "Embed code retrieved successfully",
Data: VimeoEmbedResponse{
VideoID: videoID,
EmbedURL: embedURL,
EmbedHTML: embedHTML,
},
Success: true,
StatusCode: fiber.StatusOK,
})
}
// DeleteVimeoVideo godoc
// @Summary Delete a video from Vimeo
// @Description Deletes a video from the Vimeo account
// @Tags Vimeo
// @Accept json
// @Produce json
// @Param video_id path string true "Vimeo Video ID"
// @Success 204
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/vimeo/videos/{video_id} [delete]
func (h *Handler) DeleteVimeoVideo(c *fiber.Ctx) error {
if h.vimeoSvc == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
Message: "Vimeo service is not configured",
Error: "Vimeo service is not enabled or missing access token",
})
}
videoID := c.Params("video_id")
if videoID == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "video_id is required",
Error: "video_id path parameter is empty",
})
}
if err := h.vimeoSvc.DeleteVideo(c.Context(), videoID); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to delete video",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Video deleted successfully",
Success: true,
StatusCode: fiber.StatusOK,
})
}
// GetTranscodeStatus godoc
// @Summary Get transcode status of a Vimeo video
// @Description Returns the current transcoding status of a video
// @Tags Vimeo
// @Accept json
// @Produce json
// @Param video_id path string true "Vimeo Video ID"
// @Success 200 {object} map[string]string
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/vimeo/videos/{video_id}/status [get]
func (h *Handler) GetTranscodeStatus(c *fiber.Ctx) error {
if h.vimeoSvc == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
Message: "Vimeo service is not configured",
Error: "Vimeo service is not enabled or missing access token",
})
}
videoID := c.Params("video_id")
if videoID == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "video_id is required",
Error: "video_id path parameter is empty",
})
}
status, err := h.vimeoSvc.GetTranscodeStatus(c.Context(), videoID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to get transcode status",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Transcode status retrieved successfully",
Data: fiber.Map{
"video_id": videoID,
"status": status,
},
Success: true,
StatusCode: fiber.StatusOK,
})
}
// GetSampleVideo godoc
// @Summary Get a sample Vimeo video with iframe embed
// @Description Fetches a sample video from Vimeo and returns video details along with an embeddable iframe for client-side integration
// @Tags Vimeo
// @Accept json
// @Produce json
// @Param video_id query string false "Vimeo Video ID to use as sample" default(76979871)
// @Param width query int false "Player width" default(640)
// @Param height query int false "Player height" default(360)
// @Success 200 {object} domain.Response
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/vimeo/sample [get]
func (h *Handler) GetSampleVideo(c *fiber.Ctx) error {
if h.vimeoSvc == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
Message: "Vimeo service is not configured",
Error: "Vimeo service is not enabled or missing access token",
})
}
videoID := c.Query("video_id", "76979871")
width, _ := strconv.Atoi(c.Query("width", "640"))
height, _ := strconv.Atoi(c.Query("height", "360"))
info, iframe, err := h.vimeoSvc.GetSampleVideo(c.Context(), videoID, width, height)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to get sample video",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Sample video retrieved successfully",
Data: fiber.Map{
"video": VimeoVideoResponse{
VimeoID: info.VimeoID,
URI: info.URI,
Name: info.Name,
Description: info.Description,
Duration: info.Duration,
Width: info.Width,
Height: info.Height,
Link: info.Link,
EmbedURL: info.EmbedURL,
EmbedHTML: info.EmbedHTML,
ThumbnailURL: info.ThumbnailURL,
Status: info.Status,
TranscodeStatus: info.TranscodeStatus,
},
"iframe": iframe,
},
Success: true,
StatusCode: fiber.StatusOK,
})
}
// GetOEmbed godoc
// @Summary Get oEmbed data for a Vimeo URL
// @Description Fetches oEmbed metadata for a Vimeo video URL
// @Tags Vimeo
// @Accept json
// @Produce json
// @Param url query string true "Vimeo video URL"
// @Param width query int false "Desired width"
// @Param height query int false "Desired height"
// @Success 200 {object} vimeo.OEmbedResponse
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/vimeo/oembed [get]
func (h *Handler) GetOEmbed(c *fiber.Ctx) error {
if h.vimeoSvc == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
Message: "Vimeo service is not configured",
Error: "Vimeo service is not enabled or missing access token",
})
}
vimeoURL := c.Query("url")
if vimeoURL == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "url query parameter is required",
Error: "url query parameter is empty",
})
}
width, _ := strconv.Atoi(c.Query("width", "0"))
height, _ := strconv.Atoi(c.Query("height", "0"))
oembed, err := h.vimeoSvc.GetOEmbed(c.Context(), vimeoURL, width, height)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to get oEmbed data",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "oEmbed data retrieved successfully",
Data: oembed,
Success: true,
StatusCode: fiber.StatusOK,
})
}