draft
This commit is contained in:
550
internal/modules/videos/module.go
Normal file
550
internal/modules/videos/module.go
Normal file
@@ -0,0 +1,550 @@
|
||||
package videos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"gorm.io/gorm"
|
||||
"stream.api/internal/database/model"
|
||||
"stream.api/internal/modules/common"
|
||||
videodomain "stream.api/internal/video"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
runtime *common.Runtime
|
||||
}
|
||||
|
||||
func New(runtime *common.Runtime) *Module {
|
||||
return &Module{runtime: runtime}
|
||||
}
|
||||
|
||||
func (m *Module) GetUploadURL(ctx context.Context, cmd GetUploadURLCommand) (*GetUploadURLResult, error) {
|
||||
storageProvider := m.runtime.StorageProvider()
|
||||
if storageProvider == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "Storage provider is not configured")
|
||||
}
|
||||
filename := strings.TrimSpace(cmd.Filename)
|
||||
if filename == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "Filename is required")
|
||||
}
|
||||
fileID := uuid.New().String()
|
||||
key := fmt.Sprintf("videos/%s/%s-%s", cmd.UserID, fileID, filename)
|
||||
uploadURL, err := storageProvider.GeneratePresignedURL(key, 15*time.Minute)
|
||||
if err != nil {
|
||||
m.runtime.Logger().Error("Failed to generate upload URL", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Storage error")
|
||||
}
|
||||
return &GetUploadURLResult{UploadURL: uploadURL, Key: key, FileID: fileID}, nil
|
||||
}
|
||||
|
||||
func (m *Module) CreateVideo(ctx context.Context, cmd CreateVideoCommand) (*VideoView, error) {
|
||||
videoService := m.runtime.VideoService()
|
||||
if videoService == nil {
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
}
|
||||
title := strings.TrimSpace(cmd.Title)
|
||||
if title == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "Title is required")
|
||||
}
|
||||
videoURL := strings.TrimSpace(cmd.URL)
|
||||
if videoURL == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "URL is required")
|
||||
}
|
||||
description := strings.TrimSpace(cmd.Description)
|
||||
created, err := videoService.CreateVideo(ctx, videodomain.CreateVideoInput{UserID: cmd.UserID, Title: title, Description: &description, URL: videoURL, Size: cmd.Size, Duration: cmd.Duration, Format: strings.TrimSpace(cmd.Format)})
|
||||
if err != nil {
|
||||
m.runtime.Logger().Error("Failed to create video", "error", err)
|
||||
switch {
|
||||
case errors.Is(err, videodomain.ErrJobServiceUnavailable):
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
default:
|
||||
return nil, status.Error(codes.Internal, "Failed to create video")
|
||||
}
|
||||
}
|
||||
jobID := created.Job.ID
|
||||
return &VideoView{Video: created.Video, JobID: &jobID}, nil
|
||||
}
|
||||
|
||||
func (m *Module) ListVideos(ctx context.Context, queryValue ListVideosQuery) (*ListVideosResult, error) {
|
||||
page := queryValue.Page
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
limit := queryValue.Limit
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100
|
||||
}
|
||||
offset := int((page - 1) * limit)
|
||||
db := m.runtime.DB().WithContext(ctx).Model(&model.Video{}).Where("user_id = ?", queryValue.UserID)
|
||||
if search := strings.TrimSpace(queryValue.Search); search != "" {
|
||||
like := "%" + search + "%"
|
||||
db = db.Where("title ILIKE ? OR description ILIKE ?", like, like)
|
||||
}
|
||||
if st := strings.TrimSpace(queryValue.StatusFilter); st != "" && !strings.EqualFold(st, "all") {
|
||||
db = db.Where("status = ?", common.NormalizeVideoStatusValue(st))
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
m.runtime.Logger().Error("Failed to count videos", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to fetch videos")
|
||||
}
|
||||
var videos []model.Video
|
||||
if err := db.Order("created_at DESC").Offset(offset).Limit(int(limit)).Find(&videos).Error; err != nil {
|
||||
m.runtime.Logger().Error("Failed to list videos", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to fetch videos")
|
||||
}
|
||||
items := make([]VideoView, 0, len(videos))
|
||||
for i := range videos {
|
||||
payload, err := m.BuildVideo(ctx, &videos[i])
|
||||
if err != nil {
|
||||
m.runtime.Logger().Error("Failed to build video payload", "error", err, "video_id", videos[i].ID)
|
||||
return nil, status.Error(codes.Internal, "Failed to fetch videos")
|
||||
}
|
||||
items = append(items, payload)
|
||||
}
|
||||
return &ListVideosResult{Items: items, Total: total, Page: page, Limit: limit}, nil
|
||||
}
|
||||
|
||||
func (m *Module) GetVideo(ctx context.Context, queryValue GetVideoQuery) (*VideoView, error) {
|
||||
id := strings.TrimSpace(queryValue.ID)
|
||||
if id == "" {
|
||||
return nil, status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
_ = m.runtime.DB().WithContext(ctx).Model(&model.Video{}).Where("id = ? AND user_id = ?", id, queryValue.UserID).UpdateColumn("views", gorm.Expr("views + ?", 1)).Error
|
||||
var video model.Video
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ? AND user_id = ?", id, queryValue.UserID).First(&video).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
m.runtime.Logger().Error("Failed to fetch video", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to fetch video")
|
||||
}
|
||||
payload, err := m.BuildVideo(ctx, &video)
|
||||
if err != nil {
|
||||
m.runtime.Logger().Error("Failed to build video payload", "error", err, "video_id", video.ID)
|
||||
return nil, status.Error(codes.Internal, "Failed to fetch video")
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func (m *Module) UpdateVideo(ctx context.Context, cmd UpdateVideoCommand) (*VideoView, error) {
|
||||
id := strings.TrimSpace(cmd.ID)
|
||||
if id == "" {
|
||||
return nil, status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
updates := map[string]any{}
|
||||
if title := strings.TrimSpace(cmd.Title); title != "" {
|
||||
updates["name"] = title
|
||||
updates["title"] = title
|
||||
}
|
||||
if cmd.Description != nil {
|
||||
desc := strings.TrimSpace(*cmd.Description)
|
||||
updates["description"] = common.NullableTrimmedString(&desc)
|
||||
}
|
||||
if urlValue := strings.TrimSpace(cmd.URL); urlValue != "" {
|
||||
updates["url"] = urlValue
|
||||
}
|
||||
if cmd.Size > 0 {
|
||||
updates["size"] = cmd.Size
|
||||
}
|
||||
if cmd.Duration > 0 {
|
||||
updates["duration"] = cmd.Duration
|
||||
}
|
||||
if cmd.Format != nil {
|
||||
updates["format"] = strings.TrimSpace(*cmd.Format)
|
||||
}
|
||||
if cmd.Status != nil {
|
||||
updates["status"] = common.NormalizeVideoStatusValue(*cmd.Status)
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "No changes provided")
|
||||
}
|
||||
res := m.runtime.DB().WithContext(ctx).Model(&model.Video{}).Where("id = ? AND user_id = ?", id, cmd.UserID).Updates(updates)
|
||||
if res.Error != nil {
|
||||
m.runtime.Logger().Error("Failed to update video", "error", res.Error)
|
||||
return nil, status.Error(codes.Internal, "Failed to update video")
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil, status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
var video model.Video
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ? AND user_id = ?", id, cmd.UserID).First(&video).Error; err != nil {
|
||||
m.runtime.Logger().Error("Failed to reload video", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to update video")
|
||||
}
|
||||
payload, err := m.BuildVideo(ctx, &video)
|
||||
if err != nil {
|
||||
m.runtime.Logger().Error("Failed to build video payload", "error", err, "video_id", video.ID)
|
||||
return nil, status.Error(codes.Internal, "Failed to update video")
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func (m *Module) DeleteVideo(ctx context.Context, cmd DeleteVideoCommand) error {
|
||||
id := strings.TrimSpace(cmd.ID)
|
||||
if id == "" {
|
||||
return status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
var video model.Video
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ? AND user_id = ?", id, cmd.UserID).First(&video).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
m.runtime.Logger().Error("Failed to load video", "error", err)
|
||||
return status.Error(codes.Internal, "Failed to delete video")
|
||||
}
|
||||
storageProvider := m.runtime.StorageProvider()
|
||||
if storageProvider != nil && common.ShouldDeleteStoredObject(video.URL) {
|
||||
if err := storageProvider.Delete(video.URL); err != nil {
|
||||
if parsedKey := common.ExtractObjectKey(video.URL); parsedKey != "" && parsedKey != video.URL {
|
||||
if deleteErr := storageProvider.Delete(parsedKey); deleteErr != nil {
|
||||
m.runtime.Logger().Error("Failed to delete video object", "error", deleteErr, "video_id", video.ID)
|
||||
return status.Error(codes.Internal, "Failed to delete video")
|
||||
}
|
||||
} else {
|
||||
m.runtime.Logger().Error("Failed to delete video object", "error", err, "video_id", video.ID)
|
||||
return status.Error(codes.Internal, "Failed to delete video")
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("id = ? AND user_id = ?", video.ID, cmd.UserID).Delete(&model.Video{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&model.User{}).Where("id = ?", cmd.UserID).UpdateColumn("storage_used", gorm.Expr("storage_used - ?", video.Size)).Error
|
||||
}); err != nil {
|
||||
m.runtime.Logger().Error("Failed to delete video", "error", err)
|
||||
return status.Error(codes.Internal, "Failed to delete video")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) ListAdminVideos(ctx context.Context, queryValue ListAdminVideosQuery) (*ListAdminVideosResult, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
page, limit, offset := common.AdminPageLimitOffset(queryValue.Page, queryValue.Limit)
|
||||
limitInt := int(limit)
|
||||
db := m.runtime.DB().WithContext(ctx).Model(&model.Video{})
|
||||
if search := strings.TrimSpace(queryValue.Search); search != "" {
|
||||
like := "%" + search + "%"
|
||||
db = db.Where("title ILIKE ?", like)
|
||||
}
|
||||
if userID := strings.TrimSpace(queryValue.UserID); userID != "" {
|
||||
db = db.Where("user_id = ?", userID)
|
||||
}
|
||||
if statusFilter := strings.TrimSpace(queryValue.StatusFilter); statusFilter != "" && !strings.EqualFold(statusFilter, "all") {
|
||||
db = db.Where("status = ?", common.NormalizeVideoStatusValue(statusFilter))
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to list videos")
|
||||
}
|
||||
var videos []model.Video
|
||||
if err := db.Order("created_at DESC").Offset(offset).Limit(limitInt).Find(&videos).Error; err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to list videos")
|
||||
}
|
||||
items := make([]AdminVideoView, 0, len(videos))
|
||||
for i := range videos {
|
||||
payload, err := m.BuildAdminVideo(ctx, &videos[i])
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to list videos")
|
||||
}
|
||||
items = append(items, payload)
|
||||
}
|
||||
return &ListAdminVideosResult{Items: items, Total: total, Page: page, Limit: limit}, nil
|
||||
}
|
||||
|
||||
func (m *Module) GetAdminVideo(ctx context.Context, queryValue GetAdminVideoQuery) (*AdminVideoView, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id := strings.TrimSpace(queryValue.ID)
|
||||
if id == "" {
|
||||
return nil, status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
var video model.Video
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ?", id).First(&video).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "Failed to get video")
|
||||
}
|
||||
payload, err := m.BuildAdminVideo(ctx, &video)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to get video")
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func (m *Module) CreateAdminVideo(ctx context.Context, cmd CreateAdminVideoCommand) (*AdminVideoView, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
videoService := m.runtime.VideoService()
|
||||
if videoService == nil {
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
}
|
||||
userID := strings.TrimSpace(cmd.UserID)
|
||||
title := strings.TrimSpace(cmd.Title)
|
||||
videoURL := strings.TrimSpace(cmd.URL)
|
||||
if userID == "" || title == "" || videoURL == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "User ID, title, and URL are required")
|
||||
}
|
||||
if cmd.Size < 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "Size must be greater than or equal to 0")
|
||||
}
|
||||
created, err := videoService.CreateVideo(ctx, videodomain.CreateVideoInput{UserID: userID, Title: title, Description: cmd.Description, URL: videoURL, Size: cmd.Size, Duration: cmd.Duration, Format: strings.TrimSpace(cmd.Format), AdTemplateID: cmd.AdTemplateID})
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, videodomain.ErrUserNotFound):
|
||||
return nil, status.Error(codes.InvalidArgument, "User not found")
|
||||
case errors.Is(err, videodomain.ErrAdTemplateNotFound):
|
||||
return nil, status.Error(codes.InvalidArgument, "Ad template not found")
|
||||
case errors.Is(err, videodomain.ErrJobServiceUnavailable):
|
||||
return nil, status.Error(codes.Unavailable, "Job service is unavailable")
|
||||
default:
|
||||
return nil, status.Error(codes.Internal, "Failed to create video")
|
||||
}
|
||||
}
|
||||
payload, err := m.BuildAdminVideo(ctx, created.Video)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to create video")
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func (m *Module) UpdateAdminVideo(ctx context.Context, cmd UpdateAdminVideoCommand) (*AdminVideoView, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id := strings.TrimSpace(cmd.ID)
|
||||
userID := strings.TrimSpace(cmd.UserID)
|
||||
title := strings.TrimSpace(cmd.Title)
|
||||
videoURL := strings.TrimSpace(cmd.URL)
|
||||
if id == "" {
|
||||
return nil, status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
if userID == "" || title == "" || videoURL == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "User ID, title, and URL are required")
|
||||
}
|
||||
if cmd.Size < 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "Size must be greater than or equal to 0")
|
||||
}
|
||||
var video model.Video
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ?", id).First(&video).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "Failed to update video")
|
||||
}
|
||||
var user model.User
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ?", userID).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Error(codes.InvalidArgument, "User not found")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "Failed to update video")
|
||||
}
|
||||
oldSize := video.Size
|
||||
oldUserID := video.UserID
|
||||
statusValue := common.NormalizeVideoStatusValue(cmd.Status)
|
||||
processingStatus := strings.ToUpper(statusValue)
|
||||
video.UserID = user.ID
|
||||
video.Name = title
|
||||
video.Title = title
|
||||
video.Description = common.NullableTrimmedString(cmd.Description)
|
||||
video.URL = videoURL
|
||||
video.Size = cmd.Size
|
||||
video.Duration = cmd.Duration
|
||||
video.Format = strings.TrimSpace(cmd.Format)
|
||||
video.Status = model.StringPtr(statusValue)
|
||||
video.ProcessingStatus = model.StringPtr(processingStatus)
|
||||
video.StorageType = model.StringPtr(common.DetectStorageType(videoURL))
|
||||
err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Save(&video).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if oldUserID == user.ID {
|
||||
delta := video.Size - oldSize
|
||||
if delta != 0 {
|
||||
if err := tx.Model(&model.User{}).Where("id = ?", user.ID).UpdateColumn("storage_used", gorm.Expr("GREATEST(storage_used + ?, 0)", delta)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := tx.Model(&model.User{}).Where("id = ?", oldUserID).UpdateColumn("storage_used", gorm.Expr("GREATEST(storage_used - ?, 0)", oldSize)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&model.User{}).Where("id = ?", user.ID).UpdateColumn("storage_used", gorm.Expr("storage_used + ?", video.Size)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return m.saveAdminVideoAdConfig(ctx, tx, &video, user.ID, cmd.AdTemplateID)
|
||||
})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "Ad template not found") {
|
||||
return nil, status.Error(codes.InvalidArgument, "Ad template not found")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "Failed to update video")
|
||||
}
|
||||
payload, err := m.BuildAdminVideo(ctx, &video)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to update video")
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func (m *Module) DeleteAdminVideo(ctx context.Context, cmd DeleteAdminVideoCommand) error {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
id := strings.TrimSpace(cmd.ID)
|
||||
if id == "" {
|
||||
return status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
var video model.Video
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ?", id).First(&video).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return status.Error(codes.NotFound, "Video not found")
|
||||
}
|
||||
return status.Error(codes.Internal, "Failed to find video")
|
||||
}
|
||||
err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("id = ?", video.ID).Delete(&model.Video{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&model.User{}).Where("id = ?", video.UserID).UpdateColumn("storage_used", gorm.Expr("GREATEST(storage_used - ?, 0)", video.Size)).Error
|
||||
})
|
||||
if err != nil {
|
||||
return status.Error(codes.Internal, "Failed to delete video")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) BuildVideo(ctx context.Context, video *model.Video) (VideoView, error) {
|
||||
if video == nil {
|
||||
return VideoView{}, nil
|
||||
}
|
||||
jobID, err := m.LoadLatestVideoJobID(ctx, video.ID)
|
||||
if err != nil {
|
||||
return VideoView{}, err
|
||||
}
|
||||
return VideoView{Video: video, JobID: jobID}, nil
|
||||
}
|
||||
|
||||
func (m *Module) BuildAdminVideo(ctx context.Context, video *model.Video) (AdminVideoView, error) {
|
||||
if video == nil {
|
||||
return AdminVideoView{}, nil
|
||||
}
|
||||
statusValue := common.StringValue(video.Status)
|
||||
if statusValue == "" {
|
||||
statusValue = "ready"
|
||||
}
|
||||
jobID, err := m.LoadLatestVideoJobID(ctx, video.ID)
|
||||
if err != nil {
|
||||
return AdminVideoView{}, err
|
||||
}
|
||||
ownerEmail, err := m.loadAdminUserEmail(ctx, video.UserID)
|
||||
if err != nil {
|
||||
return AdminVideoView{}, err
|
||||
}
|
||||
adTemplateID, adTemplateName, err := m.loadAdminVideoAdTemplateDetails(ctx, video)
|
||||
if err != nil {
|
||||
return AdminVideoView{}, err
|
||||
}
|
||||
var createdAt *string
|
||||
if video.CreatedAt != nil {
|
||||
formatted := video.CreatedAt.UTC().Format(time.RFC3339)
|
||||
createdAt = &formatted
|
||||
}
|
||||
updated := video.UpdatedAt.UTC().Format(time.RFC3339)
|
||||
updatedAt := &updated
|
||||
return AdminVideoView{ID: video.ID, UserID: video.UserID, Title: video.Title, Description: common.NullableTrimmedString(video.Description), URL: video.URL, Status: strings.ToLower(statusValue), Size: video.Size, Duration: video.Duration, Format: video.Format, CreatedAt: createdAt, UpdatedAt: updatedAt, ProcessingStatus: common.NullableTrimmedString(video.ProcessingStatus), JobID: jobID, OwnerEmail: ownerEmail, AdTemplateID: adTemplateID, AdTemplateName: adTemplateName}, nil
|
||||
}
|
||||
|
||||
func (m *Module) LoadLatestVideoJobID(ctx context.Context, videoID string) (*string, error) {
|
||||
videoID = strings.TrimSpace(videoID)
|
||||
if videoID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
var job model.Job
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("config::jsonb ->> 'video_id' = ?", videoID).Order("created_at DESC").First(&job).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return common.StringPointerOrNil(job.ID), nil
|
||||
}
|
||||
|
||||
func (m *Module) saveAdminVideoAdConfig(ctx context.Context, tx *gorm.DB, video *model.Video, userID string, adTemplateID *string) error {
|
||||
if video == nil || adTemplateID == nil {
|
||||
return nil
|
||||
}
|
||||
trimmed := strings.TrimSpace(*adTemplateID)
|
||||
if trimmed == "" {
|
||||
if err := tx.WithContext(ctx).Model(&model.Video{}).Where("id = ?", video.ID).Update("ad_id", nil).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
video.AdID = nil
|
||||
return nil
|
||||
}
|
||||
var template model.AdTemplate
|
||||
if err := tx.WithContext(ctx).Select("id").Where("id = ? AND user_id = ?", trimmed, userID).First(&template).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("Ad template not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := tx.WithContext(ctx).Model(&model.Video{}).Where("id = ?", video.ID).Update("ad_id", template.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
video.AdID = &template.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) loadAdminUserEmail(ctx context.Context, userID string) (*string, error) {
|
||||
var user model.User
|
||||
if err := m.runtime.DB().WithContext(ctx).Select("id, email").Where("id = ?", userID).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return common.NullableTrimmedString(&user.Email), nil
|
||||
}
|
||||
|
||||
func (m *Module) loadAdminVideoAdTemplateDetails(ctx context.Context, video *model.Video) (*string, *string, error) {
|
||||
if video == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
adTemplateID := common.NullableTrimmedString(video.AdID)
|
||||
if adTemplateID == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
adTemplateName, err := m.loadAdminAdTemplateName(ctx, *adTemplateID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return adTemplateID, adTemplateName, nil
|
||||
}
|
||||
|
||||
func (m *Module) loadAdminAdTemplateName(ctx context.Context, adTemplateID string) (*string, error) {
|
||||
var template model.AdTemplate
|
||||
if err := m.runtime.DB().WithContext(ctx).Select("id, name").Where("id = ?", adTemplateID).First(&template).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return common.NullableTrimmedString(&template.Name), nil
|
||||
}
|
||||
Reference in New Issue
Block a user