draft
This commit is contained in:
151
internal/modules/adtemplates/handler.go
Normal file
151
internal/modules/adtemplates/handler.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package adtemplates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
appv1 "stream.api/internal/gen/proto/app/v1"
|
||||
"stream.api/internal/modules/common"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
appv1.UnimplementedAdTemplatesServiceServer
|
||||
module *Module
|
||||
}
|
||||
|
||||
var _ appv1.AdTemplatesServiceServer = (*Handler)(nil)
|
||||
|
||||
func NewHandler(module *Module) *Handler { return &Handler{module: module} }
|
||||
|
||||
func (h *Handler) ListAdTemplates(ctx context.Context, _ *appv1.ListAdTemplatesRequest) (*appv1.ListAdTemplatesResponse, error) {
|
||||
result, err := h.module.runtime.Authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload, err := h.module.ListAdTemplates(ctx, ListAdTemplatesQuery{UserID: result.UserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentListAdTemplatesResponse(payload), nil
|
||||
}
|
||||
|
||||
func (h *Handler) CreateAdTemplate(ctx context.Context, req *appv1.CreateAdTemplateRequest) (*appv1.CreateAdTemplateResponse, error) {
|
||||
result, err := h.module.runtime.Authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := common.EnsurePaidPlan(result.User); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload, err := h.module.CreateAdTemplate(ctx, CreateAdTemplateCommand{
|
||||
UserID: result.UserID,
|
||||
Name: req.GetName(),
|
||||
Description: req.Description,
|
||||
VastTagURL: req.GetVastTagUrl(),
|
||||
AdFormat: req.GetAdFormat(),
|
||||
Duration: req.Duration,
|
||||
IsActive: req.IsActive,
|
||||
IsDefault: req.IsDefault,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentCreateAdTemplateResponse(*payload), nil
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateAdTemplate(ctx context.Context, req *appv1.UpdateAdTemplateRequest) (*appv1.UpdateAdTemplateResponse, error) {
|
||||
result, err := h.module.runtime.Authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := common.EnsurePaidPlan(result.User); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload, err := h.module.UpdateAdTemplate(ctx, UpdateAdTemplateCommand{
|
||||
UserID: result.UserID,
|
||||
ID: strings.TrimSpace(req.GetId()),
|
||||
Name: req.GetName(),
|
||||
Description: req.Description,
|
||||
VastTagURL: req.GetVastTagUrl(),
|
||||
AdFormat: req.GetAdFormat(),
|
||||
Duration: req.Duration,
|
||||
IsActive: req.IsActive,
|
||||
IsDefault: req.IsDefault,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentUpdateAdTemplateResponse(*payload), nil
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteAdTemplate(ctx context.Context, req *appv1.DeleteAdTemplateRequest) (*appv1.MessageResponse, error) {
|
||||
result, err := h.module.runtime.Authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := common.EnsurePaidPlan(result.User); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := h.module.DeleteAdTemplate(ctx, DeleteAdTemplateCommand{UserID: result.UserID, ID: strings.TrimSpace(req.GetId())}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &appv1.MessageResponse{Message: "Ad template deleted"}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) ListAdminAdTemplates(ctx context.Context, req *appv1.ListAdminAdTemplatesRequest) (*appv1.ListAdminAdTemplatesResponse, error) {
|
||||
payload, err := h.module.ListAdminAdTemplates(ctx, ListAdminAdTemplatesQuery{Page: req.GetPage(), Limit: req.GetLimit(), Search: req.Search, UserID: req.UserId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentListAdminAdTemplatesResponse(payload), nil
|
||||
}
|
||||
|
||||
func (h *Handler) GetAdminAdTemplate(ctx context.Context, req *appv1.GetAdminAdTemplateRequest) (*appv1.GetAdminAdTemplateResponse, error) {
|
||||
payload, err := h.module.GetAdminAdTemplate(ctx, GetAdminAdTemplateQuery{ID: strings.TrimSpace(req.GetId())})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentGetAdminAdTemplateResponse(*payload), nil
|
||||
}
|
||||
|
||||
func (h *Handler) CreateAdminAdTemplate(ctx context.Context, req *appv1.CreateAdminAdTemplateRequest) (*appv1.CreateAdminAdTemplateResponse, error) {
|
||||
payload, err := h.module.CreateAdminAdTemplate(ctx, CreateAdminAdTemplateCommand{
|
||||
UserID: strings.TrimSpace(req.GetUserId()),
|
||||
Name: req.GetName(),
|
||||
Description: req.Description,
|
||||
VastTagURL: req.GetVastTagUrl(),
|
||||
AdFormat: req.GetAdFormat(),
|
||||
Duration: req.Duration,
|
||||
IsActive: req.GetIsActive(),
|
||||
IsDefault: req.GetIsDefault(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentCreateAdminAdTemplateResponse(*payload), nil
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateAdminAdTemplate(ctx context.Context, req *appv1.UpdateAdminAdTemplateRequest) (*appv1.UpdateAdminAdTemplateResponse, error) {
|
||||
payload, err := h.module.UpdateAdminAdTemplate(ctx, UpdateAdminAdTemplateCommand{
|
||||
ID: strings.TrimSpace(req.GetId()),
|
||||
UserID: strings.TrimSpace(req.GetUserId()),
|
||||
Name: req.GetName(),
|
||||
Description: req.Description,
|
||||
VastTagURL: req.GetVastTagUrl(),
|
||||
AdFormat: req.GetAdFormat(),
|
||||
Duration: req.Duration,
|
||||
IsActive: req.GetIsActive(),
|
||||
IsDefault: req.GetIsDefault(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return presentUpdateAdminAdTemplateResponse(*payload), nil
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteAdminAdTemplate(ctx context.Context, req *appv1.DeleteAdminAdTemplateRequest) (*appv1.MessageResponse, error) {
|
||||
if err := h.module.DeleteAdminAdTemplate(ctx, DeleteAdminAdTemplateCommand{ID: strings.TrimSpace(req.GetId())}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &appv1.MessageResponse{Message: "Ad template deleted"}, nil
|
||||
}
|
||||
364
internal/modules/adtemplates/module.go
Normal file
364
internal/modules/adtemplates/module.go
Normal file
@@ -0,0 +1,364 @@
|
||||
package adtemplates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
runtime *common.Runtime
|
||||
}
|
||||
|
||||
func New(runtime *common.Runtime) *Module {
|
||||
return &Module{runtime: runtime}
|
||||
}
|
||||
|
||||
func (m *Module) ListAdTemplates(ctx context.Context, queryValue ListAdTemplatesQuery) (*ListAdTemplatesResult, error) {
|
||||
var items []model.AdTemplate
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("user_id = ?", queryValue.UserID).Order("is_default DESC").Order("created_at DESC").Find(&items).Error; err != nil {
|
||||
m.runtime.Logger().Error("Failed to list ad templates", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to load ad templates")
|
||||
}
|
||||
result := &ListAdTemplatesResult{Items: make([]AdTemplateView, 0, len(items))}
|
||||
for i := range items {
|
||||
result.Items = append(result.Items, AdTemplateView{Template: &items[i]})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *Module) CreateAdTemplate(ctx context.Context, cmd CreateAdTemplateCommand) (*AdTemplateView, error) {
|
||||
name := strings.TrimSpace(cmd.Name)
|
||||
vastURL := strings.TrimSpace(cmd.VastTagURL)
|
||||
if name == "" || vastURL == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "Name and VAST URL are required")
|
||||
}
|
||||
format := common.NormalizeAdFormat(cmd.AdFormat)
|
||||
if format == "mid-roll" && (cmd.Duration == nil || *cmd.Duration <= 0) {
|
||||
return nil, status.Error(codes.InvalidArgument, "Duration is required for mid-roll templates")
|
||||
}
|
||||
item := &model.AdTemplate{
|
||||
ID: uuid.New().String(),
|
||||
UserID: cmd.UserID,
|
||||
Name: name,
|
||||
Description: common.NullableTrimmedString(cmd.Description),
|
||||
VastTagURL: vastURL,
|
||||
AdFormat: model.StringPtr(format),
|
||||
Duration: common.Int32PtrToInt64Ptr(cmd.Duration),
|
||||
IsActive: model.BoolPtr(cmd.IsActive == nil || *cmd.IsActive),
|
||||
IsDefault: cmd.IsDefault != nil && *cmd.IsDefault,
|
||||
}
|
||||
if !common.AdTemplateIsActive(item.IsActive) {
|
||||
item.IsDefault = false
|
||||
}
|
||||
if err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if item.IsDefault {
|
||||
if err := common.UnsetDefaultTemplates(tx, cmd.UserID, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Create(item).Error
|
||||
}); err != nil {
|
||||
m.runtime.Logger().Error("Failed to create ad template", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to save ad template")
|
||||
}
|
||||
return &AdTemplateView{Template: item}, nil
|
||||
}
|
||||
|
||||
func (m *Module) UpdateAdTemplate(ctx context.Context, cmd UpdateAdTemplateCommand) (*AdTemplateView, error) {
|
||||
if cmd.ID == "" {
|
||||
return nil, status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
name := strings.TrimSpace(cmd.Name)
|
||||
vastURL := strings.TrimSpace(cmd.VastTagURL)
|
||||
if name == "" || vastURL == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "Name and VAST URL are required")
|
||||
}
|
||||
format := common.NormalizeAdFormat(cmd.AdFormat)
|
||||
if format == "mid-roll" && (cmd.Duration == nil || *cmd.Duration <= 0) {
|
||||
return nil, status.Error(codes.InvalidArgument, "Duration is required for mid-roll templates")
|
||||
}
|
||||
var item model.AdTemplate
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ? AND user_id = ?", cmd.ID, cmd.UserID).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
m.runtime.Logger().Error("Failed to load ad template", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to save ad template")
|
||||
}
|
||||
item.Name = name
|
||||
item.Description = common.NullableTrimmedString(cmd.Description)
|
||||
item.VastTagURL = vastURL
|
||||
item.AdFormat = model.StringPtr(format)
|
||||
item.Duration = common.Int32PtrToInt64Ptr(cmd.Duration)
|
||||
if cmd.IsActive != nil {
|
||||
item.IsActive = model.BoolPtr(*cmd.IsActive)
|
||||
}
|
||||
if cmd.IsDefault != nil {
|
||||
item.IsDefault = *cmd.IsDefault
|
||||
}
|
||||
if !common.AdTemplateIsActive(item.IsActive) {
|
||||
item.IsDefault = false
|
||||
}
|
||||
if err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if item.IsDefault {
|
||||
if err := common.UnsetDefaultTemplates(tx, cmd.UserID, item.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Save(&item).Error
|
||||
}); err != nil {
|
||||
m.runtime.Logger().Error("Failed to update ad template", "error", err)
|
||||
return nil, status.Error(codes.Internal, "Failed to save ad template")
|
||||
}
|
||||
return &AdTemplateView{Template: &item}, nil
|
||||
}
|
||||
|
||||
func (m *Module) DeleteAdTemplate(ctx context.Context, cmd DeleteAdTemplateCommand) error {
|
||||
if cmd.ID == "" {
|
||||
return status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
if err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.Video{}).Where("user_id = ? AND ad_id = ?", cmd.UserID, cmd.ID).Update("ad_id", nil).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
res := tx.Where("id = ? AND user_id = ?", cmd.ID, cmd.UserID).Delete(&model.AdTemplate{})
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
m.runtime.Logger().Error("Failed to delete ad template", "error", err)
|
||||
return status.Error(codes.Internal, "Failed to delete ad template")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) ListAdminAdTemplates(ctx context.Context, queryValue ListAdminAdTemplatesQuery) (*ListAdminAdTemplatesResult, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
page, limit, offset := common.AdminPageLimitOffset(queryValue.Page, queryValue.Limit)
|
||||
limitInt := int(limit)
|
||||
search := strings.TrimSpace(common.ProtoStringValue(queryValue.Search))
|
||||
userID := strings.TrimSpace(common.ProtoStringValue(queryValue.UserID))
|
||||
db := m.runtime.DB().WithContext(ctx).Model(&model.AdTemplate{})
|
||||
if search != "" {
|
||||
like := "%" + search + "%"
|
||||
db = db.Where("name ILIKE ?", like)
|
||||
}
|
||||
if userID != "" {
|
||||
db = db.Where("user_id = ?", userID)
|
||||
}
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to list ad templates")
|
||||
}
|
||||
var templates []model.AdTemplate
|
||||
if err := db.Order("created_at DESC").Offset(offset).Limit(limitInt).Find(&templates).Error; err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to list ad templates")
|
||||
}
|
||||
items := make([]AdminAdTemplateView, 0, len(templates))
|
||||
for i := range templates {
|
||||
view, err := m.buildAdminAdTemplate(ctx, &templates[i])
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to list ad templates")
|
||||
}
|
||||
items = append(items, view)
|
||||
}
|
||||
return &ListAdminAdTemplatesResult{Items: items, Total: total, Page: page, Limit: limit}, nil
|
||||
}
|
||||
|
||||
func (m *Module) GetAdminAdTemplate(ctx context.Context, queryValue GetAdminAdTemplateQuery) (*AdminAdTemplateView, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if queryValue.ID == "" {
|
||||
return nil, status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
var item model.AdTemplate
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ?", queryValue.ID).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "Failed to load ad template")
|
||||
}
|
||||
payload, err := m.buildAdminAdTemplate(ctx, &item)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to load ad template")
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func (m *Module) CreateAdminAdTemplate(ctx context.Context, cmd CreateAdminAdTemplateCommand) (*AdminAdTemplateView, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg := validateAdminAdTemplateInput(cmd.UserID, cmd.Name, cmd.VastTagURL, cmd.AdFormat, cmd.Duration); msg != "" {
|
||||
return nil, status.Error(codes.InvalidArgument, msg)
|
||||
}
|
||||
var user model.User
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ?", strings.TrimSpace(cmd.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 save ad template")
|
||||
}
|
||||
item := &model.AdTemplate{ID: uuid.New().String(), UserID: user.ID, Name: strings.TrimSpace(cmd.Name), Description: common.NullableTrimmedStringPtr(cmd.Description), VastTagURL: strings.TrimSpace(cmd.VastTagURL), AdFormat: model.StringPtr(common.NormalizeAdFormat(cmd.AdFormat)), Duration: cmd.Duration, IsActive: model.BoolPtr(cmd.IsActive), IsDefault: cmd.IsDefault}
|
||||
if !common.BoolValue(item.IsActive) {
|
||||
item.IsDefault = false
|
||||
}
|
||||
if err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if item.IsDefault {
|
||||
if err := common.UnsetDefaultTemplates(tx, item.UserID, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Create(item).Error
|
||||
}); err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to save ad template")
|
||||
}
|
||||
payload, err := m.buildAdminAdTemplate(ctx, item)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to save ad template")
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func (m *Module) UpdateAdminAdTemplate(ctx context.Context, cmd UpdateAdminAdTemplateCommand) (*AdminAdTemplateView, error) {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cmd.ID == "" {
|
||||
return nil, status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
if msg := validateAdminAdTemplateInput(cmd.UserID, cmd.Name, cmd.VastTagURL, cmd.AdFormat, cmd.Duration); msg != "" {
|
||||
return nil, status.Error(codes.InvalidArgument, msg)
|
||||
}
|
||||
var user model.User
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ?", strings.TrimSpace(cmd.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 save ad template")
|
||||
}
|
||||
var item model.AdTemplate
|
||||
if err := m.runtime.DB().WithContext(ctx).Where("id = ?", cmd.ID).First(&item).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
return nil, status.Error(codes.Internal, "Failed to save ad template")
|
||||
}
|
||||
item.UserID = user.ID
|
||||
item.Name = strings.TrimSpace(cmd.Name)
|
||||
item.Description = common.NullableTrimmedStringPtr(cmd.Description)
|
||||
item.VastTagURL = strings.TrimSpace(cmd.VastTagURL)
|
||||
item.AdFormat = model.StringPtr(common.NormalizeAdFormat(cmd.AdFormat))
|
||||
item.Duration = cmd.Duration
|
||||
item.IsActive = model.BoolPtr(cmd.IsActive)
|
||||
item.IsDefault = cmd.IsDefault
|
||||
if !common.BoolValue(item.IsActive) {
|
||||
item.IsDefault = false
|
||||
}
|
||||
if err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if item.IsDefault {
|
||||
if err := common.UnsetDefaultTemplates(tx, item.UserID, item.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Save(&item).Error
|
||||
}); err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to save ad template")
|
||||
}
|
||||
payload, err := m.buildAdminAdTemplate(ctx, &item)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, "Failed to save ad template")
|
||||
}
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func (m *Module) DeleteAdminAdTemplate(ctx context.Context, cmd DeleteAdminAdTemplateCommand) error {
|
||||
if _, err := m.runtime.RequireAdmin(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.ID == "" {
|
||||
return status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
err := m.runtime.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&model.Video{}).Where("ad_id = ?", cmd.ID).Update("ad_id", nil).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
res := tx.Where("id = ?", cmd.ID).Delete(&model.AdTemplate{})
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return status.Error(codes.NotFound, "Ad template not found")
|
||||
}
|
||||
return status.Error(codes.Internal, "Failed to delete ad template")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) buildAdminAdTemplate(ctx context.Context, item *model.AdTemplate) (AdminAdTemplateView, error) {
|
||||
if item == nil {
|
||||
return AdminAdTemplateView{}, nil
|
||||
}
|
||||
var createdAt *string
|
||||
if item.CreatedAt != nil {
|
||||
formatted := item.CreatedAt.UTC().Format(time.RFC3339)
|
||||
createdAt = &formatted
|
||||
}
|
||||
updated := item.UpdatedAt.UTC().Format(time.RFC3339)
|
||||
updatedAt := &updated
|
||||
ownerEmail, err := m.loadAdminUserEmail(ctx, item.UserID)
|
||||
if err != nil {
|
||||
return AdminAdTemplateView{}, err
|
||||
}
|
||||
return AdminAdTemplateView{ID: item.ID, UserID: item.UserID, Name: item.Name, Description: common.NullableTrimmedString(item.Description), VastTagURL: item.VastTagURL, AdFormat: common.StringValue(item.AdFormat), Duration: item.Duration, IsActive: common.BoolValue(item.IsActive), IsDefault: item.IsDefault, OwnerEmail: ownerEmail, CreatedAt: createdAt, UpdatedAt: updatedAt}, 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 validateAdminAdTemplateInput(userID, name, vastTagURL, adFormat string, duration *int64) string {
|
||||
if strings.TrimSpace(userID) == "" {
|
||||
return "User ID is required"
|
||||
}
|
||||
if strings.TrimSpace(name) == "" || strings.TrimSpace(vastTagURL) == "" {
|
||||
return "Name and VAST URL are required"
|
||||
}
|
||||
format := common.NormalizeAdFormat(adFormat)
|
||||
if format == "mid-roll" && (duration == nil || *duration <= 0) {
|
||||
return "Duration is required for mid-roll templates"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
85
internal/modules/adtemplates/presenter.go
Normal file
85
internal/modules/adtemplates/presenter.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package adtemplates
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
appv1 "stream.api/internal/gen/proto/app/v1"
|
||||
"stream.api/internal/modules/common"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func presentAdTemplate(view AdTemplateView) *appv1.AdTemplate {
|
||||
return common.ToProtoAdTemplate(view.Template)
|
||||
}
|
||||
|
||||
func presentListAdTemplatesResponse(result *ListAdTemplatesResult) *appv1.ListAdTemplatesResponse {
|
||||
items := make([]*appv1.AdTemplate, 0, len(result.Items))
|
||||
for _, item := range result.Items {
|
||||
items = append(items, presentAdTemplate(item))
|
||||
}
|
||||
return &appv1.ListAdTemplatesResponse{Templates: items}
|
||||
}
|
||||
|
||||
func presentCreateAdTemplateResponse(view AdTemplateView) *appv1.CreateAdTemplateResponse {
|
||||
return &appv1.CreateAdTemplateResponse{Template: presentAdTemplate(view)}
|
||||
}
|
||||
|
||||
func presentUpdateAdTemplateResponse(view AdTemplateView) *appv1.UpdateAdTemplateResponse {
|
||||
return &appv1.UpdateAdTemplateResponse{Template: presentAdTemplate(view)}
|
||||
}
|
||||
|
||||
func presentAdminAdTemplate(view AdminAdTemplateView) *appv1.AdminAdTemplate {
|
||||
return &appv1.AdminAdTemplate{
|
||||
Id: view.ID,
|
||||
UserId: view.UserID,
|
||||
Name: view.Name,
|
||||
Description: view.Description,
|
||||
VastTagUrl: view.VastTagURL,
|
||||
AdFormat: view.AdFormat,
|
||||
Duration: view.Duration,
|
||||
IsActive: view.IsActive,
|
||||
IsDefault: view.IsDefault,
|
||||
OwnerEmail: view.OwnerEmail,
|
||||
CreatedAt: parseRFC3339ToProto(view.CreatedAt),
|
||||
UpdatedAt: parseRFC3339ToProto(view.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func presentListAdminAdTemplatesResponse(result *ListAdminAdTemplatesResult) *appv1.ListAdminAdTemplatesResponse {
|
||||
items := make([]*appv1.AdminAdTemplate, 0, len(result.Items))
|
||||
for _, item := range result.Items {
|
||||
items = append(items, presentAdminAdTemplate(item))
|
||||
}
|
||||
return &appv1.ListAdminAdTemplatesResponse{Templates: items, Total: result.Total, Page: result.Page, Limit: result.Limit}
|
||||
}
|
||||
|
||||
func presentGetAdminAdTemplateResponse(view AdminAdTemplateView) *appv1.GetAdminAdTemplateResponse {
|
||||
return &appv1.GetAdminAdTemplateResponse{Template: presentAdminAdTemplate(view)}
|
||||
}
|
||||
|
||||
func presentCreateAdminAdTemplateResponse(view AdminAdTemplateView) *appv1.CreateAdminAdTemplateResponse {
|
||||
return &appv1.CreateAdminAdTemplateResponse{Template: presentAdminAdTemplate(view)}
|
||||
}
|
||||
|
||||
func presentUpdateAdminAdTemplateResponse(view AdminAdTemplateView) *appv1.UpdateAdminAdTemplateResponse {
|
||||
return &appv1.UpdateAdminAdTemplateResponse{Template: presentAdminAdTemplate(view)}
|
||||
}
|
||||
|
||||
func parseRFC3339ToProto(value *string) *timestamppb.Timestamp {
|
||||
if value == nil || *value == "" {
|
||||
return nil
|
||||
}
|
||||
parsed, err := time.Parse(time.RFC3339, *value)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return timestamppb.New(parsed.UTC())
|
||||
}
|
||||
|
||||
func parseRFC3339ToTimePointer(value *time.Time) *string {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
formatted := value.UTC().Format(time.RFC3339)
|
||||
return &formatted
|
||||
}
|
||||
103
internal/modules/adtemplates/types.go
Normal file
103
internal/modules/adtemplates/types.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package adtemplates
|
||||
|
||||
import "stream.api/internal/database/model"
|
||||
|
||||
type AdTemplateView struct {
|
||||
Template *model.AdTemplate
|
||||
}
|
||||
|
||||
type ListAdTemplatesQuery struct {
|
||||
UserID string
|
||||
}
|
||||
|
||||
type ListAdTemplatesResult struct {
|
||||
Items []AdTemplateView
|
||||
}
|
||||
|
||||
type CreateAdTemplateCommand struct {
|
||||
UserID string
|
||||
Name string
|
||||
Description *string
|
||||
VastTagURL string
|
||||
AdFormat string
|
||||
Duration *int32
|
||||
IsActive *bool
|
||||
IsDefault *bool
|
||||
}
|
||||
|
||||
type UpdateAdTemplateCommand struct {
|
||||
UserID string
|
||||
ID string
|
||||
Name string
|
||||
Description *string
|
||||
VastTagURL string
|
||||
AdFormat string
|
||||
Duration *int32
|
||||
IsActive *bool
|
||||
IsDefault *bool
|
||||
}
|
||||
|
||||
type DeleteAdTemplateCommand struct {
|
||||
UserID string
|
||||
ID string
|
||||
}
|
||||
|
||||
type AdminAdTemplateView struct {
|
||||
ID string
|
||||
UserID string
|
||||
Name string
|
||||
Description *string
|
||||
VastTagURL string
|
||||
AdFormat string
|
||||
Duration *int64
|
||||
IsActive bool
|
||||
IsDefault bool
|
||||
OwnerEmail *string
|
||||
CreatedAt *string
|
||||
UpdatedAt *string
|
||||
}
|
||||
|
||||
type ListAdminAdTemplatesQuery struct {
|
||||
Page int32
|
||||
Limit int32
|
||||
Search *string
|
||||
UserID *string
|
||||
}
|
||||
|
||||
type ListAdminAdTemplatesResult struct {
|
||||
Items []AdminAdTemplateView
|
||||
Total int64
|
||||
Page int32
|
||||
Limit int32
|
||||
}
|
||||
|
||||
type GetAdminAdTemplateQuery struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type CreateAdminAdTemplateCommand struct {
|
||||
UserID string
|
||||
Name string
|
||||
Description *string
|
||||
VastTagURL string
|
||||
AdFormat string
|
||||
Duration *int64
|
||||
IsActive bool
|
||||
IsDefault bool
|
||||
}
|
||||
|
||||
type UpdateAdminAdTemplateCommand struct {
|
||||
ID string
|
||||
UserID string
|
||||
Name string
|
||||
Description *string
|
||||
VastTagURL string
|
||||
AdFormat string
|
||||
Duration *int64
|
||||
IsActive bool
|
||||
IsDefault bool
|
||||
}
|
||||
|
||||
type DeleteAdminAdTemplateCommand struct {
|
||||
ID string
|
||||
}
|
||||
Reference in New Issue
Block a user