feat: Implement video workflow repository and related services

- Added videoWorkflowRepository with methods to manage video and user interactions.
- Introduced catalog_mapper for converting database models to protobuf representations.
- Created domain_helpers for normalizing domain and ad format values.
- Defined service interfaces for payment, account, notification, domain, ad template, player config, video, and user management.
- Implemented OAuth helpers for generating state and caching keys.
- Developed payment_proto_helpers for mapping payment-related models to protobuf.
- Added service policy helpers to enforce plan requirements and user permissions.
- Created user_mapper for converting user payloads to protobuf format.
- Implemented value_helpers for handling various value conversions and nil checks.
- Developed video_helpers for normalizing video statuses and managing storage types.
- Created video_mapper for mapping video models to protobuf format.
- Implemented render workflow for managing video creation and job processing.
This commit is contained in:
2026-03-26 18:38:47 +07:00
parent fbbecd7674
commit a0ae2b681a
55 changed files with 3464 additions and 13091 deletions

View File

@@ -7,46 +7,42 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/wrapperspb"
"gorm.io/gorm"
appv1 "stream.api/internal/api/proto/app/v1"
"stream.api/internal/database/model"
"stream.api/internal/database/query"
)
func (s *appServices) GetMe(ctx context.Context, _ *appv1.GetMeRequest) (*appv1.GetMeResponse, error) {
func (s *accountAppService) GetMe(ctx context.Context, _ *appv1.GetMeRequest) (*appv1.GetMeResponse, error) {
result, err := s.authenticate(ctx)
if err != nil {
return nil, err
}
payload, err := buildUserPayload(ctx, s.db, result.User)
payload, err := buildUserPayload(ctx, s.preferenceRepository, s.billingRepository, result.User)
if err != nil {
return nil, status.Error(codes.Internal, "Failed to build user payload")
}
return &appv1.GetMeResponse{User: toProtoUser(payload)}, nil
}
func (s *appServices) GetUserById(ctx context.Context, req *wrapperspb.StringValue) (*appv1.User, error) {
func (s *accountAppService) GetUserById(ctx context.Context, req *wrapperspb.StringValue) (*appv1.User, error) {
_, err := s.authenticator.RequireTrustedMetadata(ctx)
if err != nil {
return nil, err
}
u := query.User
user, err := u.WithContext(ctx).Where(u.ID.Eq(req.Value)).First()
user, err := s.userRepository.GetByID(ctx, req.Value)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "Unauthorized")
}
payload, err := buildUserPayload(ctx, s.db, user)
payload, err := buildUserPayload(ctx, s.preferenceRepository, s.billingRepository, user)
if err != nil {
return nil, status.Error(codes.Internal, "Failed to build user payload")
}
return toProtoUser(payload), nil
}
func (s *appServices) UpdateMe(ctx context.Context, req *appv1.UpdateMeRequest) (*appv1.UpdateMeResponse, error) {
func (s *accountAppService) UpdateMe(ctx context.Context, req *appv1.UpdateMeRequest) (*appv1.UpdateMeResponse, error) {
result, err := s.authenticate(ctx)
if err != nil {
return nil, err
}
updatedUser, err := updateUserProfile(ctx, s.db, s.logger, result.UserID, updateProfileInput{
updatedUser, err := updateUserProfile(ctx, s.userRepository, s.preferenceRepository, s.logger, result.UserID, updateProfileInput{
Username: req.Username,
Email: req.Email,
Language: req.Language,
@@ -61,103 +57,57 @@ func (s *appServices) UpdateMe(ctx context.Context, req *appv1.UpdateMeRequest)
}
}
payload, err := buildUserPayload(ctx, s.db, updatedUser)
payload, err := buildUserPayload(ctx, s.preferenceRepository, s.billingRepository, updatedUser)
if err != nil {
return nil, status.Error(codes.Internal, "Failed to build user payload")
}
return &appv1.UpdateMeResponse{User: toProtoUser(payload)}, nil
}
func (s *appServices) DeleteMe(ctx context.Context, _ *appv1.DeleteMeRequest) (*appv1.MessageResponse, error) {
func (s *accountAppService) DeleteMe(ctx context.Context, _ *appv1.DeleteMeRequest) (*appv1.MessageResponse, error) {
result, err := s.authenticate(ctx)
if err != nil {
return nil, err
}
userID := result.UserID
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Where("user_id = ?", userID).Delete(&model.Notification{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.Domain{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.AdTemplate{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.WalletTransaction{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.PlanSubscription{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.UserPreference{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.Payment{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.Video{}).Error; err != nil {
return err
}
if err := tx.Where("id = ?", userID).Delete(&model.User{}).Error; err != nil {
return err
}
return nil
}); err != nil {
if err := s.accountRepository.DeleteUserAccount(ctx, userID); err != nil {
s.logger.Error("Failed to delete user", "error", err)
return nil, status.Error(codes.Internal, "Failed to delete account")
}
return messageResponse("Account deleted successfully"), nil
}
func (s *appServices) ClearMyData(ctx context.Context, _ *appv1.ClearMyDataRequest) (*appv1.MessageResponse, error) {
func (s *accountAppService) ClearMyData(ctx context.Context, _ *appv1.ClearMyDataRequest) (*appv1.MessageResponse, error) {
result, err := s.authenticate(ctx)
if err != nil {
return nil, err
}
userID := result.UserID
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Where("user_id = ?", userID).Delete(&model.Notification{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.Domain{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.AdTemplate{}).Error; err != nil {
return err
}
if err := tx.Where("user_id = ?", userID).Delete(&model.Video{}).Error; err != nil {
return err
}
if err := tx.Model(&model.User{}).Where("id = ?", userID).Updates(map[string]interface{}{"storage_used": 0}).Error; err != nil {
return err
}
return nil
}); err != nil {
if err := s.accountRepository.ClearUserData(ctx, userID); err != nil {
s.logger.Error("Failed to clear user data", "error", err)
return nil, status.Error(codes.Internal, "Failed to clear data")
}
return messageResponse("Data cleared successfully"), nil
}
func (s *appServices) GetPreferences(ctx context.Context, _ *appv1.GetPreferencesRequest) (*appv1.GetPreferencesResponse, error) {
func (s *accountAppService) GetPreferences(ctx context.Context, _ *appv1.GetPreferencesRequest) (*appv1.GetPreferencesResponse, error) {
result, err := s.authenticate(ctx)
if err != nil {
return nil, err
}
pref, err := loadUserPreferences(ctx, s.db, result.UserID)
pref, err := loadUserPreferences(ctx, s.preferenceRepository, result.UserID)
if err != nil {
return nil, status.Error(codes.Internal, "Failed to load preferences")
}
return &appv1.GetPreferencesResponse{Preferences: toProtoPreferences(pref)}, nil
}
func (s *appServices) UpdatePreferences(ctx context.Context, req *appv1.UpdatePreferencesRequest) (*appv1.UpdatePreferencesResponse, error) {
func (s *accountAppService) UpdatePreferences(ctx context.Context, req *appv1.UpdatePreferencesRequest) (*appv1.UpdatePreferencesResponse, error) {
result, err := s.authenticate(ctx)
if err != nil {
return nil, err
}
pref, err := updateUserPreferences(ctx, s.db, s.logger, result.UserID, updatePreferencesInput{
pref, err := updateUserPreferences(ctx, s.preferenceRepository, s.logger, result.UserID, updatePreferencesInput{
EmailNotifications: req.EmailNotifications,
PushNotifications: req.PushNotifications,
MarketingNotifications: req.MarketingNotifications,
@@ -170,12 +120,12 @@ func (s *appServices) UpdatePreferences(ctx context.Context, req *appv1.UpdatePr
}
return &appv1.UpdatePreferencesResponse{Preferences: toProtoPreferences(pref)}, nil
}
func (s *appServices) GetUsage(ctx context.Context, _ *appv1.GetUsageRequest) (*appv1.GetUsageResponse, error) {
func (s *accountAppService) GetUsage(ctx context.Context, _ *appv1.GetUsageRequest) (*appv1.GetUsageResponse, error) {
result, err := s.authenticate(ctx)
if err != nil {
return nil, err
}
payload, err := loadUsage(ctx, s.db, s.logger, result.User)
payload, err := loadUsage(ctx, s.videoRepository, s.logger, result.User)
if err != nil {
return nil, status.Error(codes.Internal, "Failed to load usage")
}