Goondex/internal/db/performer_store.go
Stu Leak 16fb407a3c v0.1.0-dev4: Add web frontend with UI component library
- Implement full web interface with Go html/template server
- Add GX component library (buttons, dialogs, tables, forms, etc.)
- Create scene/performer/studio/movie detail and listing pages
- Add Adult Empire scraper for additional metadata sources
- Implement movie support with database schema
- Add import and sync services for data management
- Include comprehensive API and frontend documentation
- Add custom color scheme and responsive layout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 10:47:30 -05:00

307 lines
11 KiB
Go

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
}