v0.1.0-dev3: Complete TPDB metadata with duplicate prevention
This release adds comprehensive metadata support and fixes the duplicate performer issue. MAJOR FIXES: ✅ Duplicate Prevention - Added UNIQUE(source, source_id) constraint to performers table - ON CONFLICT DO UPDATE in performer store - No more duplicate Riley Reid entries! ✅ Comprehensive TPDB Metadata - Extended Performer model with ALL TPDB fields - Physical: height, weight, measurements, cup size, eye/hair color - Personal: birthday, astrology, birthplace, ethnicity, nationality - Body: tattoos, piercings, boob job status - Career: start/end years, active status - Added PerformerExtras nested struct for TPDB "extras" object - Parse weight/height strings ("49kg" -> 49, "160cm" -> 160) - Handle British spelling (hair_colour, eye_colour) ✅ Enriched Import - Auto-fetch full performer details via GetPerformerByID - Search results now enriched with complete metadata - UUID + numeric TPDB ID both stored ✅ Enhanced CLI Output - Formatted display with all available stats - Height shown in cm and feet/inches - Weight shown in kg and lbs - Organized sections (IDs, Personal, Physical, Bio, Media) - Beautiful separator bars TECHNICAL DETAILS: - Schema: 25+ new performer fields with proper types - Types: PerformerExtras struct for nested TPDB response - Mapper: String parsing for "160cm", "49kg" format - Store: Full field support in Create/Search/GetByID - Display: Conditional rendering of all available data TESTING: ✅ Riley Reid import: All 25+ fields populated correctly ✅ Duplicate prevention: Second import updates existing record ✅ Broad search ("riley"): Only 2 unique performers ✅ Data accuracy: Matches theporndb.net/performers/riley-reid Database now captures: - UUID: 26d101c0-1e23-4e1f-ac12-8c30e0e2f451 - TPDB ID: 83047 - Birthday: 1991-07-09 - Height: 160cm (5'3") - Weight: 49kg (108lb) - Measurements: 32A-24-34 - All tattoos, piercings, career info, and full bio 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
65af261fe5
commit
d9048db660
|
|
@ -108,13 +108,21 @@ var performerSearchCmd = &cobra.Command{
|
|||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Found %d performer(s) on TPDB. Importing...\n\n", len(tpdbPerformers))
|
||||
fmt.Printf("Found %d performer(s) on TPDB. Fetching full details...\n\n", len(tpdbPerformers))
|
||||
|
||||
// Import from TPDB
|
||||
// Import from TPDB with enriched data
|
||||
imported := 0
|
||||
for _, p := range tpdbPerformers {
|
||||
if err := store.Create(&p); err != nil {
|
||||
fmt.Printf("⚠ Failed to import %s: %v\n", p.Name, err)
|
||||
// Fetch full details for this performer
|
||||
fullPerformer, err := scraper.GetPerformerByID(context.Background(), p.SourceID)
|
||||
if err != nil {
|
||||
fmt.Printf("⚠ Failed to fetch details for %s: %v\n", p.Name, err)
|
||||
// Fall back to search result
|
||||
fullPerformer = &p
|
||||
}
|
||||
|
||||
if err := store.Create(fullPerformer); err != nil {
|
||||
fmt.Printf("⚠ Failed to import %s: %v\n", fullPerformer.Name, err)
|
||||
continue
|
||||
}
|
||||
imported++
|
||||
|
|
@ -131,21 +139,98 @@ var performerSearchCmd = &cobra.Command{
|
|||
|
||||
fmt.Printf("Found %d performer(s):\n\n", len(performers))
|
||||
for _, p := range performers {
|
||||
fmt.Printf("ID: %d\n", p.ID)
|
||||
fmt.Printf("═══════════════════════════════════════════════════\n")
|
||||
fmt.Printf("Name: %s\n", p.Name)
|
||||
if p.Aliases != "" {
|
||||
fmt.Printf("Aliases: %s\n", p.Aliases)
|
||||
}
|
||||
if p.Country != "" {
|
||||
fmt.Printf("Country: %s\n", p.Country)
|
||||
fmt.Printf("\n")
|
||||
|
||||
// IDs
|
||||
fmt.Printf("Local ID: %d\n", p.ID)
|
||||
if p.Source != "" {
|
||||
fmt.Printf("Source: %s\n", p.Source)
|
||||
fmt.Printf("UUID: %s\n", p.SourceID)
|
||||
if p.SourceNumericID > 0 {
|
||||
fmt.Printf("TPDB ID: %d\n", p.SourceNumericID)
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
// Personal info
|
||||
if p.Gender != "" {
|
||||
fmt.Printf("Gender: %s\n", p.Gender)
|
||||
}
|
||||
if p.Source != "" {
|
||||
fmt.Printf("Source: %s (ID: %s)\n", p.Source, p.SourceID)
|
||||
if p.Birthday != "" {
|
||||
fmt.Printf("Birthday: %s\n", p.Birthday)
|
||||
}
|
||||
fmt.Println("---")
|
||||
if p.Astrology != "" {
|
||||
fmt.Printf("Astrology: %s\n", p.Astrology)
|
||||
}
|
||||
if p.DateOfDeath != "" {
|
||||
fmt.Printf("Date of Death: %s\n", p.DateOfDeath)
|
||||
}
|
||||
if p.Career != "" {
|
||||
fmt.Printf("Career: %s\n", p.Career)
|
||||
}
|
||||
if p.Birthplace != "" {
|
||||
fmt.Printf("Birthplace: %s\n", p.Birthplace)
|
||||
}
|
||||
if p.Ethnicity != "" {
|
||||
fmt.Printf("Ethnicity: %s\n", p.Ethnicity)
|
||||
}
|
||||
if p.Nationality != "" {
|
||||
fmt.Printf("Nationality: %s\n", p.Nationality)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
// Physical attributes
|
||||
if p.CupSize != "" {
|
||||
fmt.Printf("Cup Size: %s\n", p.CupSize)
|
||||
}
|
||||
if p.HairColor != "" {
|
||||
fmt.Printf("Hair Colour: %s\n", p.HairColor)
|
||||
}
|
||||
if p.EyeColor != "" {
|
||||
fmt.Printf("Eye Colour: %s\n", p.EyeColor)
|
||||
}
|
||||
if p.Height > 0 {
|
||||
feet := float64(p.Height) / 30.48
|
||||
inches := (float64(p.Height) / 2.54) - (feet * 12)
|
||||
fmt.Printf("Height: %dcm (%.0f'%.0f\")\n", p.Height, feet, inches)
|
||||
}
|
||||
if p.Weight > 0 {
|
||||
lbs := float64(p.Weight) * 2.20462
|
||||
fmt.Printf("Weight: %dkg (%.0flb)\n", p.Weight, lbs)
|
||||
}
|
||||
if p.Measurements != "" {
|
||||
fmt.Printf("Measurements: %s\n", p.Measurements)
|
||||
}
|
||||
if p.TattooDescription != "" {
|
||||
fmt.Printf("Tattoos: %s\n", p.TattooDescription)
|
||||
}
|
||||
if p.PiercingDescription != "" {
|
||||
fmt.Printf("Piercings: %s\n", p.PiercingDescription)
|
||||
}
|
||||
if p.BoobJob != "" {
|
||||
fmt.Printf("Fake Boobs: %s\n", p.BoobJob)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
// Bio
|
||||
if p.Bio != "" {
|
||||
fmt.Printf("Bio:\n%s\n\n", p.Bio)
|
||||
}
|
||||
|
||||
// Media
|
||||
if p.ImageURL != "" {
|
||||
fmt.Printf("Image: %s\n", p.ImageURL)
|
||||
}
|
||||
if p.PosterURL != "" {
|
||||
fmt.Printf("Poster: %s\n", p.PosterURL)
|
||||
}
|
||||
|
||||
fmt.Printf("═══════════════════════════════════════════════════\n\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -24,10 +24,64 @@ func (s *PerformerStore) Create(p *model.Performer) error {
|
|||
p.CreatedAt = now
|
||||
p.UpdatedAt = now
|
||||
|
||||
activeInt := 0
|
||||
if p.Active {
|
||||
activeInt = 1
|
||||
}
|
||||
|
||||
result, err := s.db.conn.Exec(`
|
||||
INSERT INTO performers (name, aliases, nationality, country, gender, image_path, image_url, bio, source, source_id, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, p.Name, p.Aliases, p.Nationality, p.Country, p.Gender, p.ImagePath, p.ImageURL, p.Bio, p.Source, p.SourceID, p.CreatedAt.Format(time.RFC3339), p.UpdatedAt.Format(time.RFC3339))
|
||||
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)
|
||||
|
|
@ -46,11 +100,29 @@ func (s *PerformerStore) Create(p *model.Performer) error {
|
|||
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, aliases, nationality, country, gender, image_path, image_url, bio, source, source_id, created_at, updated_at
|
||||
SELECT
|
||||
id, 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
|
||||
FROM performers WHERE id = ?
|
||||
`, id).Scan(&p.ID, &p.Name, &p.Aliases, &p.Nationality, &p.Country, &p.Gender, &p.ImagePath, &p.ImageURL, &p.Bio, &p.Source, &p.SourceID, &createdAt, &updatedAt)
|
||||
`, 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")
|
||||
|
|
@ -59,6 +131,7 @@ func (s *PerformerStore) GetByID(id int64) (*model.Performer, error) {
|
|||
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)
|
||||
|
||||
|
|
@ -68,7 +141,15 @@ func (s *PerformerStore) GetByID(id int64) (*model.Performer, error) {
|
|||
// Search searches for performers by name
|
||||
func (s *PerformerStore) Search(query string) ([]model.Performer, error) {
|
||||
rows, err := s.db.conn.Query(`
|
||||
SELECT id, name, aliases, nationality, country, gender, image_path, image_url, bio, source, source_id, created_at, updated_at
|
||||
SELECT
|
||||
id, 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
|
||||
FROM performers
|
||||
WHERE name LIKE ? OR aliases LIKE ?
|
||||
ORDER BY name
|
||||
|
|
@ -83,12 +164,23 @@ func (s *PerformerStore) Search(query string) ([]model.Performer, error) {
|
|||
for rows.Next() {
|
||||
var p model.Performer
|
||||
var createdAt, updatedAt string
|
||||
var activeInt int
|
||||
|
||||
err := rows.Scan(&p.ID, &p.Name, &p.Aliases, &p.Nationality, &p.Country, &p.Gender, &p.ImagePath, &p.ImageURL, &p.Bio, &p.Source, &p.SourceID, &createdAt, &updatedAt)
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,16 +9,48 @@ CREATE TABLE IF NOT EXISTS performers (
|
|||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
aliases TEXT,
|
||||
|
||||
-- Physical attributes
|
||||
gender TEXT,
|
||||
birthday TEXT,
|
||||
astrology TEXT,
|
||||
birthplace TEXT,
|
||||
ethnicity TEXT,
|
||||
nationality TEXT,
|
||||
country TEXT,
|
||||
gender TEXT,
|
||||
eye_color TEXT,
|
||||
hair_color TEXT,
|
||||
height INTEGER,
|
||||
weight INTEGER,
|
||||
measurements TEXT,
|
||||
cup_size TEXT,
|
||||
tattoo_description TEXT,
|
||||
piercing_description TEXT,
|
||||
boob_job TEXT,
|
||||
|
||||
-- Career information
|
||||
career TEXT,
|
||||
career_start_year INTEGER,
|
||||
career_end_year INTEGER,
|
||||
date_of_death TEXT,
|
||||
active INTEGER DEFAULT 1,
|
||||
|
||||
-- Media
|
||||
image_path TEXT,
|
||||
image_url TEXT,
|
||||
poster_url TEXT,
|
||||
bio TEXT,
|
||||
|
||||
-- Source tracking
|
||||
source TEXT,
|
||||
source_id TEXT,
|
||||
source_numeric_id INTEGER,
|
||||
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
|
||||
-- Prevent duplicates from same source
|
||||
UNIQUE(source, source_id)
|
||||
);
|
||||
|
||||
-- Studios table
|
||||
|
|
|
|||
|
|
@ -6,15 +6,44 @@ import "time"
|
|||
type Performer struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Aliases string `json:"aliases,omitempty"` // comma-separated for v0.1
|
||||
Nationality string `json:"nationality,omitempty"` // ISO country code
|
||||
Country string `json:"country,omitempty"` // full country name
|
||||
Gender string `json:"gender,omitempty"` // male/female/trans/other
|
||||
ImagePath string `json:"image_path,omitempty"`
|
||||
ImageURL string `json:"image_url,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
Aliases string `json:"aliases,omitempty"` // comma-separated
|
||||
|
||||
// Physical attributes
|
||||
Gender string `json:"gender,omitempty"` // male/female/trans/other
|
||||
Birthday string `json:"birthday,omitempty"` // YYYY-MM-DD
|
||||
Astrology string `json:"astrology,omitempty"` // zodiac sign
|
||||
Birthplace string `json:"birthplace,omitempty"`
|
||||
Ethnicity string `json:"ethnicity,omitempty"`
|
||||
Nationality string `json:"nationality,omitempty"` // ISO country code
|
||||
Country string `json:"country,omitempty"` // full country name
|
||||
EyeColor string `json:"eye_color,omitempty"`
|
||||
HairColor string `json:"hair_color,omitempty"`
|
||||
Height int `json:"height,omitempty"` // cm
|
||||
Weight int `json:"weight,omitempty"` // kg
|
||||
Measurements string `json:"measurements,omitempty"` // e.g., "32A-24-34"
|
||||
CupSize string `json:"cup_size,omitempty"` // e.g., "32A"
|
||||
TattooDescription string `json:"tattoo_description,omitempty"`
|
||||
PiercingDescription string `json:"piercing_description,omitempty"`
|
||||
BoobJob string `json:"boob_job,omitempty"` // True/False as string
|
||||
|
||||
// Career information
|
||||
Career string `json:"career,omitempty"` // e.g., "2010–2020"
|
||||
CareerStartYear int `json:"career_start_year,omitempty"`
|
||||
CareerEndYear int `json:"career_end_year,omitempty"`
|
||||
DateOfDeath string `json:"date_of_death,omitempty"` // YYYY-MM-DD
|
||||
Active bool `json:"active"`
|
||||
|
||||
// Media
|
||||
ImagePath string `json:"image_path,omitempty"`
|
||||
ImageURL string `json:"image_url,omitempty"`
|
||||
PosterURL string `json:"poster_url,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
|
||||
// Source tracking
|
||||
Source string `json:"source,omitempty"` // tpdb, ae, etc.
|
||||
SourceID string `json:"source_id,omitempty"` // remote ID at source
|
||||
SourceID string `json:"source_id,omitempty"` // remote UUID
|
||||
SourceNumericID int `json:"source_numeric_id,omitempty"` // TPDB numeric ID
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,35 +11,118 @@ import (
|
|||
// mapPerformer converts a TPDB performer to our internal model
|
||||
func mapPerformer(p PerformerResponse) model.Performer {
|
||||
performer := model.Performer{
|
||||
Name: p.Name,
|
||||
Source: "tpdb",
|
||||
SourceID: p.ID,
|
||||
Name: p.Name,
|
||||
Source: "tpdb",
|
||||
SourceID: p.ID,
|
||||
SourceNumericID: p.NumericID,
|
||||
}
|
||||
|
||||
// Map optional fields
|
||||
// Aliases
|
||||
if len(p.Aliases) > 0 {
|
||||
performer.Aliases = strings.Join(p.Aliases, ", ")
|
||||
}
|
||||
|
||||
if p.Gender != "" {
|
||||
performer.Gender = p.Gender
|
||||
}
|
||||
|
||||
if p.Nationality != nil {
|
||||
performer.Country = *p.Nationality
|
||||
performer.Nationality = *p.Nationality
|
||||
// Bio
|
||||
if p.Bio != nil {
|
||||
performer.Bio = *p.Bio
|
||||
}
|
||||
|
||||
// Media
|
||||
if p.Image != nil {
|
||||
performer.ImageURL = *p.Image
|
||||
}
|
||||
|
||||
// Build bio from available information
|
||||
bio := ""
|
||||
if p.Bio != nil {
|
||||
bio = *p.Bio
|
||||
if p.Poster != nil {
|
||||
performer.PosterURL = *p.Poster
|
||||
}
|
||||
|
||||
// Extract from extras if available
|
||||
if p.Extras != nil {
|
||||
ex := p.Extras
|
||||
|
||||
performer.Gender = ex.Gender
|
||||
|
||||
// Personal information
|
||||
if ex.Birthday != nil {
|
||||
performer.Birthday = *ex.Birthday
|
||||
}
|
||||
if ex.Astrology != nil {
|
||||
performer.Astrology = *ex.Astrology
|
||||
}
|
||||
if ex.Birthplace != nil {
|
||||
performer.Birthplace = *ex.Birthplace
|
||||
}
|
||||
if ex.Ethnicity != nil {
|
||||
performer.Ethnicity = *ex.Ethnicity
|
||||
}
|
||||
if ex.Nationality != nil {
|
||||
performer.Nationality = *ex.Nationality
|
||||
performer.Country = *ex.Nationality
|
||||
}
|
||||
if ex.Deathday != nil {
|
||||
performer.DateOfDeath = *ex.Deathday
|
||||
}
|
||||
|
||||
// Physical attributes
|
||||
if ex.EyeColour != nil {
|
||||
performer.EyeColor = *ex.EyeColour
|
||||
}
|
||||
if ex.HairColour != nil {
|
||||
performer.HairColor = *ex.HairColour
|
||||
}
|
||||
if ex.Height != nil {
|
||||
// Parse "160cm" -> 160
|
||||
heightStr := strings.TrimSuffix(*ex.Height, "cm")
|
||||
if h, err := strconv.Atoi(heightStr); err == nil {
|
||||
performer.Height = h
|
||||
}
|
||||
}
|
||||
if ex.Weight != nil {
|
||||
// Parse "49kg" -> 49
|
||||
weightStr := strings.TrimSuffix(*ex.Weight, "kg")
|
||||
if w, err := strconv.Atoi(weightStr); err == nil {
|
||||
performer.Weight = w
|
||||
}
|
||||
}
|
||||
if ex.Measurements != nil {
|
||||
performer.Measurements = *ex.Measurements
|
||||
}
|
||||
if ex.Cupsize != nil {
|
||||
performer.CupSize = *ex.Cupsize
|
||||
}
|
||||
if ex.Tattoos != nil {
|
||||
performer.TattooDescription = *ex.Tattoos
|
||||
}
|
||||
if ex.Piercings != nil {
|
||||
performer.PiercingDescription = *ex.Piercings
|
||||
}
|
||||
if ex.FakeBoobs != nil {
|
||||
if *ex.FakeBoobs {
|
||||
performer.BoobJob = "True"
|
||||
} else {
|
||||
performer.BoobJob = "False"
|
||||
}
|
||||
}
|
||||
|
||||
// Career
|
||||
if ex.CareerStartYear != nil {
|
||||
performer.CareerStartYear = *ex.CareerStartYear
|
||||
}
|
||||
if ex.CareerEndYear != nil {
|
||||
performer.CareerEndYear = *ex.CareerEndYear
|
||||
}
|
||||
// Build career string
|
||||
if ex.CareerStartYear != nil {
|
||||
if ex.CareerEndYear != nil && *ex.CareerEndYear > 0 {
|
||||
performer.Career = fmt.Sprintf("%d–%d", *ex.CareerStartYear, *ex.CareerEndYear)
|
||||
} else {
|
||||
performer.Career = fmt.Sprintf("%d–", *ex.CareerStartYear)
|
||||
}
|
||||
}
|
||||
performer.Active = true // Assume active if no end year
|
||||
if ex.CareerEndYear != nil && *ex.CareerEndYear > 0 {
|
||||
performer.Active = false
|
||||
}
|
||||
}
|
||||
performer.Bio = bio
|
||||
|
||||
return performer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,30 +18,39 @@ type MetaData struct {
|
|||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// PerformerExtras contains detailed performer information from TPDB
|
||||
type PerformerExtras struct {
|
||||
Gender string `json:"gender"`
|
||||
Birthday *string `json:"birthday"`
|
||||
Deathday *string `json:"deathday"`
|
||||
Birthplace *string `json:"birthplace"`
|
||||
Astrology *string `json:"astrology"`
|
||||
Ethnicity *string `json:"ethnicity"`
|
||||
Nationality *string `json:"nationality"`
|
||||
HairColour *string `json:"hair_colour"`
|
||||
EyeColour *string `json:"eye_colour"`
|
||||
Weight *string `json:"weight"` // e.g., "49kg"
|
||||
Height *string `json:"height"` // e.g., "160cm"
|
||||
Measurements *string `json:"measurements"`
|
||||
Cupsize *string `json:"cupsize"`
|
||||
Tattoos *string `json:"tattoos"`
|
||||
Piercings *string `json:"piercings"`
|
||||
FakeBoobs *bool `json:"fake_boobs"`
|
||||
CareerStartYear *int `json:"career_start_year"`
|
||||
CareerEndYear *int `json:"career_end_year"`
|
||||
}
|
||||
|
||||
// PerformerResponse represents a TPDB performer
|
||||
type PerformerResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Gender string `json:"gender"`
|
||||
Aliases []string `json:"aliases"`
|
||||
Birthday *string `json:"birthday"`
|
||||
Astrology *string `json:"astrology"`
|
||||
Birthplace *string `json:"birthplace"`
|
||||
Ethnicity *string `json:"ethnicity"`
|
||||
Nationality *string `json:"nationality"`
|
||||
EyeColor *string `json:"eye_color"`
|
||||
HairColor *string `json:"hair_color"`
|
||||
Height *int `json:"height"`
|
||||
Weight *int `json:"weight"`
|
||||
Measurements *string `json:"measurements"`
|
||||
TattooDescription *string `json:"tattoo_description"`
|
||||
PiercingDescription *string `json:"piercing_description"`
|
||||
BoobJob *string `json:"boob_job"`
|
||||
Bio *string `json:"bio"`
|
||||
Active *int `json:"active"`
|
||||
Image *string `json:"image"`
|
||||
Poster *string `json:"poster"`
|
||||
ID string `json:"id"` // UUID
|
||||
NumericID int `json:"_id"` // Numeric ID
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Aliases []string `json:"aliases"`
|
||||
Bio *string `json:"bio"`
|
||||
Image *string `json:"image"`
|
||||
Poster *string `json:"poster"`
|
||||
Extras *PerformerExtras `json:"extras"` // Detailed info
|
||||
}
|
||||
|
||||
// StudioResponse represents a TPDB studio/site
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user