710 lines
20 KiB
Go
710 lines
20 KiB
Go
// veza-backend-api/internal/api/user/service.go
|
|
package user
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"veza-backend-api/internal/database"
|
|
"veza-backend-api/internal/utils"
|
|
)
|
|
|
|
// Service handles user business logic
|
|
type Service struct {
|
|
db *database.DB
|
|
}
|
|
|
|
// NewService creates a new user service
|
|
func NewService(db *database.DB) *Service {
|
|
return &Service{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// GetUsers retrieves users with pagination and optional search
|
|
func (s *Service) GetUsers(page, limit int, search string) ([]UserResponse, int, error) {
|
|
offset := (page - 1) * limit
|
|
|
|
// Build the query with optional search
|
|
baseQuery := `
|
|
SELECT id, email, first_name, last_name, username, avatar, bio,
|
|
role, is_active, is_verified, last_login_at, created_at, updated_at
|
|
FROM users
|
|
`
|
|
countQuery := "SELECT COUNT(*) FROM users"
|
|
|
|
var whereClause string
|
|
var args []interface{}
|
|
argIndex := 1
|
|
|
|
if search != "" {
|
|
whereClause = ` WHERE (
|
|
email ILIKE $` + fmt.Sprintf("%d", argIndex) + ` OR
|
|
first_name ILIKE $` + fmt.Sprintf("%d", argIndex) + ` OR
|
|
last_name ILIKE $` + fmt.Sprintf("%d", argIndex) + ` OR
|
|
username ILIKE $` + fmt.Sprintf("%d", argIndex) + `
|
|
)`
|
|
args = append(args, "%"+search+"%")
|
|
argIndex++
|
|
}
|
|
|
|
// Get total count
|
|
var total int
|
|
err := s.db.QueryRow(countQuery+whereClause, args...).Scan(&total)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("failed to count users: %w", err)
|
|
}
|
|
|
|
// Get users
|
|
orderClause := " ORDER BY created_at DESC"
|
|
limitClause := fmt.Sprintf(" LIMIT $%d OFFSET $%d", argIndex, argIndex+1)
|
|
args = append(args, limit, offset)
|
|
|
|
query := baseQuery + whereClause + orderClause + limitClause
|
|
rows, err := s.db.Query(query, args...)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("failed to query users: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var users []UserResponse
|
|
for rows.Next() {
|
|
var user UserResponse
|
|
err := rows.Scan(
|
|
&user.ID, &user.Email, &user.FirstName, &user.LastName,
|
|
&user.Username, &user.Avatar, &user.Bio, &user.Role,
|
|
&user.IsActive, &user.IsVerified, &user.LastLoginAt,
|
|
&user.CreatedAt, &user.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("failed to scan user: %w", err)
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
|
|
return users, total, nil
|
|
}
|
|
|
|
// GetUserByID retrieves a user by ID
|
|
func (s *Service) GetUserByID(userID uuid.UUID) (*UserResponse, error) {
|
|
query := `
|
|
SELECT id, email, first_name, last_name, username, avatar, bio,
|
|
role, is_active, is_verified, last_login_at, created_at, updated_at
|
|
FROM users
|
|
WHERE id = $1 AND is_active = true
|
|
`
|
|
|
|
var user UserResponse
|
|
err := s.db.QueryRow(query, userID).Scan(
|
|
&user.ID, &user.Email, &user.FirstName, &user.LastName,
|
|
&user.Username, &user.Avatar, &user.Bio, &user.Role,
|
|
&user.IsActive, &user.IsVerified, &user.LastLoginAt,
|
|
&user.CreatedAt, &user.UpdatedAt,
|
|
)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
return nil, fmt.Errorf("failed to get user: %w", err)
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// GetUserByEmail retrieves a user by email (includes password hash for auth)
|
|
func (s *Service) GetUserByEmail(email string) (*User, error) {
|
|
query := `
|
|
SELECT id, email, password_hash, first_name, last_name, username,
|
|
avatar, bio, role, is_active, is_verified, last_login_at,
|
|
created_at, updated_at
|
|
FROM users
|
|
WHERE email = $1
|
|
`
|
|
|
|
var user User
|
|
err := s.db.QueryRow(query, email).Scan(
|
|
&user.ID, &user.Email, &user.Password, &user.FirstName,
|
|
&user.LastName, &user.Username, &user.Avatar, &user.Bio,
|
|
&user.Role, &user.IsActive, &user.IsVerified, &user.LastLoginAt,
|
|
&user.CreatedAt, &user.UpdatedAt,
|
|
)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
return nil, fmt.Errorf("failed to get user: %w", err)
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// CreateUser creates a new user
|
|
func (s *Service) CreateUser(req CreateUserRequest) (*UserResponse, error) {
|
|
// Hash the password
|
|
passwordHash, err := utils.HashPassword(req.Password)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to hash password: %w", err)
|
|
}
|
|
|
|
// Set default role if not provided
|
|
role := req.Role
|
|
if role == "" {
|
|
role = "user"
|
|
}
|
|
|
|
query := `
|
|
INSERT INTO users (email, password_hash, first_name, last_name, username, role, is_active, is_verified, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, true, false, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
RETURNING id, email, first_name, last_name, username, role, is_active, is_verified, created_at, updated_at
|
|
`
|
|
|
|
var user UserResponse
|
|
err = s.db.QueryRow(
|
|
query, req.Email, passwordHash, req.FirstName, req.LastName,
|
|
req.Username, role,
|
|
).Scan(
|
|
&user.ID, &user.Email, &user.FirstName, &user.LastName,
|
|
&user.Username, &user.Role, &user.IsActive, &user.IsVerified,
|
|
&user.CreatedAt, &user.UpdatedAt,
|
|
)
|
|
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "unique") {
|
|
return nil, fmt.Errorf("email already exists")
|
|
}
|
|
return nil, fmt.Errorf("failed to create user: %w", err)
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// UpdateUser updates an existing user
|
|
func (s *Service) UpdateUser(userID uuid.UUID, req UpdateUserRequest) (*UserResponse, error) {
|
|
// Build dynamic update query
|
|
setParts := []string{"updated_at = CURRENT_TIMESTAMP"}
|
|
args := []interface{}{}
|
|
argIndex := 1
|
|
|
|
if req.FirstName != nil {
|
|
setParts = append(setParts, fmt.Sprintf("first_name = $%d", argIndex))
|
|
args = append(args, req.FirstName)
|
|
argIndex++
|
|
}
|
|
|
|
if req.LastName != nil {
|
|
setParts = append(setParts, fmt.Sprintf("last_name = $%d", argIndex))
|
|
args = append(args, req.LastName)
|
|
argIndex++
|
|
}
|
|
|
|
if req.Username != nil {
|
|
setParts = append(setParts, fmt.Sprintf("username = $%d", argIndex))
|
|
args = append(args, req.Username)
|
|
argIndex++
|
|
}
|
|
|
|
if req.Avatar != nil {
|
|
setParts = append(setParts, fmt.Sprintf("avatar = $%d", argIndex))
|
|
args = append(args, req.Avatar)
|
|
argIndex++
|
|
}
|
|
|
|
if req.Bio != nil {
|
|
setParts = append(setParts, fmt.Sprintf("bio = $%d", argIndex))
|
|
args = append(args, req.Bio)
|
|
argIndex++
|
|
}
|
|
|
|
if req.IsActive != nil {
|
|
setParts = append(setParts, fmt.Sprintf("is_active = $%d", argIndex))
|
|
args = append(args, req.IsActive)
|
|
argIndex++
|
|
}
|
|
|
|
if req.IsVerified != nil {
|
|
setParts = append(setParts, fmt.Sprintf("is_verified = $%d", argIndex))
|
|
args = append(args, req.IsVerified)
|
|
argIndex++
|
|
}
|
|
|
|
if req.Role != nil {
|
|
setParts = append(setParts, fmt.Sprintf("role = $%d", argIndex))
|
|
args = append(args, req.Role)
|
|
argIndex++
|
|
}
|
|
|
|
// Add user ID as the last argument
|
|
args = append(args, userID)
|
|
|
|
query := fmt.Sprintf(`
|
|
UPDATE users
|
|
SET %s
|
|
WHERE id = $%d
|
|
RETURNING id, email, first_name, last_name, username, avatar, bio,
|
|
role, is_active, is_verified, last_login_at, created_at, updated_at
|
|
`, strings.Join(setParts, ", "), argIndex)
|
|
|
|
var user UserResponse
|
|
err := s.db.QueryRow(query, args...).Scan(
|
|
&user.ID, &user.Email, &user.FirstName, &user.LastName,
|
|
&user.Username, &user.Avatar, &user.Bio, &user.Role,
|
|
&user.IsActive, &user.IsVerified, &user.LastLoginAt,
|
|
&user.CreatedAt, &user.UpdatedAt,
|
|
)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
return nil, fmt.Errorf("failed to update user: %w", err)
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
// DeleteUser soft deletes a user (sets is_active to false)
|
|
func (s *Service) DeleteUser(userID uuid.UUID) error {
|
|
query := `
|
|
UPDATE users
|
|
SET is_active = false, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $1 AND is_active = true
|
|
`
|
|
|
|
result, err := s.db.Exec(query, userID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete user: %w", err)
|
|
}
|
|
|
|
rowsAffected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get rows affected: %w", err)
|
|
}
|
|
|
|
if rowsAffected == 0 {
|
|
return fmt.Errorf("user not found")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateLastLogin updates the user's last login timestamp
|
|
func (s *Service) UpdateLastLogin(userID uuid.UUID) error {
|
|
query := `
|
|
UPDATE users
|
|
SET last_login_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $1
|
|
`
|
|
|
|
_, err := s.db.Exec(query, userID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update last login: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChangePassword updates a user's password
|
|
func (s *Service) ChangePassword(userID uuid.UUID, currentPassword, newPassword string) error {
|
|
// First, get the current password hash
|
|
var currentHash string
|
|
err := s.db.QueryRow("SELECT password_hash FROM users WHERE id = $1", userID).Scan(¤tHash)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return fmt.Errorf("user not found")
|
|
}
|
|
return fmt.Errorf("failed to get user password: %w", err)
|
|
}
|
|
|
|
// Verify current password
|
|
if err := utils.CheckPasswordHash(currentPassword, currentHash); err != nil {
|
|
return fmt.Errorf("current password is incorrect")
|
|
}
|
|
|
|
// Hash new password
|
|
newHash, err := utils.HashPassword(newPassword)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to hash new password: %w", err)
|
|
}
|
|
|
|
// Update password
|
|
query := `
|
|
UPDATE users
|
|
SET password_hash = $1, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $2
|
|
`
|
|
|
|
_, err = s.db.Exec(query, newHash, userID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update password: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetUserStats returns basic user statistics
|
|
func (s *Service) GetUserStats() (map[string]interface{}, error) {
|
|
stats := make(map[string]interface{})
|
|
|
|
// Total users
|
|
var totalUsers int
|
|
err := s.db.QueryRow("SELECT COUNT(*) FROM users WHERE is_active = true").Scan(&totalUsers)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get total users: %w", err)
|
|
}
|
|
stats["total_users"] = totalUsers
|
|
|
|
// Verified users
|
|
var verifiedUsers int
|
|
err = s.db.QueryRow("SELECT COUNT(*) FROM users WHERE is_active = true AND is_verified = true").Scan(&verifiedUsers)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get verified users: %w", err)
|
|
}
|
|
stats["verified_users"] = verifiedUsers
|
|
|
|
// Active users (logged in within last 30 days)
|
|
var activeUsers int
|
|
err = s.db.QueryRow(`
|
|
SELECT COUNT(*) FROM users
|
|
WHERE is_active = true AND last_login_at > CURRENT_TIMESTAMP - INTERVAL '30 days'
|
|
`).Scan(&activeUsers)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get active users: %w", err)
|
|
}
|
|
stats["active_users"] = activeUsers
|
|
|
|
// New users this month
|
|
var newUsersThisMonth int
|
|
err = s.db.QueryRow(`
|
|
SELECT COUNT(*) FROM users
|
|
WHERE is_active = true AND created_at >= date_trunc('month', CURRENT_TIMESTAMP)
|
|
`).Scan(&newUsersThisMonth)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get new users this month: %w", err)
|
|
}
|
|
stats["new_users_this_month"] = newUsersThisMonth
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// GetUserPreferences récupère les préférences d'un utilisateur
|
|
func (s *Service) GetUserPreferences(userID uuid.UUID) (*UserPreferencesResponse, error) {
|
|
query := `
|
|
SELECT user_id, theme, language, timezone,
|
|
COALESCE(notifications, '{}') as notifications,
|
|
COALESCE(privacy, '{}') as privacy,
|
|
COALESCE(audio, '{}') as audio,
|
|
updated_at
|
|
FROM user_preferences
|
|
WHERE user_id = $1
|
|
`
|
|
|
|
var preferences UserPreferencesResponse
|
|
var notificationsJSON, privacyJSON, audioJSON string
|
|
|
|
err := s.db.QueryRow(query, userID).Scan(
|
|
&preferences.UserID, &preferences.Theme, &preferences.Language,
|
|
&preferences.Timezone, ¬ificationsJSON, &privacyJSON,
|
|
&audioJSON, &preferences.UpdatedAt,
|
|
)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
// Retourner les préférences par défaut
|
|
return &UserPreferencesResponse{
|
|
UserID: userID,
|
|
Theme: "light",
|
|
Language: "en",
|
|
Timezone: "UTC",
|
|
Notifications: NotificationSettings{
|
|
Email: true, Push: true, Desktop: true,
|
|
NewFollowers: true, TrackComments: true,
|
|
DirectMessages: true, Mentions: true, Likes: false,
|
|
},
|
|
Privacy: PrivacySettings{
|
|
ShowEmail: false, ShowActivity: true, AllowDM: true,
|
|
TrackVisibility: "public", ProfileVisibility: "public",
|
|
},
|
|
Audio: AudioSettings{
|
|
AutoPlay: true, Quality: "high", Volume: 0.8, Crossfade: 5,
|
|
},
|
|
UpdatedAt: time.Now(),
|
|
}, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to get user preferences: %w", err)
|
|
}
|
|
|
|
// TODO: Parse JSON strings to structs (simplified for now)
|
|
preferences.Notifications = NotificationSettings{
|
|
Email: true, Push: true, Desktop: true,
|
|
NewFollowers: true, TrackComments: true,
|
|
DirectMessages: true, Mentions: true, Likes: false,
|
|
}
|
|
preferences.Privacy = PrivacySettings{
|
|
ShowEmail: false, ShowActivity: true, AllowDM: true,
|
|
TrackVisibility: "public", ProfileVisibility: "public",
|
|
}
|
|
preferences.Audio = AudioSettings{
|
|
AutoPlay: true, Quality: "high", Volume: 0.8, Crossfade: 5,
|
|
}
|
|
|
|
return &preferences, nil
|
|
}
|
|
|
|
// UpdateUserPreferences met à jour les préférences d'un utilisateur
|
|
func (s *Service) UpdateUserPreferences(userID uuid.UUID, req UserPreferencesRequest) (*UserPreferencesResponse, error) {
|
|
// Récupérer les préférences actuelles
|
|
current, err := s.GetUserPreferences(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Appliquer les mises à jour
|
|
if req.Theme != nil {
|
|
current.Theme = *req.Theme
|
|
}
|
|
if req.Language != nil {
|
|
current.Language = *req.Language
|
|
}
|
|
if req.Timezone != nil {
|
|
current.Timezone = *req.Timezone
|
|
}
|
|
if req.Notifications != nil {
|
|
current.Notifications = *req.Notifications
|
|
}
|
|
if req.Privacy != nil {
|
|
current.Privacy = *req.Privacy
|
|
}
|
|
if req.Audio != nil {
|
|
current.Audio = *req.Audio
|
|
}
|
|
|
|
current.UpdatedAt = time.Now()
|
|
|
|
// Sauvegarder en base (upsert)
|
|
query := `
|
|
INSERT INTO user_preferences (user_id, theme, language, timezone, notifications, privacy, audio, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
ON CONFLICT (user_id) DO UPDATE SET
|
|
theme = EXCLUDED.theme,
|
|
language = EXCLUDED.language,
|
|
timezone = EXCLUDED.timezone,
|
|
notifications = EXCLUDED.notifications,
|
|
privacy = EXCLUDED.privacy,
|
|
audio = EXCLUDED.audio,
|
|
updated_at = EXCLUDED.updated_at
|
|
`
|
|
|
|
// TODO: Serialize structs to JSON (simplified for now)
|
|
notificationsJSON := "{}"
|
|
privacyJSON := "{}"
|
|
audioJSON := "{}"
|
|
|
|
_, err = s.db.Exec(query, userID, current.Theme, current.Language, current.Timezone,
|
|
notificationsJSON, privacyJSON, audioJSON, current.UpdatedAt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to update user preferences: %w", err)
|
|
}
|
|
|
|
return current, nil
|
|
}
|
|
|
|
// DeleteAccount supprime le compte d'un utilisateur (soft delete)
|
|
func (s *Service) DeleteAccount(userID uuid.UUID, password, reason string) error {
|
|
// Vérifier le mot de passe
|
|
var currentHash string
|
|
err := s.db.QueryRow("SELECT password_hash FROM users WHERE id = $1", userID).Scan(¤tHash)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return fmt.Errorf("user not found")
|
|
}
|
|
return fmt.Errorf("failed to get user password: %w", err)
|
|
}
|
|
|
|
if err := utils.CheckPasswordHash(password, currentHash); err != nil {
|
|
return fmt.Errorf("invalid password")
|
|
}
|
|
|
|
// Marquer le compte comme supprimé avec période de grâce de 30 jours
|
|
recoveryDeadline := time.Now().Add(30 * 24 * time.Hour)
|
|
query := `
|
|
UPDATE users
|
|
SET is_active = false, deleted_at = CURRENT_TIMESTAMP,
|
|
deletion_reason = $2, recovery_deadline = $3, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $1
|
|
`
|
|
|
|
_, err = s.db.Exec(query, userID, reason, recoveryDeadline)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete account: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RecoverAccount récupère un compte supprimé
|
|
func (s *Service) RecoverAccount(email, password string) error {
|
|
// Vérifier l'utilisateur et son statut
|
|
var userID uuid.UUID
|
|
var currentHash string
|
|
var deletedAt sql.NullTime
|
|
var recoveryDeadline sql.NullTime
|
|
|
|
query := `
|
|
SELECT id, password_hash, deleted_at, recovery_deadline
|
|
FROM users
|
|
WHERE email = $1 AND deleted_at IS NOT NULL
|
|
`
|
|
|
|
err := s.db.QueryRow(query, email).Scan(&userID, ¤tHash, &deletedAt, &recoveryDeadline)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return fmt.Errorf("no deleted account found for this email")
|
|
}
|
|
return fmt.Errorf("failed to find account: %w", err)
|
|
}
|
|
|
|
// Vérifier que la période de récupération n'est pas expirée
|
|
if recoveryDeadline.Valid && time.Now().After(recoveryDeadline.Time) {
|
|
return fmt.Errorf("recovery period has expired")
|
|
}
|
|
|
|
// Vérifier le mot de passe
|
|
if err := utils.CheckPasswordHash(password, currentHash); err != nil {
|
|
return fmt.Errorf("invalid password")
|
|
}
|
|
|
|
// Réactiver le compte
|
|
updateQuery := `
|
|
UPDATE users
|
|
SET is_active = true, deleted_at = NULL, deletion_reason = NULL,
|
|
recovery_deadline = NULL, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = $1
|
|
`
|
|
|
|
_, err = s.db.Exec(updateQuery, userID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to recover account: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExportUserData exporte toutes les données d'un utilisateur (RGPD)
|
|
func (s *Service) ExportUserData(userID uuid.UUID) (*UserDataExport, error) {
|
|
// Récupérer le profil
|
|
profile, err := s.GetUserByID(userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get user profile: %w", err)
|
|
}
|
|
|
|
// Récupérer les préférences
|
|
preferences, err := s.GetUserPreferences(userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get user preferences: %w", err)
|
|
}
|
|
|
|
// Récupérer l'activité (simplifié)
|
|
activity := []UserActivity{
|
|
{ID: uuid.New(), Type: "login", Details: "User login", CreatedAt: time.Now()},
|
|
{ID: uuid.New(), Type: "profile_update", Details: "Profile updated", CreatedAt: time.Now()},
|
|
}
|
|
|
|
// Récupérer le contenu (simplifié)
|
|
content := []UserContent{
|
|
{ID: uuid.New(), Type: "track", Title: "Sample Track", URL: "/tracks/1", CreatedAt: time.Now()},
|
|
}
|
|
|
|
// Récupérer les interactions (simplifié)
|
|
interactions := []UserInteraction{
|
|
{ID: uuid.New(), Type: "like", TargetID: uuid.New(), CreatedAt: time.Now()},
|
|
}
|
|
|
|
export := &UserDataExport{
|
|
UserID: userID,
|
|
Profile: *profile,
|
|
Preferences: *preferences,
|
|
Activity: activity,
|
|
Content: content,
|
|
Interactions: interactions,
|
|
ExportedAt: time.Now(),
|
|
}
|
|
|
|
return export, nil
|
|
}
|
|
|
|
// RequestDataDeletion demande la suppression définitive des données
|
|
func (s *Service) RequestDataDeletion(userID uuid.UUID, password, reason string) error {
|
|
// Vérifier le mot de passe
|
|
var currentHash string
|
|
err := s.db.QueryRow("SELECT password_hash FROM users WHERE id = $1", userID).Scan(¤tHash)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return fmt.Errorf("user not found")
|
|
}
|
|
return fmt.Errorf("failed to get user password: %w", err)
|
|
}
|
|
|
|
if err := utils.CheckPasswordHash(password, currentHash); err != nil {
|
|
return fmt.Errorf("invalid password")
|
|
}
|
|
|
|
// Créer une demande de suppression définitive
|
|
query := `
|
|
INSERT INTO data_deletion_requests (user_id, reason, status, requested_at)
|
|
VALUES ($1, $2, 'pending', CURRENT_TIMESTAMP)
|
|
`
|
|
|
|
_, err = s.db.Exec(query, userID, reason)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create deletion request: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAccountStatus récupère le statut du compte
|
|
func (s *Service) GetAccountStatus(userID uuid.UUID) (*AccountStatus, error) {
|
|
query := `
|
|
SELECT id, is_active, is_verified, created_at, deleted_at,
|
|
COALESCE(deletion_reason, '') as deletion_reason,
|
|
recovery_deadline
|
|
FROM users
|
|
WHERE id = $1
|
|
`
|
|
|
|
var status AccountStatus
|
|
var deletedAt sql.NullTime
|
|
var recoveryDeadline sql.NullTime
|
|
|
|
err := s.db.QueryRow(query, userID).Scan(
|
|
&status.UserID, &status.IsActive, &status.IsVerified,
|
|
&status.CreatedAt, &deletedAt, &status.DeletionReason, &recoveryDeadline,
|
|
)
|
|
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
return nil, fmt.Errorf("failed to get account status: %w", err)
|
|
}
|
|
|
|
// Déterminer le statut
|
|
if deletedAt.Valid {
|
|
status.Status = "deleted"
|
|
status.DeletedAt = &deletedAt.Time
|
|
if recoveryDeadline.Valid {
|
|
status.RecoveryDeadline = &recoveryDeadline.Time
|
|
}
|
|
} else if !status.IsActive {
|
|
status.Status = "suspended"
|
|
} else {
|
|
status.Status = "active"
|
|
}
|
|
|
|
return &status, nil
|
|
}
|