package repositories import ( "clapclap/internal/models" "clapclap/internal/utils" "errors" "log" "slices" "strings" "clapclap/internal/uuid" "gorm.io/gorm/clause" ) // TODO (pipo) : move this somewhere else + refactor // Security Check: Whitelist arrays var allowedColumns [4]string var allowedOrders [2]string func init() { allowedColumns = [4]string{"name", "created_at", "random", "is_public"} allowedOrders = [2]string{"ASC", "DESC"} } func (r *Repo) GetAllTracks(limit int, page int, search string, genreID string, sortBy string, order string, preloads []string, visibility string, userID uuid.UUID) ([]models.Track, error) { var tracks []models.Track query := r.db.Preload("Genre") // 1. Visibility & Authentication Logic isLoggedIn := userID != uuid.Nil switch visibility { case "private": // Only private tracks owned by the current user if isLoggedIn { query = query.Where("is_public = ? AND owner_id = ?", false, userID) } else { return tracks, nil } case "both": // "both" (public tracks + user's private tracks) if isLoggedIn { query = query.Where("is_public = ? OR owner_id = ?", true, userID) } else { query = query.Where("is_public = ?", true) // Fallback for guests } default: // Only public tracks query = query.Where("is_public = ?", true) } // 2. Search Functionality if search != "" { query = query.Where("LOWER(name) LIKE LOWER(?)", "%"+search+"%") } // 3. Genre Filter if genreID != "" { uid, err := uuid.Parse(genreID) if err == nil { query = query.Where("genre_id = ?", uid) } } // 4. Set fallback values when invalid if !slices.Contains(allowedColumns[:], sortBy) { sortBy = "created_at" } orderDir := strings.ToUpper(order) if !slices.Contains(allowedOrders[:], orderDir) { orderDir = "DESC" } // 5. Apply the validated sorting query = query.Order(sortBy + " " + orderDir) // 6. Pagination query = query.Limit(limit).Offset((page - 1) * limit) // 7. Preloads for _, p := range preloads { query = query.Preload(p) } err := query.Find(&tracks).Error if err != nil { return nil, errors.New("GetAllTracks: " + err.Error()) } return tracks, nil } func (r *Repo) GetTracksByGenre(limit int, page int, preloads []string, genreID string) ([]models.Track, error) { var tracks []models.Track uid, err := uuid.Parse(genreID) if err != nil { return nil, err } query := r.db. Order("created_at DESC"). Limit(limit). Offset((page - 1) * limit) for _, p := range preloads { query = query.Preload(p) } err = query.Where("genre_id = ? AND is_public = ?", uid, true).Find(&tracks).Error if err != nil { utils.LogError(err) } return tracks, err } // Create saves a new track. // Note: Ensure the 'OwnerID' and 'GenreID' are set in the track object. func (r *Repo) CreateTrack(track *models.Track) error { err := r.db.Create(track).Error if err != nil { utils.LogError(err) return err } log.Printf("track %s related to s3 object %s was created", track.Name, track.AudioFile) return nil } // GetByID fetches a specific track with full details (Comments). func (r *Repo) GetTrackByID(id string, preloads []string, userID uuid.UUID) (*models.Track, error) { var track models.Track uid, err := uuid.Parse(id) if err != nil { utils.LogError(err) return nil, err } query := r.db.Preload("Genre") if userID != uuid.Nil { query = query.Where("is_public = ? OR owner_id = ?", true, userID) } else { // If guest, they can ONLY see public tracks query = query.Where("is_public = ?", true) } for _, p := range preloads { query = query.Preload(p) } err = query.First(&track, "id = ?", uid).Error return &track, err } // Update modifies track details. func (r *Repo) UpdateTrack(track *models.Track) error { if err := r.db.Model(track).Select("*").Updates(track).Error; err != nil { utils.LogError(err) return err } log.Printf("track %s is successfully updated", track.Name) return nil } // Delete removes a track. func (r *Repo) DeleteTrack(id string) error { uid, err := uuid.Parse(id) if err != nil { return err } var track models.Track err = r.db.Clauses(clause.Returning{}). Where("id = ?", uid). Delete(&track).Error if err != nil { utils.LogError(err) return err } log.Printf("track %s has been deleted", track.Name) return nil } // GetByOwnerID fetches all tracks uploaded by a specific user. func (r *Repo) GetTracksByOwnerID(ownerID string, preloads []string) ([]models.Track, error) { var tracks []models.Track uid, err := uuid.Parse(ownerID) if err != nil { return nil, err } // Hardcode the is_public = true requirement // TODO: by desing, only public tracks are given query := r.db.Where("owner_id = ? AND is_public = ?", uid, true).Preload("Genre") for _, p := range preloads { query = query.Preload(p) } err = query.Find(&tracks).Error return tracks, err } // CountTracks returns the total number of tracks matching the search criteria. func (r *Repo) CountTracks(search string, genreID string, visibility string, userID uuid.UUID) (int64, error) { var count int64 query := r.db.Model(&models.Track{}) isLoggedIn := userID != uuid.Nil // 1. Visibility & Authentication Logic switch visibility { case "public": query = query.Where("is_public = ?", true) case "private": if isLoggedIn { query = query.Where("is_public = ? AND owner_id = ?", false, userID) } else { return 0, nil // Return 0 immediately if guest asks for private tracks } default: if isLoggedIn { query = query.Where("is_public = ? OR owner_id = ?", true, userID) } else { query = query.Where("is_public = ?", true) } } // 2. Search Functionality if search != "" { query = query.Where("LOWER(name) LIKE LOWER(?)", "%"+search+"%") } // 3. Genre Filter if genreID != "" { uid, err := uuid.Parse(genreID) if err == nil { query = query.Where("genre_id = ?", uid) } } if err := query.Count(&count).Error; err != nil { return 0, err } return count, nil } func (r *Repo) IsOwned(audioFile string, userID uuid.UUID) bool { var count int64 log.Printf("aud %v, owner %v\n", audioFile, userID) err := r.db.Model(&models.Track{}). Where("audio_file = ? AND owner_id = ?", audioFile, userID). Count(&count).Error if err != nil { utils.LogError(err) return false } return count > 0 }