package services import ( "context" "encoding/json" "fmt" "time" "veza-backend-api/internal/models" "github.com/google/uuid" "go.uber.org/zap" "gorm.io/gorm" ) // DataExportService gère l'export de données utilisateur pour la conformité GDPR (BE-SVC-022) type DataExportService struct { db *gorm.DB logger *zap.Logger } // NewDataExportService crée un nouveau service d'export de données func NewDataExportService(db *gorm.DB, logger *zap.Logger) *DataExportService { return &DataExportService{ db: db, logger: logger, } } // UserDataExport représente toutes les données d'un utilisateur à exporter type UserDataExport struct { UserID uuid.UUID `json:"user_id"` ExportedAt time.Time `json:"exported_at"` Profile *UserProfileExport `json:"profile"` Settings *UserSettingsExport `json:"settings"` Tracks []TrackExport `json:"tracks"` Playlists []PlaylistExport `json:"playlists"` Comments []CommentExport `json:"comments"` Likes []LikeExport `json:"likes"` Analytics []AnalyticsExport `json:"analytics"` FederatedIDs []FederatedIDExport `json:"federated_identities"` Roles []RoleExport `json:"roles"` } // UserProfileExport représente les données de profil utilisateur type UserProfileExport struct { ID uuid.UUID `json:"id"` Username string `json:"username"` Email string `json:"email"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Avatar string `json:"avatar"` Bio string `json:"bio"` Location string `json:"location"` Birthdate *time.Time `json:"birthdate"` Gender string `json:"gender"` Role string `json:"role"` IsActive bool `json:"is_active"` IsVerified bool `json:"is_verified"` IsPublic bool `json:"is_public"` LastLoginAt *time.Time `json:"last_login_at"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // UserSettingsExport représente les paramètres utilisateur type UserSettingsExport struct { EmailNotifications bool `json:"email_notifications"` PushNotifications bool `json:"push_notifications"` BrowserNotifications bool `json:"browser_notifications"` EmailOnFollow bool `json:"email_on_follow"` EmailOnLike bool `json:"email_on_like"` EmailOnComment bool `json:"email_on_comment"` EmailOnMessage bool `json:"email_on_message"` EmailOnMention bool `json:"email_on_mention"` EmailMarketing bool `json:"email_marketing"` AllowSearchIndexing bool `json:"allow_search_indexing"` ShowActivity bool `json:"show_activity"` ExplicitContent bool `json:"explicit_content"` Autoplay bool `json:"autoplay"` } // TrackExport représente un track exporté type TrackExport struct { ID uuid.UUID `json:"id"` Title string `json:"title"` Artist string `json:"artist"` Album string `json:"album"` Duration int `json:"duration"` Genre string `json:"genre"` Year int `json:"year"` FilePath string `json:"file_path"` CoverArtPath string `json:"cover_art_path"` IsPublic bool `json:"is_public"` Status string `json:"status"` PlayCount int64 `json:"play_count"` LikeCount int64 `json:"like_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // PlaylistExport représente une playlist exportée type PlaylistExport struct { ID uuid.UUID `json:"id"` Title string `json:"title"` Description string `json:"description"` IsPublic bool `json:"is_public"` TrackCount int `json:"track_count"` FollowerCount int `json:"follower_count"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // CommentExport représente un commentaire exporté type CommentExport struct { ID uuid.UUID `json:"id"` TrackID uuid.UUID `json:"track_id"` Content string `json:"content"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // LikeExport représente un like exporté type LikeExport struct { ID uuid.UUID `json:"id"` TrackID uuid.UUID `json:"track_id"` CreatedAt time.Time `json:"created_at"` } // AnalyticsExport représente des analytics exportées type AnalyticsExport struct { ID uuid.UUID `json:"id"` TrackID uuid.UUID `json:"track_id"` PlayTime int `json:"play_time"` PauseCount int `json:"pause_count"` SeekCount int `json:"seek_count"` CompletionRate float64 `json:"completion_rate"` StartedAt time.Time `json:"started_at"` EndedAt *time.Time `json:"ended_at"` CreatedAt time.Time `json:"created_at"` } // FederatedIDExport représente une identité fédérée exportée type FederatedIDExport struct { ID uuid.UUID `json:"id"` Provider string `json:"provider"` ProviderID string `json:"provider_id"` Email string `json:"email"` DisplayName string `json:"display_name"` CreatedAt time.Time `json:"created_at"` } // RoleExport représente un rôle exporté type RoleExport struct { ID uuid.UUID `json:"id"` RoleName string `json:"role_name"` AssignedAt time.Time `json:"assigned_at"` ExpiresAt *time.Time `json:"expires_at"` IsActive bool `json:"is_active"` } // ExportUserData exporte toutes les données d'un utilisateur (BE-SVC-022) func (s *DataExportService) ExportUserData(ctx context.Context, userID uuid.UUID) (*UserDataExport, error) { export := &UserDataExport{ UserID: userID, ExportedAt: time.Now(), } // 1. Récupérer le profil utilisateur var user models.User if err := s.db.WithContext(ctx).First(&user, "id = ?", userID).Error; err != nil { return nil, fmt.Errorf("failed to get user: %w", err) } export.Profile = &UserProfileExport{ ID: user.ID, Username: user.Username, Email: user.Email, FirstName: user.FirstName, LastName: user.LastName, Avatar: user.Avatar, Bio: user.Bio, Location: user.Location, Birthdate: user.Birthdate, Gender: user.Gender, Role: user.Role, IsActive: user.IsActive, IsVerified: user.IsVerified, IsPublic: user.IsPublic, LastLoginAt: user.LastLoginAt, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, } // 2. Récupérer les paramètres utilisateur var settings models.UserSettings if err := s.db.WithContext(ctx).First(&settings, "user_id = ?", userID).Error; err == nil { export.Settings = &UserSettingsExport{ EmailNotifications: settings.EmailNotifications, PushNotifications: settings.PushNotifications, BrowserNotifications: settings.BrowserNotifications, EmailOnFollow: settings.EmailOnFollow, EmailOnLike: settings.EmailOnLike, EmailOnComment: settings.EmailOnComment, EmailOnMessage: settings.EmailOnMessage, EmailOnMention: settings.EmailOnMention, EmailMarketing: settings.EmailMarketing, AllowSearchIndexing: settings.AllowSearchIndexing, ShowActivity: settings.ShowActivity, ExplicitContent: settings.ExplicitContent, Autoplay: settings.Autoplay, } } // 3. Récupérer les tracks de l'utilisateur var tracks []models.Track if err := s.db.WithContext(ctx).Where("creator_id = ?", userID).Find(&tracks).Error; err == nil { export.Tracks = make([]TrackExport, len(tracks)) for i, track := range tracks { export.Tracks[i] = TrackExport{ ID: track.ID, Title: track.Title, Artist: track.Artist, Album: track.Album, Duration: track.Duration, Genre: track.Genre, Year: track.Year, FilePath: track.FilePath, CoverArtPath: track.CoverArtPath, IsPublic: track.IsPublic, Status: string(track.Status), PlayCount: track.PlayCount, LikeCount: track.LikeCount, CreatedAt: track.CreatedAt, UpdatedAt: track.UpdatedAt, } } } // 4. Récupérer les playlists de l'utilisateur var playlists []models.Playlist if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&playlists).Error; err == nil { export.Playlists = make([]PlaylistExport, len(playlists)) for i, playlist := range playlists { export.Playlists[i] = PlaylistExport{ ID: playlist.ID, Title: playlist.Title, Description: playlist.Description, IsPublic: playlist.IsPublic, TrackCount: playlist.TrackCount, FollowerCount: playlist.FollowerCount, CreatedAt: playlist.CreatedAt, UpdatedAt: playlist.UpdatedAt, } } } // 5. Récupérer les commentaires de l'utilisateur var comments []models.TrackComment if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&comments).Error; err == nil { export.Comments = make([]CommentExport, len(comments)) for i, comment := range comments { export.Comments[i] = CommentExport{ ID: comment.ID, TrackID: comment.TrackID, Content: comment.Content, CreatedAt: comment.CreatedAt, UpdatedAt: comment.UpdatedAt, } } } // 6. Récupérer les likes de l'utilisateur var likes []models.TrackLike if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&likes).Error; err == nil { export.Likes = make([]LikeExport, len(likes)) for i, like := range likes { export.Likes[i] = LikeExport{ ID: like.ID, TrackID: like.TrackID, CreatedAt: like.CreatedAt, } } } // 7. Récupérer les analytics de l'utilisateur var analytics []models.PlaybackAnalytics if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&analytics).Error; err == nil { export.Analytics = make([]AnalyticsExport, len(analytics)) for i, a := range analytics { export.Analytics[i] = AnalyticsExport{ ID: a.ID, TrackID: a.TrackID, PlayTime: a.PlayTime, PauseCount: a.PauseCount, SeekCount: a.SeekCount, CompletionRate: a.CompletionRate, StartedAt: a.StartedAt, EndedAt: a.EndedAt, CreatedAt: a.CreatedAt, } } } // 8. Récupérer les identités fédérées var federatedIDs []models.FederatedIdentity if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&federatedIDs).Error; err == nil { export.FederatedIDs = make([]FederatedIDExport, len(federatedIDs)) for i, fid := range federatedIDs { export.FederatedIDs[i] = FederatedIDExport{ ID: fid.ID, Provider: fid.Provider, ProviderID: fid.ProviderID, Email: fid.Email, DisplayName: fid.DisplayName, CreatedAt: fid.CreatedAt, } } } // 9. Récupérer les rôles de l'utilisateur var userRoles []models.UserRole if err := s.db.WithContext(ctx).Where("user_id = ?", userID).Find(&userRoles).Error; err == nil { export.Roles = make([]RoleExport, len(userRoles)) for i, ur := range userRoles { export.Roles[i] = RoleExport{ ID: ur.ID, RoleName: ur.RoleName, AssignedAt: ur.AssignedAt, ExpiresAt: ur.ExpiresAt, IsActive: ur.IsActive, } } } s.logger.Info("User data exported", zap.String("user_id", userID.String()), zap.Int("tracks", len(export.Tracks)), zap.Int("playlists", len(export.Playlists)), zap.Int("comments", len(export.Comments)), zap.Int("likes", len(export.Likes)), zap.Int("analytics", len(export.Analytics)), ) return export, nil } // ExportUserDataAsJSON exporte les données utilisateur au format JSON func (s *DataExportService) ExportUserDataAsJSON(ctx context.Context, userID uuid.UUID) ([]byte, error) { export, err := s.ExportUserData(ctx, userID) if err != nil { return nil, err } jsonData, err := json.MarshalIndent(export, "", " ") if err != nil { return nil, fmt.Errorf("failed to marshal export data: %w", err) } return jsonData, nil }