747 lines
22 KiB
Go
747 lines
22 KiB
Go
package services
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
"mime/multipart"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"veza-backend-api/internal/models"
|
|
"veza-backend-api/internal/types"
|
|
"veza-backend-api/internal/utils"
|
|
)
|
|
|
|
// UserRepository defines the interface for user repository operations
|
|
type UserRepository interface {
|
|
GetByID(id string) (*models.User, error)
|
|
GetByEmail(email string) (*models.User, error)
|
|
GetByUsername(username string) (*models.User, error)
|
|
Create(user *models.User) error
|
|
Update(user *models.User) error
|
|
Delete(id string) error
|
|
}
|
|
|
|
// UserService gère les opérations sur les utilisateurs
|
|
type UserService struct {
|
|
userRepo UserRepository
|
|
db *gorm.DB // Optional DB access for settings
|
|
}
|
|
|
|
// UpdateProfileRequest represents profile update data
|
|
type UpdateProfileRequest struct {
|
|
FirstName *string `json:"first_name"`
|
|
LastName *string `json:"last_name"`
|
|
Username *string `json:"username"`
|
|
Bio *string `json:"bio"`
|
|
Location *string `json:"location"`
|
|
BirthDate *string `json:"birth_date"`
|
|
Gender *string `json:"gender"`
|
|
Timezone *string `json:"timezone"`
|
|
SocialLinks map[string]interface{} `json:"social_links"`
|
|
WebsiteURL *string `json:"website_url"`
|
|
ProfilePrivacy *string `json:"profile_privacy"`
|
|
}
|
|
|
|
// Profile represents a user profile with necessary fields
|
|
// MIGRATION UUID: ID et UserID migrés vers uuid.UUID
|
|
type Profile struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
Username string `json:"username"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
AvatarURL *string `json:"avatar_url"`
|
|
Bio *string `json:"bio"`
|
|
Location *string `json:"location"`
|
|
Birthdate *string `json:"birthdate"`
|
|
Gender *string `json:"gender"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// UserStats est maintenant défini dans internal/types/stats.go
|
|
// Import: veza-backend-api/internal/types
|
|
|
|
// ProfileCompletion represents profile completion status
|
|
type ProfileCompletion struct {
|
|
Percentage int `json:"percentage"`
|
|
Missing []string `json:"missing"`
|
|
}
|
|
|
|
// NewUserService crée une nouvelle instance d'UserService
|
|
func NewUserService(userRepo UserRepository) *UserService {
|
|
return &UserService{
|
|
userRepo: userRepo,
|
|
}
|
|
}
|
|
|
|
// NewUserServiceWithDB crée une nouvelle instance d'UserService avec accès DB
|
|
func NewUserServiceWithDB(userRepo UserRepository, db *gorm.DB) *UserService {
|
|
return &UserService{
|
|
userRepo: userRepo,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// GetProfileByString récupère le profil d'un utilisateur par ID string (legacy method)
|
|
func (s *UserService) GetProfileByString(userID string) (*models.User, error) {
|
|
user, err := s.userRepo.GetByID(userID)
|
|
if err != nil {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
|
|
// PasswordHash est déjà exclu avec json:"-"
|
|
return user, nil
|
|
}
|
|
|
|
// UpdateProfile met à jour le profil d'un utilisateur
|
|
// UpdateProfileLegacy updates user profile using a map (legacy method, kept for backward compatibility)
|
|
// DEPRECATED: Use UpdateProfile(userID uuid.UUID, req types.UpdateProfileRequest) instead
|
|
func (s *UserService) UpdateProfileLegacy(userID string, updates map[string]interface{}) (*models.User, error) {
|
|
user, err := s.userRepo.GetByID(userID)
|
|
if err != nil {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
|
|
// Appliquer les mises à jour
|
|
if username, ok := updates["username"].(string); ok {
|
|
user.Username = username
|
|
}
|
|
if email, ok := updates["email"].(string); ok {
|
|
user.Email = email
|
|
}
|
|
|
|
// Sauvegarder les modifications
|
|
err = s.userRepo.Update(user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// PasswordHash est déjà exclu avec json:"-"
|
|
return user, nil
|
|
}
|
|
|
|
// GetByID retrieves a user by ID
|
|
func (s *UserService) GetByID(userID uuid.UUID) (*models.User, error) {
|
|
return s.userRepo.GetByID(userID.String())
|
|
}
|
|
|
|
// GetProfileByID retrieves a user profile by ID (alias for GetByID for clarity)
|
|
func (s *UserService) GetProfileByID(userID uuid.UUID) (*models.User, error) {
|
|
return s.GetByID(userID)
|
|
}
|
|
|
|
// GetByUsername retrieves a user by username
|
|
func (s *UserService) GetByUsername(username string) (*models.User, error) {
|
|
return s.userRepo.GetByUsername(username)
|
|
}
|
|
|
|
// UpdateProfileWithRequest updates user profile with new request structure
|
|
func (s *UserService) UpdateProfileWithRequest(userID uuid.UUID, req *UpdateProfileRequest) (*models.User, error) {
|
|
user, err := s.userRepo.GetByID(userID.String())
|
|
if err != nil {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
|
|
// Apply updates
|
|
if req.Bio != nil {
|
|
user.Bio = *req.Bio
|
|
}
|
|
// Add more field updates as needed
|
|
|
|
// Save changes
|
|
err = s.userRepo.Update(user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// GetProfile retrieves a user profile by ID
|
|
// requesterID can be nil for unauthenticated requests
|
|
// If profile is private and requesterID is different from userID, returns limited fields
|
|
// MIGRATION UUID: requesterID migré vers *uuid.UUID
|
|
func (s *UserService) GetProfile(userID uuid.UUID, requesterID *uuid.UUID) (*Profile, error) {
|
|
user, err := s.userRepo.GetByID(userID.String())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
|
|
profile := s.userToProfile(user)
|
|
|
|
// If profile is private and requester is different from owner, limit fields
|
|
if !user.IsPublic && (requesterID == nil || *requesterID != userID) {
|
|
profile.Bio = nil
|
|
profile.Location = nil
|
|
profile.Birthdate = nil
|
|
profile.Gender = nil
|
|
}
|
|
|
|
return profile, nil
|
|
}
|
|
|
|
// GetProfileByUsername retrieves a user profile by username
|
|
// requesterID can be nil for unauthenticated requests
|
|
// If profile is private and requesterID is different from userID, returns limited fields
|
|
// MIGRATION UUID: requesterID migré vers *uuid.UUID
|
|
func (s *UserService) GetProfileByUsername(username string, requesterID *uuid.UUID) (*Profile, error) {
|
|
user, err := s.userRepo.GetByUsername(username)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
|
|
profile := s.userToProfile(user)
|
|
|
|
// If profile is private and requester is different from owner, limit fields
|
|
if !user.IsPublic && (requesterID == nil || *requesterID != user.ID) {
|
|
profile.Bio = nil
|
|
profile.Location = nil
|
|
profile.Birthdate = nil
|
|
profile.Gender = nil
|
|
}
|
|
|
|
return profile, nil
|
|
}
|
|
|
|
// UpdateProfile updates a user profile and returns the updated profile
|
|
func (s *UserService) UpdateProfile(userID uuid.UUID, req types.UpdateProfileRequest) (*Profile, error) {
|
|
user, err := s.userRepo.GetByID(userID.String())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
|
|
// Build updates map dynamically based on provided fields
|
|
updates := make(map[string]interface{})
|
|
|
|
if req.FirstName != nil && *req.FirstName != "" {
|
|
updates["first_name"] = *req.FirstName
|
|
}
|
|
if req.LastName != nil && *req.LastName != "" {
|
|
updates["last_name"] = *req.LastName
|
|
}
|
|
if req.Username != nil && *req.Username != "" {
|
|
updates["username"] = *req.Username
|
|
// Set username_changed_at when username changes
|
|
now := time.Now()
|
|
updates["username_changed_at"] = &now
|
|
// T0219: Generate and update slug when username changes
|
|
slug := utils.Slugify(*req.Username)
|
|
// Simplified: let the database handle uniqueness via unique constraint
|
|
updates["slug"] = slug
|
|
}
|
|
if req.Bio != nil && *req.Bio != "" {
|
|
updates["bio"] = *req.Bio
|
|
}
|
|
if req.Location != nil && *req.Location != "" {
|
|
updates["location"] = *req.Location
|
|
}
|
|
if req.BirthDate != nil && *req.BirthDate != "" {
|
|
birthdate, err := time.Parse("2006-01-02", *req.BirthDate)
|
|
if err == nil {
|
|
updates["birthdate"] = &birthdate
|
|
}
|
|
}
|
|
if req.Gender != nil && *req.Gender != "" {
|
|
updates["gender"] = *req.Gender
|
|
}
|
|
|
|
// Apply updates to user object
|
|
if firstname, ok := updates["first_name"].(string); ok {
|
|
user.FirstName = firstname
|
|
}
|
|
if lastname, ok := updates["last_name"].(string); ok {
|
|
user.LastName = lastname
|
|
}
|
|
if username, ok := updates["username"].(string); ok {
|
|
user.Username = username
|
|
}
|
|
if slug, ok := updates["slug"].(string); ok {
|
|
user.Slug = slug
|
|
}
|
|
if usernameChangedAt, ok := updates["username_changed_at"].(*time.Time); ok {
|
|
user.UsernameChangedAt = usernameChangedAt
|
|
}
|
|
if bio, ok := updates["bio"].(string); ok {
|
|
user.Bio = bio
|
|
}
|
|
if location, ok := updates["location"].(string); ok {
|
|
user.Location = location
|
|
}
|
|
if birthdate, ok := updates["birthdate"].(*time.Time); ok {
|
|
user.Birthdate = birthdate
|
|
}
|
|
if gender, ok := updates["gender"].(string); ok {
|
|
user.Gender = gender
|
|
}
|
|
|
|
// Save changes
|
|
err = s.userRepo.Update(user)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to update profile: %w", err)
|
|
}
|
|
|
|
// Return updated profile
|
|
return s.userToProfile(user), nil
|
|
}
|
|
|
|
// userToProfile converts a models.User to a Profile struct
|
|
func (s *UserService) userToProfile(user *models.User) *Profile {
|
|
var avatarURL *string
|
|
if user.Avatar != "" {
|
|
avatarURL = &user.Avatar
|
|
}
|
|
|
|
var bio *string
|
|
if user.Bio != "" {
|
|
bio = &user.Bio
|
|
}
|
|
|
|
var location *string
|
|
if user.Location != "" {
|
|
location = &user.Location
|
|
}
|
|
|
|
var birthdate *string
|
|
if user.Birthdate != nil {
|
|
birthdateStr := user.Birthdate.Format("2006-01-02")
|
|
birthdate = &birthdateStr
|
|
}
|
|
|
|
var gender *string
|
|
if user.Gender != "" {
|
|
gender = &user.Gender
|
|
}
|
|
|
|
return &Profile{
|
|
ID: user.ID,
|
|
UserID: user.ID,
|
|
Username: user.Username,
|
|
FirstName: user.FirstName,
|
|
LastName: user.LastName,
|
|
AvatarURL: avatarURL,
|
|
Bio: bio,
|
|
Location: location,
|
|
Birthdate: birthdate,
|
|
Gender: gender,
|
|
CreatedAt: user.CreatedAt,
|
|
}
|
|
}
|
|
|
|
// UploadAvatar handles avatar file upload
|
|
func (s *UserService) UploadAvatar(userID uuid.UUID, file *multipart.FileHeader) (string, error) {
|
|
// Create uploads directory if it doesn't exist
|
|
uploadDir := "uploads/avatars"
|
|
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
|
return "", fmt.Errorf("failed to create upload directory: %w", err)
|
|
}
|
|
|
|
// Generate unique filename
|
|
filename := fmt.Sprintf("%s_%s%s", userID.String(), uuid.New().String(), filepath.Ext(file.Filename))
|
|
filePath := filepath.Join(uploadDir, filename)
|
|
|
|
// Save file
|
|
src, err := file.Open()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer src.Close()
|
|
|
|
dst, err := os.Create(filePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer dst.Close()
|
|
|
|
if _, err := dst.ReadFrom(src); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Return URL
|
|
avatarURL := fmt.Sprintf("/uploads/avatars/%s", filename)
|
|
return avatarURL, nil
|
|
}
|
|
|
|
// UpdateAvatarURL updates the avatar URL for a user
|
|
// T0221: Updates the avatar field in the users table
|
|
// T0222: Can accept empty string to set avatar to NULL
|
|
func (s *UserService) UpdateAvatarURL(userID uuid.UUID, avatarURL string) error {
|
|
user, err := s.userRepo.GetByID(userID.String())
|
|
if err != nil {
|
|
return fmt.Errorf("user not found")
|
|
}
|
|
|
|
// If avatarURL is empty string, set to empty (will be NULL in DB)
|
|
user.Avatar = avatarURL
|
|
if err := s.userRepo.Update(user); err != nil {
|
|
return fmt.Errorf("failed to update avatar URL: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetUserStats retrieves user statistics
|
|
func (s *UserService) GetUserStats(username string) (*types.UserStats, error) {
|
|
// This would typically query the database for stats
|
|
// For now, return empty stats
|
|
return &types.UserStats{
|
|
FollowersCount: 0,
|
|
FollowingCount: 0,
|
|
TracksCount: 0,
|
|
PlaylistsCount: 0,
|
|
}, nil
|
|
}
|
|
|
|
// ValidateUsername checks if a username is unique and if it can be changed (once per month)
|
|
func (s *UserService) ValidateUsername(userID uuid.UUID, username string) error {
|
|
// Vérifier si username existe pour autre user
|
|
existingUser, err := s.userRepo.GetByUsername(username)
|
|
if err == nil && existingUser != nil && existingUser.ID != userID {
|
|
return errors.New("username already taken")
|
|
}
|
|
|
|
// Vérifier si username modifiable (1 fois par mois)
|
|
user, err := s.userRepo.GetByID(userID.String())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check username change date: %w", err)
|
|
}
|
|
|
|
// Si le username actuel est le même, pas besoin de vérifier la date de changement
|
|
if user.Username == username {
|
|
return nil
|
|
}
|
|
|
|
// Vérifier si username_changed_at existe et si moins de 30 jours
|
|
if user.UsernameChangedAt != nil {
|
|
timeSinceChange := time.Since(*user.UsernameChangedAt)
|
|
if timeSinceChange < 30*24*time.Hour {
|
|
return errors.New("username can only be changed once per month")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CanChangeUsername checks if a user can change their username (once per month)
|
|
func (s *UserService) CanChangeUsername(userID uuid.UUID) (bool, error) {
|
|
user, err := s.userRepo.GetByID(userID.String())
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// If UsernameChangedAt is nil, user can change username
|
|
if user.UsernameChangedAt == nil {
|
|
return true, nil
|
|
}
|
|
|
|
// Check if it's been at least 1 month since last change
|
|
oneMonthAgo := time.Now().AddDate(0, -1, 0)
|
|
return user.UsernameChangedAt.Before(oneMonthAgo), nil
|
|
}
|
|
|
|
// CalculateProfileCompletion calculates the profile completion percentage
|
|
// T0220: Returns percentage (0-100) and list of missing required fields
|
|
func (s *UserService) CalculateProfileCompletion(userID uuid.UUID) (*ProfileCompletion, error) {
|
|
// Get profile as owner (to see all fields)
|
|
profile, err := s.GetProfile(userID, &userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
|
|
totalFields := 5
|
|
completedFields := 0
|
|
missing := []string{}
|
|
|
|
// Check username
|
|
if profile.Username != "" {
|
|
completedFields++
|
|
} else {
|
|
missing = append(missing, "username")
|
|
}
|
|
|
|
// Check first_name
|
|
if profile.FirstName != "" {
|
|
completedFields++
|
|
} else {
|
|
missing = append(missing, "first_name")
|
|
}
|
|
|
|
// Check last_name
|
|
if profile.LastName != "" {
|
|
completedFields++
|
|
} else {
|
|
missing = append(missing, "last_name")
|
|
}
|
|
|
|
// Check bio
|
|
if profile.Bio != nil && *profile.Bio != "" {
|
|
completedFields++
|
|
} else {
|
|
missing = append(missing, "bio")
|
|
}
|
|
|
|
// Check avatar
|
|
if profile.AvatarURL != nil && *profile.AvatarURL != "" {
|
|
completedFields++
|
|
} else {
|
|
missing = append(missing, "avatar")
|
|
}
|
|
|
|
// Calculate percentage
|
|
percentage := (completedFields * 100) / totalFields
|
|
|
|
return &ProfileCompletion{
|
|
Percentage: percentage,
|
|
Missing: missing,
|
|
}, nil
|
|
}
|
|
|
|
// UpdateProfileByID updates a user profile by ID with the new request structure
|
|
func (s *UserService) UpdateProfileByID(userID uuid.UUID, req *UpdateProfileRequest) (*models.User, error) {
|
|
user, err := s.userRepo.GetByID(userID.String())
|
|
if err != nil {
|
|
return nil, errors.New("user not found")
|
|
}
|
|
|
|
// Apply updates
|
|
if req.FirstName != nil && *req.FirstName != "" {
|
|
user.FirstName = *req.FirstName
|
|
}
|
|
if req.LastName != nil && *req.LastName != "" {
|
|
user.LastName = *req.LastName
|
|
}
|
|
if req.Username != nil && *req.Username != "" {
|
|
user.Username = *req.Username
|
|
now := time.Now()
|
|
user.UsernameChangedAt = &now
|
|
}
|
|
if req.Bio != nil {
|
|
user.Bio = *req.Bio
|
|
}
|
|
if req.Location != nil {
|
|
user.Location = *req.Location
|
|
}
|
|
if req.BirthDate != nil && *req.BirthDate != "" {
|
|
birthdate, err := time.Parse("2006-01-02", *req.BirthDate)
|
|
if err == nil {
|
|
user.Birthdate = &birthdate
|
|
}
|
|
}
|
|
if req.Gender != nil {
|
|
user.Gender = *req.Gender
|
|
}
|
|
|
|
// Save changes
|
|
err = s.userRepo.Update(user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// GetUserSettings récupère les paramètres utilisateur
|
|
// T0231: Récupère user_settings depuis DB et user_profiles pour language, timezone, theme
|
|
func (s *UserService) GetUserSettings(userID uuid.UUID) (*types.UserSettingsResponse, error) {
|
|
if s.db == nil {
|
|
return nil, fmt.Errorf("database access not available")
|
|
}
|
|
|
|
// Récupérer ou créer user_settings
|
|
var settings models.UserSettings
|
|
result := s.db.Where("user_id = ?", userID).First(&settings)
|
|
if result.Error != nil {
|
|
if result.Error == gorm.ErrRecordNotFound {
|
|
// Créer settings par défaut
|
|
settings = models.UserSettings{
|
|
UserID: userID,
|
|
EmailNotifications: true,
|
|
PushNotifications: true,
|
|
BrowserNotifications: true,
|
|
EmailOnFollow: true,
|
|
EmailOnLike: true,
|
|
EmailOnComment: true,
|
|
EmailOnMessage: true,
|
|
EmailOnMention: true,
|
|
AllowSearchIndexing: true,
|
|
ShowActivity: true,
|
|
Autoplay: true,
|
|
}
|
|
if err := s.db.Create(&settings).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to create default settings: %w", err)
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("failed to get settings: %w", result.Error)
|
|
}
|
|
}
|
|
|
|
// Récupérer user_profiles pour preferences (language, timezone, theme)
|
|
// T0233: Récupérer depuis user_profiles avec création auto si n'existe pas
|
|
var profile models.UserProfile
|
|
result = s.db.Where("user_id = ?", userID).First(&profile)
|
|
if result.Error != nil {
|
|
if result.Error == gorm.ErrRecordNotFound {
|
|
// Créer profile par défaut
|
|
profile = models.UserProfile{
|
|
UserID: userID,
|
|
Language: "en",
|
|
Timezone: "UTC",
|
|
Theme: "auto",
|
|
}
|
|
if err := s.db.Create(&profile).Error; err != nil {
|
|
return nil, fmt.Errorf("failed to create default profile: %w", err)
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("failed to get profile: %w", result.Error)
|
|
}
|
|
}
|
|
|
|
language := profile.Language
|
|
timezone := profile.Timezone
|
|
// theme := profile.Theme // Not used in PreferenceSettings (no Theme field)
|
|
|
|
return &types.UserSettingsResponse{
|
|
Notifications: types.NotificationSettings{
|
|
Email: settings.EmailNotifications,
|
|
Push: settings.PushNotifications,
|
|
InApp: settings.BrowserNotifications,
|
|
Comments: settings.EmailOnComment,
|
|
Likes: settings.EmailOnLike,
|
|
Followers: settings.EmailOnFollow,
|
|
Mentions: settings.EmailOnMention,
|
|
Playlist: false, // Not mapped from settings
|
|
},
|
|
Privacy: types.PrivacySettings{
|
|
ProfileVisibility: "public", // Default, should be read from settings if available
|
|
PlaylistsPublic: true, // Default, should be read from settings if available
|
|
},
|
|
Content: types.ContentSettings{
|
|
ExplicitContent: settings.ExplicitContent,
|
|
},
|
|
Preferences: types.PreferenceSettings{
|
|
Language: language,
|
|
Timezone: timezone,
|
|
DateFormat: "YYYY-MM-DD", // Default
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// UpdateUserSettings met à jour les paramètres utilisateur
|
|
// T0232: Mettre à jour user_settings et user_profiles en DB
|
|
func (s *UserService) UpdateUserSettings(userID uuid.UUID, req *types.UpdateSettingsRequest) error {
|
|
if s.db == nil {
|
|
return fmt.Errorf("database access not available")
|
|
}
|
|
|
|
// Mettre à jour user_settings
|
|
if req.Notifications != nil || req.Privacy != nil || req.Content != nil {
|
|
updates := map[string]interface{}{}
|
|
|
|
if req.Notifications != nil {
|
|
updates["email_notifications"] = req.Notifications.Email
|
|
updates["push_notifications"] = req.Notifications.Push
|
|
updates["browser_notifications"] = req.Notifications.InApp
|
|
updates["email_on_follow"] = req.Notifications.Followers
|
|
updates["email_on_like"] = req.Notifications.Likes
|
|
updates["email_on_comment"] = req.Notifications.Comments
|
|
updates["email_on_mention"] = req.Notifications.Mentions
|
|
// EmailOnMessage and EmailMarketing not mapped (no corresponding fields in NotificationSettings)
|
|
}
|
|
|
|
if req.Privacy != nil {
|
|
// AllowSearchIndexing and ShowActivity not mapped (no corresponding fields in PrivacySettings)
|
|
// PrivacySettings only has ProfileVisibility and PlaylistsPublic
|
|
}
|
|
|
|
if req.Content != nil {
|
|
updates["explicit_content"] = req.Content.ExplicitContent
|
|
// Autoplay not available in ContentSettings type
|
|
}
|
|
|
|
if len(updates) > 0 {
|
|
// S'assurer que user_settings existe d'abord
|
|
var settings models.UserSettings
|
|
result := s.db.Where("user_id = ?", userID).First(&settings)
|
|
if result.Error == gorm.ErrRecordNotFound {
|
|
// Créer settings par défaut si n'existe pas
|
|
settings = models.UserSettings{
|
|
UserID: userID,
|
|
EmailNotifications: true,
|
|
PushNotifications: true,
|
|
BrowserNotifications: true,
|
|
EmailOnFollow: true,
|
|
EmailOnLike: true,
|
|
EmailOnComment: true,
|
|
EmailOnMessage: true,
|
|
EmailOnMention: true,
|
|
AllowSearchIndexing: true,
|
|
ShowActivity: true,
|
|
Autoplay: true,
|
|
}
|
|
if err := s.db.Create(&settings).Error; err != nil {
|
|
return fmt.Errorf("failed to create default settings: %w", err)
|
|
}
|
|
} else if result.Error != nil {
|
|
return fmt.Errorf("failed to get settings: %w", result.Error)
|
|
}
|
|
|
|
// Mettre à jour
|
|
if err := s.db.Model(&models.UserSettings{}).Where("user_id = ?", userID).Updates(updates).Error; err != nil {
|
|
return fmt.Errorf("failed to update settings: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mettre à jour user_profiles (preferences)
|
|
// T0233: Mettre à jour user_profiles avec création auto si n'existe pas
|
|
if req.Preferences != nil {
|
|
profileUpdates := map[string]interface{}{}
|
|
if req.Preferences.Language != "" {
|
|
profileUpdates["language"] = req.Preferences.Language
|
|
}
|
|
if req.Preferences.Timezone != "" {
|
|
profileUpdates["timezone"] = req.Preferences.Timezone
|
|
}
|
|
// Theme not available in PreferenceSettings type (only Language, Timezone, DateFormat)
|
|
|
|
if len(profileUpdates) > 0 {
|
|
// S'assurer que user_profiles existe d'abord
|
|
var profile models.UserProfile
|
|
result := s.db.Where("user_id = ?", userID).First(&profile)
|
|
if result.Error == gorm.ErrRecordNotFound {
|
|
// Créer profile par défaut si n'existe pas
|
|
profile = models.UserProfile{
|
|
UserID: userID,
|
|
Language: "en",
|
|
Timezone: "UTC",
|
|
Theme: "auto",
|
|
}
|
|
// Appliquer les updates avant création
|
|
if lang, ok := profileUpdates["language"].(string); ok {
|
|
profile.Language = lang
|
|
}
|
|
if tz, ok := profileUpdates["timezone"].(string); ok {
|
|
profile.Timezone = tz
|
|
}
|
|
if th, ok := profileUpdates["theme"].(string); ok {
|
|
profile.Theme = th
|
|
}
|
|
if err := s.db.Create(&profile).Error; err != nil {
|
|
return fmt.Errorf("failed to create default profile: %w", err)
|
|
}
|
|
} else if result.Error != nil {
|
|
return fmt.Errorf("failed to get profile: %w", result.Error)
|
|
} else {
|
|
// Mettre à jour
|
|
if err := s.db.Model(&models.UserProfile{}).Where("user_id = ?", userID).Updates(profileUpdates).Error; err != nil {
|
|
return fmt.Errorf("failed to update profile: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|