package repositories import ( "clapclap/internal/models" "clapclap/internal/uuid" "errors" "strings" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "gorm.io/gorm/clause" "log" "time" ) func (r *Repo) UpdateLastActivity(id uuid.UUID, ts time.Time) error { return r.db.Model(&models.User{}). Where("id = ?", id). Update("last_activity", ts). Error } func (r *Repo) CreateUser(user *models.User, password string) error { hashed, err := bcrypt. GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err } user.HashedPassword = string(hashed) err = r.db.Create(user).Error if err != nil { return err } log.Printf("user %s was created", user.Username) return nil } func (r *Repo) FindUserByEmail(email string) (*models.User, error) { var user models.User err := r.db.Where("email = ?", email).First(&user).Error if err != nil { return nil, err } return &user, nil } func (r *Repo) FindUser(e string, u string) (*models.User, error) { var user models.User err := r.db.Where("email = ? OR username = ?", e, u).First(&user).Error if err != nil { return nil, err } return &user, nil } func (r *Repo) FindUserByUsername(username string, preloads []string) (*models.User, error) { var user models.User query := r.db // Apply dynamic preloads for _, p := range preloads { if p == "OwnedTracks" { query = query.Preload("OwnedTracks", "is_public = ?", true) } else { query = query.Preload(p) } } err := query.Where("username = ?", username).First(&user).Error if err != nil { return nil, err } return &user, nil } func (r *Repo) Find42UserByUsername(username string, preloads []string) (*models.User, error) { var user models.User query := r.db // Apply dynamic preloads for _, p := range preloads { if p == "OwnedTracks" { query = query.Preload("OwnedTracks", "is_public = ?", true) } else { query = query.Preload(p) } } err := query.Where("username = ?", username).First(&user).Error if err != nil { return nil, err } return &user, nil } func (r *Repo) GetAllUsers(users *[]models.User, preloads []string) error { query := r.db // Apply dynamic preloads for _, p := range preloads { if p == "OwnedTracks" { query = query.Preload("OwnedTracks", "is_public = ?", true) } else { query = query.Preload(p) } } return query.Find(users).Error } func (r *Repo) GetUserByID(id uuid.UUID, preloads []string) (*models.User, error) { var user models.User query := r.db // Apply dynamic preloads for _, p := range preloads { if p == "OwnedTracks" { query = query.Preload("OwnedTracks", "is_public = ?", true) } else { query = query.Preload(p) } } err := query.First(&user, "id = ?", id).Error return &user, err } func (r *Repo) UpdateUser(user *models.User) error { current, err := r.GetUserByID(user.ID, nil) if err != nil { return err } if user.Username != current.Username && strings.HasSuffix(current.Username, "@ft") { return errors.New("42 users cannot update their username") } if user.Email != current.Email && strings.HasSuffix(current.Username, "@ft") { return errors.New("42 users cannot update their email") } if user.HashedPassword != current.HashedPassword && strings.HasSuffix(current.Username, "@ft") { return errors.New("42 users cannot update their password") } // Select("*") forces GORM to update all fields, including zero-values (false), // while Omit("HashedPassword") keeps the password safe. if err := r.db.Model(user).Omit("HashedPassword").Updates(user).Error; err != nil { return err } log.Printf("user %s is successfully updated", user.Username) return nil } // NOTE: We cannnot just cascade cause we are softdeleting everyting func (r *Repo) DeleteUser(id string) error { uid, err := uuid.Parse(id) if err != nil { return err } return r.db.Transaction(func(tx *gorm.DB) error { if err := tx.Where("user_id = ?", uid).Delete(&models.Upload{}).Error; err != nil { return err } if err := tx.Where("author_id = ?", uid).Delete(&models.Rating{}).Error; err != nil { return err } if err := tx.Where("author_id = ?", uid).Delete(&models.Comment{}).Error; err != nil { return err } if err := tx.Where("owner_id = ?", uid).Delete(&models.Track{}).Error; err != nil { return err } if err := tx.Where("owner_id = ?", uid).Delete(&models.Project{}).Error; err != nil { return err } // Finally, soft-delete the User itself var user models.User if err := tx.Clauses(clause.Returning{}).Where("id = ?", uid).Delete(&user).Error; err != nil { return err } log.Printf("user %s has been soft-deleted along with related entities", user.Username) return nil }) } // NOTE: We cannnot just cascade cause we are softdeleting everyting func (r *Repo) DeleteUserByUsername(username string) error { return r.db.Transaction(func(tx *gorm.DB) error { var user models.User if err := tx.Where("username = ?", username).First(&user).Error; err != nil { return err } uid := user.ID if err := tx.Where("user_id = ?", uid).Delete(&models.Upload{}).Error; err != nil { return err } if err := tx.Where("author_id = ?", uid).Delete(&models.Rating{}).Error; err != nil { return err } if err := tx.Where("author_id = ?", uid).Delete(&models.Comment{}).Error; err != nil { return err } if err := tx.Where("owner_id = ?", uid).Delete(&models.Track{}).Error; err != nil { return err } if err := tx.Where("owner_id = ?", uid).Delete(&models.Project{}).Error; err != nil { return err } // 6. Finally, soft-delete the User itself result := tx.Clauses(clause.Returning{}).Where("id = ?", uid).Delete(&user) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return gorm.ErrRecordNotFound } log.Printf("user %v (ID: %v) has been soft-deleted along with related entities", user.Username, user.ID) return nil }) }