package db import ( "database/sql" "fmt" "time" "git.leaktechnologies.dev/stu/Goondex/internal/model" ) // PerformerStore handles CRUD operations for performers type PerformerStore struct { db *DB } // NewPerformerStore creates a new performer store func NewPerformerStore(db *DB) *PerformerStore { return &PerformerStore{db: db} } // Create inserts a new performer func (s *PerformerStore) Create(p *model.Performer) error { now := time.Now() p.CreatedAt = now p.UpdatedAt = now activeInt := 0 if p.Active { activeInt = 1 } result, err := s.db.conn.Exec(` INSERT INTO performers ( name, aliases, gender, birthday, astrology, birthplace, ethnicity, nationality, country, eye_color, hair_color, height, weight, measurements, cup_size, tattoo_description, piercing_description, boob_job, career, career_start_year, career_end_year, date_of_death, active, image_path, image_url, poster_url, bio, source, source_id, source_numeric_id, created_at, updated_at ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ON CONFLICT(source, source_id) DO UPDATE SET name = excluded.name, aliases = excluded.aliases, gender = excluded.gender, birthday = excluded.birthday, astrology = excluded.astrology, birthplace = excluded.birthplace, ethnicity = excluded.ethnicity, nationality = excluded.nationality, country = excluded.country, eye_color = excluded.eye_color, hair_color = excluded.hair_color, height = excluded.height, weight = excluded.weight, measurements = excluded.measurements, cup_size = excluded.cup_size, tattoo_description = excluded.tattoo_description, piercing_description = excluded.piercing_description, boob_job = excluded.boob_job, career = excluded.career, career_start_year = excluded.career_start_year, career_end_year = excluded.career_end_year, date_of_death = excluded.date_of_death, active = excluded.active, image_path = excluded.image_path, image_url = excluded.image_url, poster_url = excluded.poster_url, bio = excluded.bio, source_numeric_id = excluded.source_numeric_id, updated_at = excluded.updated_at `, p.Name, p.Aliases, p.Gender, p.Birthday, p.Astrology, p.Birthplace, p.Ethnicity, p.Nationality, p.Country, p.EyeColor, p.HairColor, p.Height, p.Weight, p.Measurements, p.CupSize, p.TattooDescription, p.PiercingDescription, p.BoobJob, p.Career, p.CareerStartYear, p.CareerEndYear, p.DateOfDeath, activeInt, p.ImagePath, p.ImageURL, p.PosterURL, p.Bio, p.Source, p.SourceID, p.SourceNumericID, p.CreatedAt.Format(time.RFC3339), p.UpdatedAt.Format(time.RFC3339), ) if err != nil { return fmt.Errorf("failed to create performer: %w", err) } id, err := result.LastInsertId() if err != nil { return fmt.Errorf("failed to get last insert id: %w", err) } p.ID = id return nil } // GetByID retrieves a performer by ID func (s *PerformerStore) GetByID(id int64) (*model.Performer, error) { p := &model.Performer{} var createdAt, updatedAt string var activeInt int err := s.db.conn.QueryRow(` SELECT id, name, COALESCE(aliases, ''), COALESCE(gender, ''), COALESCE(birthday, ''), COALESCE(astrology, ''), COALESCE(birthplace, ''), COALESCE(ethnicity, ''), COALESCE(nationality, ''), COALESCE(country, ''), COALESCE(eye_color, ''), COALESCE(hair_color, ''), COALESCE(height, 0), COALESCE(weight, 0), COALESCE(measurements, ''), COALESCE(cup_size, ''), COALESCE(tattoo_description, ''), COALESCE(piercing_description, ''), COALESCE(boob_job, ''), COALESCE(career, ''), COALESCE(career_start_year, 0), COALESCE(career_end_year, 0), COALESCE(date_of_death, ''), COALESCE(active, 0), COALESCE(image_path, ''), COALESCE(image_url, ''), COALESCE(poster_url, ''), COALESCE(bio, ''), COALESCE(source, ''), COALESCE(source_id, ''), COALESCE(source_numeric_id, 0), created_at, updated_at FROM performers WHERE id = ? `, id).Scan( &p.ID, &p.Name, &p.Aliases, &p.Gender, &p.Birthday, &p.Astrology, &p.Birthplace, &p.Ethnicity, &p.Nationality, &p.Country, &p.EyeColor, &p.HairColor, &p.Height, &p.Weight, &p.Measurements, &p.CupSize, &p.TattooDescription, &p.PiercingDescription, &p.BoobJob, &p.Career, &p.CareerStartYear, &p.CareerEndYear, &p.DateOfDeath, &activeInt, &p.ImagePath, &p.ImageURL, &p.PosterURL, &p.Bio, &p.Source, &p.SourceID, &p.SourceNumericID, &createdAt, &updatedAt, ) if err == sql.ErrNoRows { return nil, fmt.Errorf("performer not found") } if err != nil { return nil, fmt.Errorf("failed to get performer: %w", err) } p.Active = (activeInt == 1) p.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) p.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt) return p, nil } // Search searches for performers by name, ordered by popularity (scene count) func (s *PerformerStore) Search(query string) ([]model.Performer, error) { rows, err := s.db.conn.Query(` SELECT p.id, p.name, COALESCE(p.aliases, ''), COALESCE(p.gender, ''), COALESCE(p.birthday, ''), COALESCE(p.astrology, ''), COALESCE(p.birthplace, ''), COALESCE(p.ethnicity, ''), COALESCE(p.nationality, ''), COALESCE(p.country, ''), COALESCE(p.eye_color, ''), COALESCE(p.hair_color, ''), COALESCE(p.height, 0), COALESCE(p.weight, 0), COALESCE(p.measurements, ''), COALESCE(p.cup_size, ''), COALESCE(p.tattoo_description, ''), COALESCE(p.piercing_description, ''), COALESCE(p.boob_job, ''), COALESCE(p.career, ''), COALESCE(p.career_start_year, 0), COALESCE(p.career_end_year, 0), COALESCE(p.date_of_death, ''), COALESCE(p.active, 0), COALESCE(p.image_path, ''), COALESCE(p.image_url, ''), COALESCE(p.poster_url, ''), COALESCE(p.bio, ''), COALESCE(p.source, ''), COALESCE(p.source_id, ''), COALESCE(p.source_numeric_id, 0), p.created_at, p.updated_at FROM performers p LEFT JOIN scene_performers sp ON p.id = sp.performer_id WHERE p.name LIKE ? OR COALESCE(p.aliases, '') LIKE ? GROUP BY p.id ORDER BY COUNT(sp.scene_id) DESC, p.name ASC `, "%"+query+"%", "%"+query+"%") if err != nil { return nil, fmt.Errorf("failed to search performers: %w", err) } defer rows.Close() var performers []model.Performer for rows.Next() { var p model.Performer var createdAt, updatedAt string var activeInt int err := rows.Scan( &p.ID, &p.Name, &p.Aliases, &p.Gender, &p.Birthday, &p.Astrology, &p.Birthplace, &p.Ethnicity, &p.Nationality, &p.Country, &p.EyeColor, &p.HairColor, &p.Height, &p.Weight, &p.Measurements, &p.CupSize, &p.TattooDescription, &p.PiercingDescription, &p.BoobJob, &p.Career, &p.CareerStartYear, &p.CareerEndYear, &p.DateOfDeath, &activeInt, &p.ImagePath, &p.ImageURL, &p.PosterURL, &p.Bio, &p.Source, &p.SourceID, &p.SourceNumericID, &createdAt, &updatedAt, ) if err != nil { return nil, fmt.Errorf("failed to scan performer: %w", err) } p.Active = (activeInt == 1) p.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) p.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt) performers = append(performers, p) } return performers, nil } // GetSceneCount returns the number of scenes associated with a performer func (s *PerformerStore) GetSceneCount(performerID int64) (int, error) { var count int err := s.db.conn.QueryRow(` SELECT COUNT(*) FROM scene_performers WHERE performer_id = ? `, performerID).Scan(&count) if err != nil { return 0, fmt.Errorf("failed to count scenes: %w", err) } return count, nil } // Update updates an existing performer func (s *PerformerStore) Update(p *model.Performer) error { p.UpdatedAt = time.Now() result, err := s.db.conn.Exec(` UPDATE performers SET name = ?, aliases = ?, nationality = ?, country = ?, gender = ?, image_path = ?, image_url = ?, bio = ?, source = ?, source_id = ?, updated_at = ? WHERE id = ? `, p.Name, p.Aliases, p.Nationality, p.Country, p.Gender, p.ImagePath, p.ImageURL, p.Bio, p.Source, p.SourceID, p.UpdatedAt.Format(time.RFC3339), p.ID) if err != nil { return fmt.Errorf("failed to update performer: %w", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get rows affected: %w", err) } if rows == 0 { return fmt.Errorf("performer not found") } return nil } // Delete deletes a performer by ID func (s *PerformerStore) Delete(id int64) error { result, err := s.db.conn.Exec("DELETE FROM performers WHERE id = ?", id) if err != nil { return fmt.Errorf("failed to delete performer: %w", err) } rows, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get rows affected: %w", err) } if rows == 0 { return fmt.Errorf("performer not found") } return nil } // Upsert inserts or updates a performer based on source_id func (s *PerformerStore) Upsert(p *model.Performer) error { // Try to find existing performer by source_id existing, err := s.GetBySourceID(p.Source, p.SourceID) if err == nil && existing != nil { // Update existing p.ID = existing.ID return s.Update(p) } // Create new return s.Create(p) } // GetBySourceID retrieves a performer by its source and source_id func (s *PerformerStore) GetBySourceID(source, sourceID string) (*model.Performer, error) { var p model.Performer var activeInt int err := s.db.conn.QueryRow(` SELECT id, name, COALESCE(aliases, ''), COALESCE(gender, ''), COALESCE(birthday, ''), COALESCE(astrology, ''), COALESCE(birthplace, ''), COALESCE(ethnicity, ''), COALESCE(nationality, ''), COALESCE(country, ''), COALESCE(eye_color, ''), COALESCE(hair_color, ''), COALESCE(height, 0), COALESCE(weight, 0), COALESCE(measurements, ''), COALESCE(cup_size, ''), COALESCE(tattoo_description, ''), COALESCE(piercing_description, ''), COALESCE(boob_job, ''), COALESCE(career, ''), COALESCE(career_start_year, 0), COALESCE(career_end_year, 0), COALESCE(date_of_death, ''), COALESCE(active, 0), COALESCE(image_path, ''), COALESCE(image_url, ''), COALESCE(poster_url, ''), COALESCE(bio, ''), COALESCE(source, ''), COALESCE(source_id, ''), COALESCE(source_numeric_id, 0), created_at, updated_at FROM performers WHERE source = ? AND source_id = ? `, source, sourceID).Scan( &p.ID, &p.Name, &p.Aliases, &p.Gender, &p.Birthday, &p.Astrology, &p.Birthplace, &p.Ethnicity, &p.Nationality, &p.Country, &p.EyeColor, &p.HairColor, &p.Height, &p.Weight, &p.Measurements, &p.CupSize, &p.TattooDescription, &p.PiercingDescription, &p.BoobJob, &p.Career, &p.CareerStartYear, &p.CareerEndYear, &p.DateOfDeath, &activeInt, &p.ImagePath, &p.ImageURL, &p.PosterURL, &p.Bio, &p.Source, &p.SourceID, &p.SourceNumericID, &p.CreatedAt, &p.UpdatedAt, ) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, fmt.Errorf("failed to get performer: %w", err) } p.Active = activeInt == 1 return &p, nil }