Performer refactor (#3057)

* Separate performer model from sqlite model
* Use GenderEnum for gender
This commit is contained in:
WithoutPants 2022-10-31 14:58:01 +11:00 committed by GitHub
parent b1fa933868
commit 270bc317cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1558 additions and 1226 deletions

View file

@ -185,21 +185,6 @@ func (t changesetTranslator) optionalIntFromString(value *string, field string)
return models.NewOptionalInt(vv), nil return models.NewOptionalInt(vv), nil
} }
func (t changesetTranslator) nullBool(value *bool, field string) *sql.NullBool {
if !t.hasField(field) {
return nil
}
ret := &sql.NullBool{}
if value != nil {
ret.Bool = *value
ret.Valid = true
}
return ret
}
func (t changesetTranslator) optionalBool(value *bool, field string) models.OptionalBool { func (t changesetTranslator) optionalBool(value *bool, field string) models.OptionalBool {
if !t.hasField(field) { if !t.hasField(field) {
return models.OptionalBool{} return models.OptionalBool{}

View file

@ -10,6 +10,7 @@ import (
"github.com/stashapp/stash/internal/static" "github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/hash" "github.com/stashapp/stash/pkg/hash"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
) )
type imageBox struct { type imageBox struct {
@ -86,7 +87,7 @@ func initialiseCustomImages() {
} }
} }
func getRandomPerformerImageUsingName(name, gender, customPath string) ([]byte, error) { func getRandomPerformerImageUsingName(name string, gender models.GenderEnum, customPath string) ([]byte, error) {
var box *imageBox var box *imageBox
// If we have a custom path, we should return a new box in the given path. // If we have a custom path, we should return a new box in the given path.
@ -95,10 +96,10 @@ func getRandomPerformerImageUsingName(name, gender, customPath string) ([]byte,
} }
if box == nil { if box == nil {
switch strings.ToUpper(gender) { switch gender {
case "FEMALE": case models.GenderEnumFemale:
box = performerBox box = performerBox
case "MALE": case models.GenderEnumMale:
box = performerBoxMale box = performerBoxMale
default: default:
box = performerBox box = performerBox

View file

@ -2,7 +2,6 @@ package api
import ( import (
"context" "context"
"time"
"github.com/stashapp/stash/internal/api/urlbuilders" "github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/gallery" "github.com/stashapp/stash/pkg/gallery"
@ -10,131 +9,14 @@ import (
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
) )
func (r *performerResolver) Name(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Name.Valid {
return &obj.Name.String, nil
}
return nil, nil
}
func (r *performerResolver) URL(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.URL.Valid {
return &obj.URL.String, nil
}
return nil, nil
}
func (r *performerResolver) Gender(ctx context.Context, obj *models.Performer) (*models.GenderEnum, error) {
var ret models.GenderEnum
if obj.Gender.Valid {
ret = models.GenderEnum(obj.Gender.String)
if ret.IsValid() {
return &ret, nil
}
}
return nil, nil
}
func (r *performerResolver) Twitter(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Twitter.Valid {
return &obj.Twitter.String, nil
}
return nil, nil
}
func (r *performerResolver) Instagram(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Instagram.Valid {
return &obj.Instagram.String, nil
}
return nil, nil
}
func (r *performerResolver) Birthdate(ctx context.Context, obj *models.Performer) (*string, error) { func (r *performerResolver) Birthdate(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Birthdate.Valid { if obj.Birthdate != nil {
return &obj.Birthdate.String, nil ret := obj.Birthdate.String()
return &ret, nil
} }
return nil, nil return nil, nil
} }
func (r *performerResolver) Ethnicity(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Ethnicity.Valid {
return &obj.Ethnicity.String, nil
}
return nil, nil
}
func (r *performerResolver) Country(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Country.Valid {
return &obj.Country.String, nil
}
return nil, nil
}
func (r *performerResolver) EyeColor(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.EyeColor.Valid {
return &obj.EyeColor.String, nil
}
return nil, nil
}
func (r *performerResolver) Height(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Height.Valid {
return &obj.Height.String, nil
}
return nil, nil
}
func (r *performerResolver) Measurements(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Measurements.Valid {
return &obj.Measurements.String, nil
}
return nil, nil
}
func (r *performerResolver) FakeTits(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.FakeTits.Valid {
return &obj.FakeTits.String, nil
}
return nil, nil
}
func (r *performerResolver) CareerLength(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.CareerLength.Valid {
return &obj.CareerLength.String, nil
}
return nil, nil
}
func (r *performerResolver) Tattoos(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Tattoos.Valid {
return &obj.Tattoos.String, nil
}
return nil, nil
}
func (r *performerResolver) Piercings(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Piercings.Valid {
return &obj.Piercings.String, nil
}
return nil, nil
}
func (r *performerResolver) Aliases(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Aliases.Valid {
return &obj.Aliases.String, nil
}
return nil, nil
}
func (r *performerResolver) Favorite(ctx context.Context, obj *models.Performer) (bool, error) {
if obj.Favorite.Valid {
return obj.Favorite.Bool, nil
}
return false, nil
}
func (r *performerResolver) ImagePath(ctx context.Context, obj *models.Performer) (*string, error) { func (r *performerResolver) ImagePath(ctx context.Context, obj *models.Performer) (*string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string) baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
imagePath := urlbuilders.NewPerformerURLBuilder(baseURL, obj).GetPerformerImageURL() imagePath := urlbuilders.NewPerformerURLBuilder(baseURL, obj).GetPerformerImageURL()
@ -212,51 +94,14 @@ func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer)
return stashIDsSliceToPtrSlice(ret), nil return stashIDsSliceToPtrSlice(ret), nil
} }
func (r *performerResolver) Rating(ctx context.Context, obj *models.Performer) (*int, error) {
if obj.Rating.Valid {
rating := int(obj.Rating.Int64)
return &rating, nil
}
return nil, nil
}
func (r *performerResolver) Details(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Details.Valid {
return &obj.Details.String, nil
}
return nil, nil
}
func (r *performerResolver) DeathDate(ctx context.Context, obj *models.Performer) (*string, error) { func (r *performerResolver) DeathDate(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.DeathDate.Valid { if obj.DeathDate != nil {
return &obj.DeathDate.String, nil ret := obj.DeathDate.String()
return &ret, nil
} }
return nil, nil return nil, nil
} }
func (r *performerResolver) HairColor(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.HairColor.Valid {
return &obj.HairColor.String, nil
}
return nil, nil
}
func (r *performerResolver) Weight(ctx context.Context, obj *models.Performer) (*int, error) {
if obj.Weight.Valid {
weight := int(obj.Weight.Int64)
return &weight, nil
}
return nil, nil
}
func (r *performerResolver) CreatedAt(ctx context.Context, obj *models.Performer) (*time.Time, error) {
return &obj.CreatedAt.Timestamp, nil
}
func (r *performerResolver) UpdatedAt(ctx context.Context, obj *models.Performer) (*time.Time, error) {
return &obj.UpdatedAt.Timestamp, nil
}
func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) { func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) {
if err := r.withTxn(ctx, func(ctx context.Context) error { if err := r.withTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Movie.FindByPerformerID(ctx, obj.ID) ret, err = r.repository.Movie.FindByPerformerID(ctx, obj.ID)

View file

@ -2,7 +2,6 @@ package api
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
@ -54,78 +53,75 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
// Populate a new performer from the input // Populate a new performer from the input
currentTime := time.Now() currentTime := time.Now()
newPerformer := models.Performer{ newPerformer := models.Performer{
Name: input.Name,
Checksum: checksum, Checksum: checksum,
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, CreatedAt: currentTime,
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, UpdatedAt: currentTime,
} }
newPerformer.Name = sql.NullString{String: input.Name, Valid: true}
if input.URL != nil { if input.URL != nil {
newPerformer.URL = sql.NullString{String: *input.URL, Valid: true} newPerformer.URL = *input.URL
} }
if input.Gender != nil { if input.Gender != nil {
newPerformer.Gender = sql.NullString{String: input.Gender.String(), Valid: true} newPerformer.Gender = *input.Gender
} }
if input.Birthdate != nil { if input.Birthdate != nil {
newPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true} d := models.NewDate(*input.Birthdate)
newPerformer.Birthdate = &d
} }
if input.Ethnicity != nil { if input.Ethnicity != nil {
newPerformer.Ethnicity = sql.NullString{String: *input.Ethnicity, Valid: true} newPerformer.Ethnicity = *input.Ethnicity
} }
if input.Country != nil { if input.Country != nil {
newPerformer.Country = sql.NullString{String: *input.Country, Valid: true} newPerformer.Country = *input.Country
} }
if input.EyeColor != nil { if input.EyeColor != nil {
newPerformer.EyeColor = sql.NullString{String: *input.EyeColor, Valid: true} newPerformer.EyeColor = *input.EyeColor
} }
if input.Height != nil { if input.Height != nil {
newPerformer.Height = sql.NullString{String: *input.Height, Valid: true} newPerformer.Height = *input.Height
} }
if input.Measurements != nil { if input.Measurements != nil {
newPerformer.Measurements = sql.NullString{String: *input.Measurements, Valid: true} newPerformer.Measurements = *input.Measurements
} }
if input.FakeTits != nil { if input.FakeTits != nil {
newPerformer.FakeTits = sql.NullString{String: *input.FakeTits, Valid: true} newPerformer.FakeTits = *input.FakeTits
} }
if input.CareerLength != nil { if input.CareerLength != nil {
newPerformer.CareerLength = sql.NullString{String: *input.CareerLength, Valid: true} newPerformer.CareerLength = *input.CareerLength
} }
if input.Tattoos != nil { if input.Tattoos != nil {
newPerformer.Tattoos = sql.NullString{String: *input.Tattoos, Valid: true} newPerformer.Tattoos = *input.Tattoos
} }
if input.Piercings != nil { if input.Piercings != nil {
newPerformer.Piercings = sql.NullString{String: *input.Piercings, Valid: true} newPerformer.Piercings = *input.Piercings
} }
if input.Aliases != nil { if input.Aliases != nil {
newPerformer.Aliases = sql.NullString{String: *input.Aliases, Valid: true} newPerformer.Aliases = *input.Aliases
} }
if input.Twitter != nil { if input.Twitter != nil {
newPerformer.Twitter = sql.NullString{String: *input.Twitter, Valid: true} newPerformer.Twitter = *input.Twitter
} }
if input.Instagram != nil { if input.Instagram != nil {
newPerformer.Instagram = sql.NullString{String: *input.Instagram, Valid: true} newPerformer.Instagram = *input.Instagram
} }
if input.Favorite != nil { if input.Favorite != nil {
newPerformer.Favorite = sql.NullBool{Bool: *input.Favorite, Valid: true} newPerformer.Favorite = *input.Favorite
} else {
newPerformer.Favorite = sql.NullBool{Bool: false, Valid: true}
} }
if input.Rating != nil { if input.Rating != nil {
newPerformer.Rating = sql.NullInt64{Int64: int64(*input.Rating), Valid: true} newPerformer.Rating = input.Rating
} else {
newPerformer.Rating = sql.NullInt64{Valid: false}
} }
if input.Details != nil { if input.Details != nil {
newPerformer.Details = sql.NullString{String: *input.Details, Valid: true} newPerformer.Details = *input.Details
} }
if input.DeathDate != nil { if input.DeathDate != nil {
newPerformer.DeathDate = models.SQLiteDate{String: *input.DeathDate, Valid: true} d := models.NewDate(*input.DeathDate)
newPerformer.DeathDate = &d
} }
if input.HairColor != nil { if input.HairColor != nil {
newPerformer.HairColor = sql.NullString{String: *input.HairColor, Valid: true} newPerformer.HairColor = *input.HairColor
} }
if input.Weight != nil { if input.Weight != nil {
weight := int64(*input.Weight) newPerformer.Weight = input.Weight
newPerformer.Weight = sql.NullInt64{Int64: weight, Valid: true}
} }
if input.IgnoreAutoTag != nil { if input.IgnoreAutoTag != nil {
newPerformer.IgnoreAutoTag = *input.IgnoreAutoTag newPerformer.IgnoreAutoTag = *input.IgnoreAutoTag
@ -138,24 +134,23 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
} }
// Start the transaction and save the performer // Start the transaction and save the performer
var performer *models.Performer
if err := r.withTxn(ctx, func(ctx context.Context) error { if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Performer qb := r.repository.Performer
performer, err = qb.Create(ctx, newPerformer) err = qb.Create(ctx, &newPerformer)
if err != nil { if err != nil {
return err return err
} }
if len(input.TagIds) > 0 { if len(input.TagIds) > 0 {
if err := r.updatePerformerTags(ctx, performer.ID, input.TagIds); err != nil { if err := r.updatePerformerTags(ctx, newPerformer.ID, input.TagIds); err != nil {
return err return err
} }
} }
// update image table // update image table
if len(imageData) > 0 { if len(imageData) > 0 {
if err := qb.UpdateImage(ctx, performer.ID, imageData); err != nil { if err := qb.UpdateImage(ctx, newPerformer.ID, imageData); err != nil {
return err return err
} }
} }
@ -163,7 +158,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
// Save the stash_ids // Save the stash_ids
if input.StashIds != nil { if input.StashIds != nil {
stashIDJoins := stashIDPtrSliceToSlice(input.StashIds) stashIDJoins := stashIDPtrSliceToSlice(input.StashIds)
if err := qb.UpdateStashIDs(ctx, performer.ID, stashIDJoins); err != nil { if err := qb.UpdateStashIDs(ctx, newPerformer.ID, stashIDJoins); err != nil {
return err return err
} }
} }
@ -173,17 +168,14 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
return nil, err return nil, err
} }
r.hookExecutor.ExecutePostHooks(ctx, performer.ID, plugin.PerformerCreatePost, input, nil) r.hookExecutor.ExecutePostHooks(ctx, newPerformer.ID, plugin.PerformerCreatePost, input, nil)
return r.getPerformer(ctx, performer.ID) return r.getPerformer(ctx, newPerformer.ID)
} }
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerUpdateInput) (*models.Performer, error) { func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerUpdateInput) (*models.Performer, error) {
// Populate performer from the input // Populate performer from the input
performerID, _ := strconv.Atoi(input.ID) performerID, _ := strconv.Atoi(input.ID)
updatedPerformer := models.PerformerPartial{ updatedPerformer := models.NewPerformerPartial()
ID: performerID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
}
translator := changesetTranslator{ translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx), inputMap: getUpdateInputMap(ctx),
@ -203,54 +195,53 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU
// generate checksum from performer name rather than image // generate checksum from performer name rather than image
checksum := md5.FromString(*input.Name) checksum := md5.FromString(*input.Name)
updatedPerformer.Name = &sql.NullString{String: *input.Name, Valid: true} updatedPerformer.Name = models.NewOptionalString(*input.Name)
updatedPerformer.Checksum = &checksum updatedPerformer.Checksum = models.NewOptionalString(checksum)
} }
updatedPerformer.URL = translator.nullString(input.URL, "url") updatedPerformer.URL = translator.optionalString(input.URL, "url")
if translator.hasField("gender") { if translator.hasField("gender") {
if input.Gender != nil { if input.Gender != nil {
updatedPerformer.Gender = &sql.NullString{String: input.Gender.String(), Valid: true} updatedPerformer.Gender = models.NewOptionalString(input.Gender.String())
} else { } else {
updatedPerformer.Gender = &sql.NullString{String: "", Valid: false} updatedPerformer.Gender = models.NewOptionalStringPtr(nil)
} }
} }
updatedPerformer.Birthdate = translator.sqliteDate(input.Birthdate, "birthdate") updatedPerformer.Birthdate = translator.optionalDate(input.Birthdate, "birthdate")
updatedPerformer.Country = translator.nullString(input.Country, "country") updatedPerformer.Country = translator.optionalString(input.Country, "country")
updatedPerformer.EyeColor = translator.nullString(input.EyeColor, "eye_color") updatedPerformer.EyeColor = translator.optionalString(input.EyeColor, "eye_color")
updatedPerformer.Measurements = translator.nullString(input.Measurements, "measurements") updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements")
updatedPerformer.Height = translator.nullString(input.Height, "height") updatedPerformer.Height = translator.optionalString(input.Height, "height")
updatedPerformer.Ethnicity = translator.nullString(input.Ethnicity, "ethnicity") updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
updatedPerformer.FakeTits = translator.nullString(input.FakeTits, "fake_tits") updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
updatedPerformer.CareerLength = translator.nullString(input.CareerLength, "career_length") updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
updatedPerformer.Tattoos = translator.nullString(input.Tattoos, "tattoos") updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
updatedPerformer.Piercings = translator.nullString(input.Piercings, "piercings") updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
updatedPerformer.Aliases = translator.nullString(input.Aliases, "aliases") updatedPerformer.Aliases = translator.optionalString(input.Aliases, "aliases")
updatedPerformer.Twitter = translator.nullString(input.Twitter, "twitter") updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter")
updatedPerformer.Instagram = translator.nullString(input.Instagram, "instagram") updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram")
updatedPerformer.Favorite = translator.nullBool(input.Favorite, "favorite") updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
updatedPerformer.Rating = translator.nullInt64(input.Rating, "rating") updatedPerformer.Rating = translator.optionalInt(input.Rating, "rating")
updatedPerformer.Details = translator.nullString(input.Details, "details") updatedPerformer.Details = translator.optionalString(input.Details, "details")
updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date") updatedPerformer.DeathDate = translator.optionalDate(input.DeathDate, "death_date")
updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color") updatedPerformer.HairColor = translator.optionalString(input.HairColor, "hair_color")
updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight") updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight")
updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
// Start the transaction and save the p // Start the transaction and save the p
var p *models.Performer
if err := r.withTxn(ctx, func(ctx context.Context) error { if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Performer qb := r.repository.Performer
// need to get existing performer // need to get existing performer
existing, err := qb.Find(ctx, updatedPerformer.ID) existing, err := qb.Find(ctx, performerID)
if err != nil { if err != nil {
return err return err
} }
if existing == nil { if existing == nil {
return fmt.Errorf("performer with id %d not found", updatedPerformer.ID) return fmt.Errorf("performer with id %d not found", performerID)
} }
if err := performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate); err != nil { if err := performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate); err != nil {
@ -259,26 +250,26 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU
} }
} }
p, err = qb.Update(ctx, updatedPerformer) _, err = qb.UpdatePartial(ctx, performerID, updatedPerformer)
if err != nil { if err != nil {
return err return err
} }
// Save the tags // Save the tags
if translator.hasField("tag_ids") { if translator.hasField("tag_ids") {
if err := r.updatePerformerTags(ctx, p.ID, input.TagIds); err != nil { if err := r.updatePerformerTags(ctx, performerID, input.TagIds); err != nil {
return err return err
} }
} }
// update image table // update image table
if len(imageData) > 0 { if len(imageData) > 0 {
if err := qb.UpdateImage(ctx, p.ID, imageData); err != nil { if err := qb.UpdateImage(ctx, performerID, imageData); err != nil {
return err return err
} }
} else if imageIncluded { } else if imageIncluded {
// must be unsetting // must be unsetting
if err := qb.DestroyImage(ctx, p.ID); err != nil { if err := qb.DestroyImage(ctx, performerID); err != nil {
return err return err
} }
} }
@ -296,8 +287,8 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU
return nil, err return nil, err
} }
r.hookExecutor.ExecutePostHooks(ctx, p.ID, plugin.PerformerUpdatePost, input, translator.getFields()) r.hookExecutor.ExecutePostHooks(ctx, performerID, plugin.PerformerUpdatePost, input, translator.getFields())
return r.getPerformer(ctx, p.ID) return r.getPerformer(ctx, performerID)
} }
func (r *mutationResolver) updatePerformerTags(ctx context.Context, performerID int, tagsIDs []string) error { func (r *mutationResolver) updatePerformerTags(ctx context.Context, performerID int, tagsIDs []string) error {
@ -315,43 +306,39 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
} }
// Populate performer from the input // Populate performer from the input
updatedTime := time.Now()
translator := changesetTranslator{ translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx), inputMap: getUpdateInputMap(ctx),
} }
updatedPerformer := models.PerformerPartial{ updatedPerformer := models.NewPerformerPartial()
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
updatedPerformer.URL = translator.nullString(input.URL, "url") updatedPerformer.URL = translator.optionalString(input.URL, "url")
updatedPerformer.Birthdate = translator.sqliteDate(input.Birthdate, "birthdate") updatedPerformer.Birthdate = translator.optionalDate(input.Birthdate, "birthdate")
updatedPerformer.Ethnicity = translator.nullString(input.Ethnicity, "ethnicity") updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
updatedPerformer.Country = translator.nullString(input.Country, "country") updatedPerformer.Country = translator.optionalString(input.Country, "country")
updatedPerformer.EyeColor = translator.nullString(input.EyeColor, "eye_color") updatedPerformer.EyeColor = translator.optionalString(input.EyeColor, "eye_color")
updatedPerformer.Height = translator.nullString(input.Height, "height") updatedPerformer.Height = translator.optionalString(input.Height, "height")
updatedPerformer.Measurements = translator.nullString(input.Measurements, "measurements") updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements")
updatedPerformer.FakeTits = translator.nullString(input.FakeTits, "fake_tits") updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
updatedPerformer.CareerLength = translator.nullString(input.CareerLength, "career_length") updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
updatedPerformer.Tattoos = translator.nullString(input.Tattoos, "tattoos") updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
updatedPerformer.Piercings = translator.nullString(input.Piercings, "piercings") updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
updatedPerformer.Aliases = translator.nullString(input.Aliases, "aliases") updatedPerformer.Aliases = translator.optionalString(input.Aliases, "aliases")
updatedPerformer.Twitter = translator.nullString(input.Twitter, "twitter") updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter")
updatedPerformer.Instagram = translator.nullString(input.Instagram, "instagram") updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram")
updatedPerformer.Favorite = translator.nullBool(input.Favorite, "favorite") updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
updatedPerformer.Rating = translator.nullInt64(input.Rating, "rating") updatedPerformer.Rating = translator.optionalInt(input.Rating, "rating")
updatedPerformer.Details = translator.nullString(input.Details, "details") updatedPerformer.Details = translator.optionalString(input.Details, "details")
updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date") updatedPerformer.DeathDate = translator.optionalDate(input.DeathDate, "death_date")
updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color") updatedPerformer.HairColor = translator.optionalString(input.HairColor, "hair_color")
updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight") updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight")
updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
if translator.hasField("gender") { if translator.hasField("gender") {
if input.Gender != nil { if input.Gender != nil {
updatedPerformer.Gender = &sql.NullString{String: input.Gender.String(), Valid: true} updatedPerformer.Gender = models.NewOptionalString(input.Gender.String())
} else { } else {
updatedPerformer.Gender = &sql.NullString{String: "", Valid: false} updatedPerformer.Gender = models.NewOptionalStringPtr(nil)
} }
} }
@ -378,7 +365,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
return err return err
} }
performer, err := qb.Update(ctx, updatedPerformer) performer, err := qb.UpdatePartial(ctx, performerID, updatedPerformer)
if err != nil { if err != nil {
return err return err
} }

View file

@ -54,7 +54,7 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
} }
if len(image) == 0 || defaultParam == "true" { if len(image) == 0 || defaultParam == "true" {
image, _ = getRandomPerformerImageUsingName(performer.Name.String, performer.Gender.String, config.GetInstance().GetCustomPerformerImageLocation()) image, _ = getRandomPerformerImageUsingName(performer.Name, performer.Gender, config.GetInstance().GetCustomPerformerImageLocation())
} }
if err := utils.ServeImage(image, w, r); err != nil { if err := utils.ServeImage(image, w, r); err != nil {

View file

@ -1,8 +1,9 @@
package urlbuilders package urlbuilders
import ( import (
"github.com/stashapp/stash/pkg/models"
"strconv" "strconv"
"github.com/stashapp/stash/pkg/models"
) )
type PerformerURLBuilder struct { type PerformerURLBuilder struct {
@ -15,7 +16,7 @@ func NewPerformerURLBuilder(baseURL string, performer *models.Performer) Perform
return PerformerURLBuilder{ return PerformerURLBuilder{
BaseURL: baseURL, BaseURL: baseURL,
PerformerID: strconv.Itoa(performer.ID), PerformerID: strconv.Itoa(performer.ID),
UpdatedAt: strconv.FormatInt(performer.UpdatedAt.Timestamp.Unix(), 10), UpdatedAt: strconv.FormatInt(performer.UpdatedAt.Unix(), 10),
} }
} }

View file

@ -22,14 +22,14 @@ func TestGalleryPerformers(t *testing.T) {
const performerID = 2 const performerID = 2
performer := models.Performer{ performer := models.Performer{
ID: performerID, ID: performerID,
Name: models.NullString(performerName), Name: performerName,
} }
const reversedPerformerName = "name performer" const reversedPerformerName = "name performer"
const reversedPerformerID = 3 const reversedPerformerID = 3
reversedPerformer := models.Performer{ reversedPerformer := models.Performer{
ID: reversedPerformerID, ID: reversedPerformerID,
Name: models.NullString(reversedPerformerName), Name: reversedPerformerName,
} }
testTables := generateTestTable(performerName, galleryExt) testTables := generateTestTable(performerName, galleryExt)

View file

@ -19,14 +19,14 @@ func TestImagePerformers(t *testing.T) {
const performerID = 2 const performerID = 2
performer := models.Performer{ performer := models.Performer{
ID: performerID, ID: performerID,
Name: models.NullString(performerName), Name: performerName,
} }
const reversedPerformerName = "name performer" const reversedPerformerName = "name performer"
const reversedPerformerID = 3 const reversedPerformerID = 3
reversedPerformer := models.Performer{ reversedPerformer := models.Performer{
ID: reversedPerformerID, ID: reversedPerformerID,
Name: models.NullString(reversedPerformerName), Name: reversedPerformerName,
} }
testTables := generateTestTable(performerName, imageExt) testTables := generateTestTable(performerName, imageExt)

View file

@ -87,11 +87,10 @@ func createPerformer(ctx context.Context, pqb models.PerformerWriter) error {
// create the performer // create the performer
performer := models.Performer{ performer := models.Performer{
Checksum: testName, Checksum: testName,
Name: sql.NullString{Valid: true, String: testName}, Name: testName,
Favorite: sql.NullBool{Valid: true, Bool: false},
} }
_, err := pqb.Create(ctx, performer) err := pqb.Create(ctx, &performer)
if err != nil { if err != nil {
return err return err
} }

View file

@ -33,7 +33,7 @@ func getPerformerTagger(p *models.Performer, cache *match.Cache) tagger {
return tagger{ return tagger{
ID: p.ID, ID: p.ID,
Type: "performer", Type: "performer",
Name: p.Name.String, Name: p.Name,
cache: cache, cache: cache,
} }
} }

View file

@ -60,7 +60,7 @@ func testPerformerScenes(t *testing.T, performerName, expectedRegex string) {
performer := models.Performer{ performer := models.Performer{
ID: performerID, ID: performerID,
Name: models.NullString(performerName), Name: performerName,
} }
organized := false organized := false
@ -140,7 +140,7 @@ func testPerformerImages(t *testing.T, performerName, expectedRegex string) {
performer := models.Performer{ performer := models.Performer{
ID: performerID, ID: performerID,
Name: models.NullString(performerName), Name: performerName,
} }
organized := false organized := false
@ -221,7 +221,7 @@ func testPerformerGalleries(t *testing.T, performerName, expectedRegex string) {
performer := models.Performer{ performer := models.Performer{
ID: performerID, ID: performerID,
Name: models.NullString(performerName), Name: performerName,
} }
organized := false organized := false

View file

@ -152,14 +152,14 @@ func TestScenePerformers(t *testing.T) {
const performerID = 2 const performerID = 2
performer := models.Performer{ performer := models.Performer{
ID: performerID, ID: performerID,
Name: models.NullString(performerName), Name: performerName,
} }
const reversedPerformerName = "name performer" const reversedPerformerName = "name performer"
const reversedPerformerID = 3 const reversedPerformerID = 3
reversedPerformer := models.Performer{ reversedPerformer := models.Performer{
ID: reversedPerformerID, ID: reversedPerformerID,
Name: models.NullString(reversedPerformerName), Name: reversedPerformerName,
} }
testTables := generateTestTable(performerName, sceneExt) testTables := generateTestTable(performerName, sceneExt)

View file

@ -58,11 +58,11 @@ func (t *tagger) tagPerformers(ctx context.Context, performerReader match.Perfor
added, err := addFunc(t.ID, p.ID) added, err := addFunc(t.ID, p.ID)
if err != nil { if err != nil {
return t.addError("performer", p.Name.String, err) return t.addError("performer", p.Name, err)
} }
if added { if added {
t.addLog("performer", p.Name.String) t.addLog("performer", p.Name)
} }
} }

View file

@ -612,7 +612,7 @@ func (me *contentDirectoryService) getPerformers() []interface{} {
} }
for _, s := range performers { for _, s := range performers {
objs = append(objs, makeStorageFolder("performers/"+strconv.Itoa(s.ID), s.Name.String, "performers")) objs = append(objs, makeStorageFolder("performers/"+strconv.Itoa(s.ID), s.Name, "performers"))
} }
return nil return nil

View file

@ -2,7 +2,6 @@ package identify
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
@ -12,7 +11,7 @@ import (
) )
type PerformerCreator interface { type PerformerCreator interface {
Create(ctx context.Context, newPerformer models.Performer) (*models.Performer, error) Create(ctx context.Context, newPerformer *models.Performer) error
UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error
} }
@ -33,13 +32,14 @@ func getPerformerID(ctx context.Context, endpoint string, w PerformerCreator, p
} }
func createMissingPerformer(ctx context.Context, endpoint string, w PerformerCreator, p *models.ScrapedPerformer) (*int, error) { func createMissingPerformer(ctx context.Context, endpoint string, w PerformerCreator, p *models.ScrapedPerformer) (*int, error) {
created, err := w.Create(ctx, scrapedToPerformerInput(p)) performerInput := scrapedToPerformerInput(p)
err := w.Create(ctx, &performerInput)
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating performer: %w", err) return nil, fmt.Errorf("error creating performer: %w", err)
} }
if endpoint != "" && p.RemoteSiteID != nil { if endpoint != "" && p.RemoteSiteID != nil {
if err := w.UpdateStashIDs(ctx, created.ID, []models.StashID{ if err := w.UpdateStashIDs(ctx, performerInput.ID, []models.StashID{
{ {
Endpoint: endpoint, Endpoint: endpoint,
StashID: *p.RemoteSiteID, StashID: *p.RemoteSiteID,
@ -49,65 +49,66 @@ func createMissingPerformer(ctx context.Context, endpoint string, w PerformerCre
} }
} }
return &created.ID, nil return &performerInput.ID, nil
} }
func scrapedToPerformerInput(performer *models.ScrapedPerformer) models.Performer { func scrapedToPerformerInput(performer *models.ScrapedPerformer) models.Performer {
currentTime := time.Now() currentTime := time.Now()
ret := models.Performer{ ret := models.Performer{
Name: sql.NullString{String: *performer.Name, Valid: true}, Name: *performer.Name,
Checksum: md5.FromString(*performer.Name), Checksum: md5.FromString(*performer.Name),
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, CreatedAt: currentTime,
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, UpdatedAt: currentTime,
Favorite: sql.NullBool{Bool: false, Valid: true},
} }
if performer.Birthdate != nil { if performer.Birthdate != nil {
ret.Birthdate = models.SQLiteDate{String: *performer.Birthdate, Valid: true} d := models.NewDate(*performer.Birthdate)
ret.Birthdate = &d
} }
if performer.DeathDate != nil { if performer.DeathDate != nil {
ret.DeathDate = models.SQLiteDate{String: *performer.DeathDate, Valid: true} d := models.NewDate(*performer.DeathDate)
ret.DeathDate = &d
} }
if performer.Gender != nil { if performer.Gender != nil {
ret.Gender = sql.NullString{String: *performer.Gender, Valid: true} ret.Gender = models.GenderEnum(*performer.Gender)
} }
if performer.Ethnicity != nil { if performer.Ethnicity != nil {
ret.Ethnicity = sql.NullString{String: *performer.Ethnicity, Valid: true} ret.Ethnicity = *performer.Ethnicity
} }
if performer.Country != nil { if performer.Country != nil {
ret.Country = sql.NullString{String: *performer.Country, Valid: true} ret.Country = *performer.Country
} }
if performer.EyeColor != nil { if performer.EyeColor != nil {
ret.EyeColor = sql.NullString{String: *performer.EyeColor, Valid: true} ret.EyeColor = *performer.EyeColor
} }
if performer.HairColor != nil { if performer.HairColor != nil {
ret.HairColor = sql.NullString{String: *performer.HairColor, Valid: true} ret.HairColor = *performer.HairColor
} }
if performer.Height != nil { if performer.Height != nil {
ret.Height = sql.NullString{String: *performer.Height, Valid: true} ret.Height = *performer.Height
} }
if performer.Measurements != nil { if performer.Measurements != nil {
ret.Measurements = sql.NullString{String: *performer.Measurements, Valid: true} ret.Measurements = *performer.Measurements
} }
if performer.FakeTits != nil { if performer.FakeTits != nil {
ret.FakeTits = sql.NullString{String: *performer.FakeTits, Valid: true} ret.FakeTits = *performer.FakeTits
} }
if performer.CareerLength != nil { if performer.CareerLength != nil {
ret.CareerLength = sql.NullString{String: *performer.CareerLength, Valid: true} ret.CareerLength = *performer.CareerLength
} }
if performer.Tattoos != nil { if performer.Tattoos != nil {
ret.Tattoos = sql.NullString{String: *performer.Tattoos, Valid: true} ret.Tattoos = *performer.Tattoos
} }
if performer.Piercings != nil { if performer.Piercings != nil {
ret.Piercings = sql.NullString{String: *performer.Piercings, Valid: true} ret.Piercings = *performer.Piercings
} }
if performer.Aliases != nil { if performer.Aliases != nil {
ret.Aliases = sql.NullString{String: *performer.Aliases, Valid: true} ret.Aliases = *performer.Aliases
} }
if performer.Twitter != nil { if performer.Twitter != nil {
ret.Twitter = sql.NullString{String: *performer.Twitter, Valid: true} ret.Twitter = *performer.Twitter
} }
if performer.Instagram != nil { if performer.Instagram != nil {
ret.Instagram = sql.NullString{String: *performer.Instagram, Valid: true} ret.Instagram = *performer.Instagram
} }
return ret return ret

View file

@ -1,11 +1,11 @@
package identify package identify
import ( import (
"database/sql"
"errors" "errors"
"reflect" "reflect"
"strconv" "strconv"
"testing" "testing"
"time"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/mocks" "github.com/stashapp/stash/pkg/models/mocks"
@ -24,9 +24,10 @@ func Test_getPerformerID(t *testing.T) {
name := "name" name := "name"
mockPerformerReaderWriter := mocks.PerformerReaderWriter{} mockPerformerReaderWriter := mocks.PerformerReaderWriter{}
mockPerformerReaderWriter.On("Create", testCtx, mock.Anything).Return(&models.Performer{ mockPerformerReaderWriter.On("Create", testCtx, mock.Anything).Run(func(args mock.Arguments) {
ID: validStoredID, p := args.Get(1).(*models.Performer)
}, nil) p.ID = validStoredID
}).Return(nil)
type args struct { type args struct {
endpoint string endpoint string
@ -132,14 +133,16 @@ func Test_createMissingPerformer(t *testing.T) {
performerID := 1 performerID := 1
mockPerformerReaderWriter := mocks.PerformerReaderWriter{} mockPerformerReaderWriter := mocks.PerformerReaderWriter{}
mockPerformerReaderWriter.On("Create", testCtx, mock.MatchedBy(func(p models.Performer) bool { mockPerformerReaderWriter.On("Create", testCtx, mock.MatchedBy(func(p *models.Performer) bool {
return p.Name.String == validName return p.Name == validName
})).Return(&models.Performer{ })).Run(func(args mock.Arguments) {
ID: performerID, p := args.Get(1).(*models.Performer)
}, nil) p.ID = performerID
mockPerformerReaderWriter.On("Create", testCtx, mock.MatchedBy(func(p models.Performer) bool { }).Return(nil)
return p.Name.String == invalidName
})).Return(nil, errors.New("error creating performer")) mockPerformerReaderWriter.On("Create", testCtx, mock.MatchedBy(func(p *models.Performer) bool {
return p.Name == invalidName
})).Return(errors.New("error creating performer"))
mockPerformerReaderWriter.On("UpdateStashIDs", testCtx, performerID, []models.StashID{ mockPerformerReaderWriter.On("UpdateStashIDs", testCtx, performerID, []models.StashID{
{ {
@ -241,6 +244,10 @@ func Test_scrapedToPerformerInput(t *testing.T) {
return &ret return &ret
} }
dateToDatePtr := func(d models.Date) *models.Date {
return &d
}
tests := []struct { tests := []struct {
name string name string
performer *models.ScrapedPerformer performer *models.ScrapedPerformer
@ -268,34 +275,24 @@ func Test_scrapedToPerformerInput(t *testing.T) {
Instagram: nextVal(), Instagram: nextVal(),
}, },
models.Performer{ models.Performer{
Name: models.NullString(name), Name: name,
Checksum: md5, Checksum: md5,
Favorite: sql.NullBool{ Birthdate: dateToDatePtr(models.NewDate(*nextVal())),
Bool: false, DeathDate: dateToDatePtr(models.NewDate(*nextVal())),
Valid: true, Gender: models.GenderEnum(*nextVal()),
}, Ethnicity: *nextVal(),
Birthdate: models.SQLiteDate{ Country: *nextVal(),
String: *nextVal(), EyeColor: *nextVal(),
Valid: true, HairColor: *nextVal(),
}, Height: *nextVal(),
DeathDate: models.SQLiteDate{ Measurements: *nextVal(),
String: *nextVal(), FakeTits: *nextVal(),
Valid: true, CareerLength: *nextVal(),
}, Tattoos: *nextVal(),
Gender: models.NullString(*nextVal()), Piercings: *nextVal(),
Ethnicity: models.NullString(*nextVal()), Aliases: *nextVal(),
Country: models.NullString(*nextVal()), Twitter: *nextVal(),
EyeColor: models.NullString(*nextVal()), Instagram: *nextVal(),
HairColor: models.NullString(*nextVal()),
Height: models.NullString(*nextVal()),
Measurements: models.NullString(*nextVal()),
FakeTits: models.NullString(*nextVal()),
CareerLength: models.NullString(*nextVal()),
Tattoos: models.NullString(*nextVal()),
Piercings: models.NullString(*nextVal()),
Aliases: models.NullString(*nextVal()),
Twitter: models.NullString(*nextVal()),
Instagram: models.NullString(*nextVal()),
}, },
}, },
{ {
@ -304,12 +301,8 @@ func Test_scrapedToPerformerInput(t *testing.T) {
Name: &name, Name: &name,
}, },
models.Performer{ models.Performer{
Name: models.NullString(name), Name: name,
Checksum: md5, Checksum: md5,
Favorite: sql.NullBool{
Bool: false,
Valid: true,
},
}, },
}, },
} }
@ -318,7 +311,7 @@ func Test_scrapedToPerformerInput(t *testing.T) {
got := scrapedToPerformerInput(tt.performer) got := scrapedToPerformerInput(tt.performer)
// clear created/updated dates // clear created/updated dates
got.CreatedAt = models.SQLiteTimestamp{} got.CreatedAt = time.Time{}
got.UpdatedAt = got.CreatedAt got.UpdatedAt = got.CreatedAt
if !reflect.DeepEqual(got, tt.want) { if !reflect.DeepEqual(got, tt.want) {

View file

@ -176,7 +176,7 @@ func (j *autoTagJob) autoTagPerformers(ctx context.Context, progress *job.Progre
return nil return nil
}); err != nil { }); err != nil {
return fmt.Errorf("error auto-tagging performer '%s': %s", performer.Name.String, err.Error()) return fmt.Errorf("error auto-tagging performer '%s': %s", performer.Name, err.Error())
} }
progress.Increment() progress.Increment()

View file

@ -2,7 +2,6 @@ package manager
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"time" "time"
@ -31,7 +30,7 @@ func (t *StashBoxPerformerTagTask) Description() string {
if t.name != nil { if t.name != nil {
name = *t.name name = *t.name
} else if t.performer != nil { } else if t.performer != nil {
name = t.performer.Name.String name = t.performer.Name
} }
return fmt.Sprintf("Tagging performer %s from stash-box", name) return fmt.Sprintf("Tagging performer %s from stash-box", name)
@ -70,7 +69,7 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
if t.name != nil { if t.name != nil {
name = *t.name name = *t.name
} else { } else {
name = t.performer.Name.String name = t.performer.Name
} }
performer, err = client.FindStashBoxPerformerByName(ctx, name) performer, err = client.FindStashBoxPerformerByName(ctx, name)
} }
@ -86,84 +85,64 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
} }
if performer != nil { if performer != nil {
updatedTime := time.Now()
if t.performer != nil { if t.performer != nil {
partial := models.PerformerPartial{ partial := models.NewPerformerPartial()
ID: t.performer.ID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
if performer.Aliases != nil && !excluded["aliases"] { if performer.Aliases != nil && !excluded["aliases"] {
value := getNullString(performer.Aliases) partial.Aliases = models.NewOptionalString(*performer.Aliases)
partial.Aliases = &value
} }
if performer.Birthdate != nil && *performer.Birthdate != "" && !excluded["birthdate"] { if performer.Birthdate != nil && *performer.Birthdate != "" && !excluded["birthdate"] {
value := getDate(performer.Birthdate) value := getDate(performer.Birthdate)
partial.Birthdate = &value partial.Birthdate = models.NewOptionalDate(*value)
} }
if performer.CareerLength != nil && !excluded["career_length"] { if performer.CareerLength != nil && !excluded["career_length"] {
value := getNullString(performer.CareerLength) partial.CareerLength = models.NewOptionalString(*performer.CareerLength)
partial.CareerLength = &value
} }
if performer.Country != nil && !excluded["country"] { if performer.Country != nil && !excluded["country"] {
value := getNullString(performer.Country) partial.Country = models.NewOptionalString(*performer.Country)
partial.Country = &value
} }
if performer.Ethnicity != nil && !excluded["ethnicity"] { if performer.Ethnicity != nil && !excluded["ethnicity"] {
value := getNullString(performer.Ethnicity) partial.Ethnicity = models.NewOptionalString(*performer.Ethnicity)
partial.Ethnicity = &value
} }
if performer.EyeColor != nil && !excluded["eye_color"] { if performer.EyeColor != nil && !excluded["eye_color"] {
value := getNullString(performer.EyeColor) partial.EyeColor = models.NewOptionalString(*performer.EyeColor)
partial.EyeColor = &value
} }
if performer.FakeTits != nil && !excluded["fake_tits"] { if performer.FakeTits != nil && !excluded["fake_tits"] {
value := getNullString(performer.FakeTits) partial.FakeTits = models.NewOptionalString(*performer.FakeTits)
partial.FakeTits = &value
} }
if performer.Gender != nil && !excluded["gender"] { if performer.Gender != nil && !excluded["gender"] {
value := getNullString(performer.Gender) partial.Gender = models.NewOptionalString(*performer.Gender)
partial.Gender = &value
} }
if performer.Height != nil && !excluded["height"] { if performer.Height != nil && !excluded["height"] {
value := getNullString(performer.Height) partial.Height = models.NewOptionalString(*performer.Height)
partial.Height = &value
} }
if performer.Instagram != nil && !excluded["instagram"] { if performer.Instagram != nil && !excluded["instagram"] {
value := getNullString(performer.Instagram) partial.Instagram = models.NewOptionalString(*performer.Instagram)
partial.Instagram = &value
} }
if performer.Measurements != nil && !excluded["measurements"] { if performer.Measurements != nil && !excluded["measurements"] {
value := getNullString(performer.Measurements) partial.Measurements = models.NewOptionalString(*performer.Measurements)
partial.Measurements = &value
} }
if excluded["name"] && performer.Name != nil { if excluded["name"] && performer.Name != nil {
value := sql.NullString{String: *performer.Name, Valid: true} partial.Name = models.NewOptionalString(*performer.Name)
partial.Name = &value
checksum := md5.FromString(*performer.Name) checksum := md5.FromString(*performer.Name)
partial.Checksum = &checksum partial.Checksum = models.NewOptionalString(checksum)
} }
if performer.Piercings != nil && !excluded["piercings"] { if performer.Piercings != nil && !excluded["piercings"] {
value := getNullString(performer.Piercings) partial.Piercings = models.NewOptionalString(*performer.Piercings)
partial.Piercings = &value
} }
if performer.Tattoos != nil && !excluded["tattoos"] { if performer.Tattoos != nil && !excluded["tattoos"] {
value := getNullString(performer.Tattoos) partial.Tattoos = models.NewOptionalString(*performer.Tattoos)
partial.Tattoos = &value
} }
if performer.Twitter != nil && !excluded["twitter"] { if performer.Twitter != nil && !excluded["twitter"] {
value := getNullString(performer.Twitter) partial.Twitter = models.NewOptionalString(*performer.Twitter)
partial.Twitter = &value
} }
if performer.URL != nil && !excluded["url"] { if performer.URL != nil && !excluded["url"] {
value := getNullString(performer.URL) partial.URL = models.NewOptionalString(*performer.URL)
partial.URL = &value
} }
txnErr := txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error { txnErr := txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error {
r := instance.Repository r := instance.Repository
_, err := r.Performer.Update(ctx, partial) _, err := r.Performer.UpdatePartial(ctx, t.performer.ID, partial)
if !t.refresh { if !t.refresh {
err = r.Performer.UpdateStashIDs(ctx, t.performer.ID, []models.StashID{ err = r.Performer.UpdateStashIDs(ctx, t.performer.ID, []models.StashID{
@ -203,35 +182,34 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
} else if t.name != nil && performer.Name != nil { } else if t.name != nil && performer.Name != nil {
currentTime := time.Now() currentTime := time.Now()
newPerformer := models.Performer{ newPerformer := models.Performer{
Aliases: getNullString(performer.Aliases), Aliases: getString(performer.Aliases),
Birthdate: getDate(performer.Birthdate), Birthdate: getDate(performer.Birthdate),
CareerLength: getNullString(performer.CareerLength), CareerLength: getString(performer.CareerLength),
Checksum: md5.FromString(*performer.Name), Checksum: md5.FromString(*performer.Name),
Country: getNullString(performer.Country), Country: getString(performer.Country),
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, CreatedAt: currentTime,
Ethnicity: getNullString(performer.Ethnicity), Ethnicity: getString(performer.Ethnicity),
EyeColor: getNullString(performer.EyeColor), EyeColor: getString(performer.EyeColor),
FakeTits: getNullString(performer.FakeTits), FakeTits: getString(performer.FakeTits),
Favorite: sql.NullBool{Bool: false, Valid: true}, Gender: models.GenderEnum(getString(performer.Gender)),
Gender: getNullString(performer.Gender), Height: getString(performer.Height),
Height: getNullString(performer.Height), Instagram: getString(performer.Instagram),
Instagram: getNullString(performer.Instagram), Measurements: getString(performer.Measurements),
Measurements: getNullString(performer.Measurements), Name: *performer.Name,
Name: sql.NullString{String: *performer.Name, Valid: true}, Piercings: getString(performer.Piercings),
Piercings: getNullString(performer.Piercings), Tattoos: getString(performer.Tattoos),
Tattoos: getNullString(performer.Tattoos), Twitter: getString(performer.Twitter),
Twitter: getNullString(performer.Twitter), URL: getString(performer.URL),
URL: getNullString(performer.URL), UpdatedAt: currentTime,
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
} }
err := txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error { err := txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error {
r := instance.Repository r := instance.Repository
createdPerformer, err := r.Performer.Create(ctx, newPerformer) err := r.Performer.Create(ctx, &newPerformer)
if err != nil { if err != nil {
return err return err
} }
err = r.Performer.UpdateStashIDs(ctx, createdPerformer.ID, []models.StashID{ err = r.Performer.UpdateStashIDs(ctx, newPerformer.ID, []models.StashID{
{ {
Endpoint: t.box.Endpoint, Endpoint: t.box.Endpoint,
StashID: *performer.RemoteSiteID, StashID: *performer.RemoteSiteID,
@ -246,7 +224,7 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
if imageErr != nil { if imageErr != nil {
return imageErr return imageErr
} }
err = r.Performer.UpdateImage(ctx, createdPerformer.ID, image) err = r.Performer.UpdateImage(ctx, newPerformer.ID, image)
} }
return err return err
}) })
@ -261,24 +239,25 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
if t.name != nil { if t.name != nil {
name = *t.name name = *t.name
} else if t.performer != nil { } else if t.performer != nil {
name = t.performer.Name.String name = t.performer.Name
} }
logger.Infof("No match found for %s", name) logger.Infof("No match found for %s", name)
} }
} }
func getDate(val *string) models.SQLiteDate { func getDate(val *string) *models.Date {
if val == nil { if val == nil {
return models.SQLiteDate{Valid: false} return nil
} else {
return models.SQLiteDate{String: *val, Valid: true}
} }
ret := models.NewDate(*val)
return &ret
} }
func getNullString(val *string) sql.NullString { func getString(val *string) string {
if val == nil { if val == nil {
return sql.NullString{Valid: false} return ""
} else { } else {
return sql.NullString{String: *val, Valid: true} return *val
} }
} }

View file

@ -136,10 +136,10 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
var pluckedNames []string var pluckedNames []string
for _, performer := range performers { for _, performer := range performers {
if !performer.Name.Valid { if performer.Name == "" {
continue continue
} }
pluckedNames = append(pluckedNames, performer.Name.String) pluckedNames = append(pluckedNames, performer.Name)
} }
missingPerformers := stringslice.StrFilter(names, func(name string) bool { missingPerformers := stringslice.StrFilter(names, func(name string) bool {
@ -176,12 +176,12 @@ func (i *Importer) createPerformers(ctx context.Context, names []string) ([]*mod
for _, name := range names { for _, name := range names {
newPerformer := *models.NewPerformer(name) newPerformer := *models.NewPerformer(name)
created, err := i.PerformerWriter.Create(ctx, newPerformer) err := i.PerformerWriter.Create(ctx, &newPerformer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret = append(ret, created) ret = append(ret, &newPerformer)
} }
return ret, nil return ret, nil

View file

@ -169,7 +169,7 @@ func TestImporterPreImportWithPerformer(t *testing.T) {
performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerName}, false).Return([]*models.Performer{ performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerName}, false).Return([]*models.Performer{
{ {
ID: existingPerformerID, ID: existingPerformerID,
Name: models.NullString(existingPerformerName), Name: existingPerformerName,
}, },
}, nil).Once() }, nil).Once()
performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once() performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once()
@ -199,9 +199,10 @@ func TestImporterPreImportWithMissingPerformer(t *testing.T) {
} }
performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Times(3) performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Times(3)
performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("models.Performer")).Return(&models.Performer{ performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("*models.Performer")).Run(func(args mock.Arguments) {
ID: existingPerformerID, performer := args.Get(1).(*models.Performer)
}, nil) performer.ID = existingPerformerID
}).Return(nil)
err := i.PreImport(testCtx) err := i.PreImport(testCtx)
assert.NotNil(t, err) assert.NotNil(t, err)
@ -232,7 +233,7 @@ func TestImporterPreImportWithMissingPerformerCreateErr(t *testing.T) {
} }
performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Once() performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Once()
performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("models.Performer")).Return(nil, errors.New("Create error")) performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("*models.Performer")).Return(errors.New("Create error"))
err := i.PreImport(testCtx) err := i.PreImport(testCtx)
assert.NotNil(t, err) assert.NotNil(t, err)

View file

@ -216,10 +216,10 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
var pluckedNames []string var pluckedNames []string
for _, performer := range performers { for _, performer := range performers {
if !performer.Name.Valid { if performer.Name == "" {
continue continue
} }
pluckedNames = append(pluckedNames, performer.Name.String) pluckedNames = append(pluckedNames, performer.Name)
} }
missingPerformers := stringslice.StrFilter(names, func(name string) bool { missingPerformers := stringslice.StrFilter(names, func(name string) bool {
@ -256,12 +256,12 @@ func (i *Importer) createPerformers(ctx context.Context, names []string) ([]*mod
for _, name := range names { for _, name := range names {
newPerformer := *models.NewPerformer(name) newPerformer := *models.NewPerformer(name)
created, err := i.PerformerWriter.Create(ctx, newPerformer) err := i.PerformerWriter.Create(ctx, &newPerformer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret = append(ret, created) ret = append(ret, &newPerformer)
} }
return ret, nil return ret, nil

View file

@ -130,7 +130,7 @@ func TestImporterPreImportWithPerformer(t *testing.T) {
performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerName}, false).Return([]*models.Performer{ performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerName}, false).Return([]*models.Performer{
{ {
ID: existingPerformerID, ID: existingPerformerID,
Name: models.NullString(existingPerformerName), Name: existingPerformerName,
}, },
}, nil).Once() }, nil).Once()
performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once() performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once()
@ -160,9 +160,10 @@ func TestImporterPreImportWithMissingPerformer(t *testing.T) {
} }
performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Times(3) performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Times(3)
performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("models.Performer")).Return(&models.Performer{ performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("*models.Performer")).Run(func(args mock.Arguments) {
ID: existingPerformerID, performer := args.Get(1).(*models.Performer)
}, nil) performer.ID = existingPerformerID
}).Return(nil)
err := i.PreImport(testCtx) err := i.PreImport(testCtx)
assert.NotNil(t, err) assert.NotNil(t, err)
@ -193,7 +194,7 @@ func TestImporterPreImportWithMissingPerformerCreateErr(t *testing.T) {
} }
performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Once() performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Once()
performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("models.Performer")).Return(nil, errors.New("Create error")) performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("*models.Performer")).Return(errors.New("Create error"))
err := i.PreImport(testCtx) err := i.PreImport(testCtx)
assert.NotNil(t, err) assert.NotNil(t, err)

View file

@ -169,7 +169,7 @@ func PathToPerformers(ctx context.Context, path string, reader PerformerAutoTagQ
var ret []*models.Performer var ret []*models.Performer
for _, p := range performers { for _, p := range performers {
// TODO - commenting out alias handling until both sides work correctly // TODO - commenting out alias handling until both sides work correctly
if nameMatchesPath(p.Name.String, path) != -1 { // || nameMatchesPath(p.Aliases.String, path) { if nameMatchesPath(p.Name, path) != -1 { // || nameMatchesPath(p.Aliases.String, path) {
ret = append(ret, p) ret = append(ret, p)
} }
} }

View file

@ -80,26 +80,17 @@ func (_m *PerformerReaderWriter) CountByTagID(ctx context.Context, tagID int) (i
} }
// Create provides a mock function with given fields: ctx, newPerformer // Create provides a mock function with given fields: ctx, newPerformer
func (_m *PerformerReaderWriter) Create(ctx context.Context, newPerformer models.Performer) (*models.Performer, error) { func (_m *PerformerReaderWriter) Create(ctx context.Context, newPerformer *models.Performer) error {
ret := _m.Called(ctx, newPerformer) ret := _m.Called(ctx, newPerformer)
var r0 *models.Performer var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.Performer) *models.Performer); ok { if rf, ok := ret.Get(0).(func(context.Context, *models.Performer) error); ok {
r0 = rf(ctx, newPerformer) r0 = rf(ctx, newPerformer)
} else { } else {
if ret.Get(0) != nil { r0 = ret.Error(0)
r0 = ret.Get(0).(*models.Performer)
}
} }
var r1 error return r0
if rf, ok := ret.Get(1).(func(context.Context, models.Performer) error); ok {
r1 = rf(ctx, newPerformer)
} else {
r1 = ret.Error(1)
}
return r0, r1
} }
// Destroy provides a mock function with given fields: ctx, id // Destroy provides a mock function with given fields: ctx, id
@ -314,29 +305,6 @@ func (_m *PerformerReaderWriter) FindMany(ctx context.Context, ids []int) ([]*mo
return r0, r1 return r0, r1
} }
// FindNamesBySceneID provides a mock function with given fields: ctx, sceneID
func (_m *PerformerReaderWriter) FindNamesBySceneID(ctx context.Context, sceneID int) ([]*models.Performer, error) {
ret := _m.Called(ctx, sceneID)
var r0 []*models.Performer
if rf, ok := ret.Get(0).(func(context.Context, int) []*models.Performer); ok {
r0 = rf(ctx, sceneID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Performer)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, sceneID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetImage provides a mock function with given fields: ctx, performerID // GetImage provides a mock function with given fields: ctx, performerID
func (_m *PerformerReaderWriter) GetImage(ctx context.Context, performerID int) ([]byte, error) { func (_m *PerformerReaderWriter) GetImage(ctx context.Context, performerID int) ([]byte, error) {
ret := _m.Called(ctx, performerID) ret := _m.Called(ctx, performerID)
@ -460,49 +428,17 @@ func (_m *PerformerReaderWriter) QueryForAutoTag(ctx context.Context, words []st
} }
// Update provides a mock function with given fields: ctx, updatedPerformer // Update provides a mock function with given fields: ctx, updatedPerformer
func (_m *PerformerReaderWriter) Update(ctx context.Context, updatedPerformer models.PerformerPartial) (*models.Performer, error) { func (_m *PerformerReaderWriter) Update(ctx context.Context, updatedPerformer *models.Performer) error {
ret := _m.Called(ctx, updatedPerformer) ret := _m.Called(ctx, updatedPerformer)
var r0 *models.Performer var r0 error
if rf, ok := ret.Get(0).(func(context.Context, models.PerformerPartial) *models.Performer); ok { if rf, ok := ret.Get(0).(func(context.Context, *models.Performer) error); ok {
r0 = rf(ctx, updatedPerformer) r0 = rf(ctx, updatedPerformer)
} else { } else {
if ret.Get(0) != nil { r0 = ret.Error(0)
r0 = ret.Get(0).(*models.Performer)
}
} }
var r1 error return r0
if rf, ok := ret.Get(1).(func(context.Context, models.PerformerPartial) error); ok {
r1 = rf(ctx, updatedPerformer)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateFull provides a mock function with given fields: ctx, updatedPerformer
func (_m *PerformerReaderWriter) UpdateFull(ctx context.Context, updatedPerformer models.Performer) (*models.Performer, error) {
ret := _m.Called(ctx, updatedPerformer)
var r0 *models.Performer
if rf, ok := ret.Get(0).(func(context.Context, models.Performer) *models.Performer); ok {
r0 = rf(ctx, updatedPerformer)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Performer)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, models.Performer) error); ok {
r1 = rf(ctx, updatedPerformer)
} else {
r1 = ret.Error(1)
}
return r0, r1
} }
// UpdateImage provides a mock function with given fields: ctx, performerID, image // UpdateImage provides a mock function with given fields: ctx, performerID, image
@ -519,6 +455,29 @@ func (_m *PerformerReaderWriter) UpdateImage(ctx context.Context, performerID in
return r0 return r0
} }
// UpdatePartial provides a mock function with given fields: ctx, id, updatedPerformer
func (_m *PerformerReaderWriter) UpdatePartial(ctx context.Context, id int, updatedPerformer models.PerformerPartial) (*models.Performer, error) {
ret := _m.Called(ctx, id, updatedPerformer)
var r0 *models.Performer
if rf, ok := ret.Get(0).(func(context.Context, int, models.PerformerPartial) *models.Performer); ok {
r0 = rf(ctx, id, updatedPerformer)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Performer)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int, models.PerformerPartial) error); ok {
r1 = rf(ctx, id, updatedPerformer)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateStashIDs provides a mock function with given fields: ctx, performerID, stashIDs // UpdateStashIDs provides a mock function with given fields: ctx, performerID, stashIDs
func (_m *PerformerReaderWriter) UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error { func (_m *PerformerReaderWriter) UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error {
ret := _m.Called(ctx, performerID, stashIDs) ret := _m.Called(ctx, performerID, stashIDs)

View file

@ -1,80 +1,87 @@
package models package models
import ( import (
"database/sql"
"time" "time"
"github.com/stashapp/stash/pkg/hash/md5" "github.com/stashapp/stash/pkg/hash/md5"
) )
type Performer struct { type Performer struct {
ID int `db:"id" json:"id"` ID int `json:"id"`
Checksum string `db:"checksum" json:"checksum"` Checksum string `json:"checksum"`
Name sql.NullString `db:"name" json:"name"` Name string `json:"name"`
Gender sql.NullString `db:"gender" json:"gender"` Gender GenderEnum `json:"gender"`
URL sql.NullString `db:"url" json:"url"` URL string `json:"url"`
Twitter sql.NullString `db:"twitter" json:"twitter"` Twitter string `json:"twitter"`
Instagram sql.NullString `db:"instagram" json:"instagram"` Instagram string `json:"instagram"`
Birthdate SQLiteDate `db:"birthdate" json:"birthdate"` Birthdate *Date `json:"birthdate"`
Ethnicity sql.NullString `db:"ethnicity" json:"ethnicity"` Ethnicity string `json:"ethnicity"`
Country sql.NullString `db:"country" json:"country"` Country string `json:"country"`
EyeColor sql.NullString `db:"eye_color" json:"eye_color"` EyeColor string `json:"eye_color"`
Height sql.NullString `db:"height" json:"height"` Height string `json:"height"`
Measurements sql.NullString `db:"measurements" json:"measurements"` Measurements string `json:"measurements"`
FakeTits sql.NullString `db:"fake_tits" json:"fake_tits"` FakeTits string `json:"fake_tits"`
CareerLength sql.NullString `db:"career_length" json:"career_length"` CareerLength string `json:"career_length"`
Tattoos sql.NullString `db:"tattoos" json:"tattoos"` Tattoos string `json:"tattoos"`
Piercings sql.NullString `db:"piercings" json:"piercings"` Piercings string `json:"piercings"`
Aliases sql.NullString `db:"aliases" json:"aliases"` Aliases string `json:"aliases"`
Favorite sql.NullBool `db:"favorite" json:"favorite"` Favorite bool `json:"favorite"`
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Rating sql.NullInt64 `db:"rating" json:"rating"` Rating *int `json:"rating"`
Details sql.NullString `db:"details" json:"details"` Details string `json:"details"`
DeathDate SQLiteDate `db:"death_date" json:"death_date"` DeathDate *Date `json:"death_date"`
HairColor sql.NullString `db:"hair_color" json:"hair_color"` HairColor string `json:"hair_color"`
Weight sql.NullInt64 `db:"weight" json:"weight"` Weight *int `json:"weight"`
IgnoreAutoTag bool `db:"ignore_auto_tag" json:"ignore_auto_tag"` IgnoreAutoTag bool `json:"ignore_auto_tag"`
} }
// PerformerPartial represents part of a Performer object. It is used to update
// the database entry.
type PerformerPartial struct { type PerformerPartial struct {
ID int `db:"id" json:"id"` ID int
Checksum *string `db:"checksum" json:"checksum"` Checksum OptionalString
Name *sql.NullString `db:"name" json:"name"` Name OptionalString
Gender *sql.NullString `db:"gender" json:"gender"` Gender OptionalString
URL *sql.NullString `db:"url" json:"url"` URL OptionalString
Twitter *sql.NullString `db:"twitter" json:"twitter"` Twitter OptionalString
Instagram *sql.NullString `db:"instagram" json:"instagram"` Instagram OptionalString
Birthdate *SQLiteDate `db:"birthdate" json:"birthdate"` Birthdate OptionalDate
Ethnicity *sql.NullString `db:"ethnicity" json:"ethnicity"` Ethnicity OptionalString
Country *sql.NullString `db:"country" json:"country"` Country OptionalString
EyeColor *sql.NullString `db:"eye_color" json:"eye_color"` EyeColor OptionalString
Height *sql.NullString `db:"height" json:"height"` Height OptionalString
Measurements *sql.NullString `db:"measurements" json:"measurements"` Measurements OptionalString
FakeTits *sql.NullString `db:"fake_tits" json:"fake_tits"` FakeTits OptionalString
CareerLength *sql.NullString `db:"career_length" json:"career_length"` CareerLength OptionalString
Tattoos *sql.NullString `db:"tattoos" json:"tattoos"` Tattoos OptionalString
Piercings *sql.NullString `db:"piercings" json:"piercings"` Piercings OptionalString
Aliases *sql.NullString `db:"aliases" json:"aliases"` Aliases OptionalString
Favorite *sql.NullBool `db:"favorite" json:"favorite"` Favorite OptionalBool
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` CreatedAt OptionalTime
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` UpdatedAt OptionalTime
Rating *sql.NullInt64 `db:"rating" json:"rating"` Rating OptionalInt
Details *sql.NullString `db:"details" json:"details"` Details OptionalString
DeathDate *SQLiteDate `db:"death_date" json:"death_date"` DeathDate OptionalDate
HairColor *sql.NullString `db:"hair_color" json:"hair_color"` HairColor OptionalString
Weight *sql.NullInt64 `db:"weight" json:"weight"` Weight OptionalInt
IgnoreAutoTag *bool `db:"ignore_auto_tag" json:"ignore_auto_tag"` IgnoreAutoTag OptionalBool
} }
func NewPerformer(name string) *Performer { func NewPerformer(name string) *Performer {
currentTime := time.Now() currentTime := time.Now()
return &Performer{ return &Performer{
Checksum: md5.FromString(name), Checksum: md5.FromString(name),
Name: sql.NullString{String: name, Valid: true}, Name: name,
Favorite: sql.NullBool{Bool: false, Valid: true}, CreatedAt: currentTime,
CreatedAt: SQLiteTimestamp{Timestamp: currentTime}, UpdatedAt: currentTime,
UpdatedAt: SQLiteTimestamp{Timestamp: currentTime}, }
}
func NewPerformerPartial() PerformerPartial {
updatedTime := time.Now()
return PerformerPartial{
UpdatedAt: NewOptionalTime(updatedTime),
} }
} }

View file

@ -133,7 +133,6 @@ type PerformerReader interface {
Find(ctx context.Context, id int) (*Performer, error) Find(ctx context.Context, id int) (*Performer, error)
PerformerFinder PerformerFinder
FindBySceneID(ctx context.Context, sceneID int) ([]*Performer, error) FindBySceneID(ctx context.Context, sceneID int) ([]*Performer, error)
FindNamesBySceneID(ctx context.Context, sceneID int) ([]*Performer, error)
FindByImageID(ctx context.Context, imageID int) ([]*Performer, error) FindByImageID(ctx context.Context, imageID int) ([]*Performer, error)
FindByGalleryID(ctx context.Context, galleryID int) ([]*Performer, error) FindByGalleryID(ctx context.Context, galleryID int) ([]*Performer, error)
FindByNames(ctx context.Context, names []string, nocase bool) ([]*Performer, error) FindByNames(ctx context.Context, names []string, nocase bool) ([]*Performer, error)
@ -152,9 +151,9 @@ type PerformerReader interface {
} }
type PerformerWriter interface { type PerformerWriter interface {
Create(ctx context.Context, newPerformer Performer) (*Performer, error) Create(ctx context.Context, newPerformer *Performer) error
Update(ctx context.Context, updatedPerformer PerformerPartial) (*Performer, error) UpdatePartial(ctx context.Context, id int, updatedPerformer PerformerPartial) (*Performer, error)
UpdateFull(ctx context.Context, updatedPerformer Performer) (*Performer, error) Update(ctx context.Context, updatedPerformer *Performer) error
Destroy(ctx context.Context, id int) error Destroy(ctx context.Context, id int) error
UpdateImage(ctx context.Context, performerID int, image []byte) error UpdateImage(ctx context.Context, performerID int, image []byte) error
DestroyImage(ctx context.Context, performerID int) error DestroyImage(ctx context.Context, performerID int) error

View file

@ -18,76 +18,40 @@ type ImageStashIDGetter interface {
// ToJSON converts a Performer object into its JSON equivalent. // ToJSON converts a Performer object into its JSON equivalent.
func ToJSON(ctx context.Context, reader ImageStashIDGetter, performer *models.Performer) (*jsonschema.Performer, error) { func ToJSON(ctx context.Context, reader ImageStashIDGetter, performer *models.Performer) (*jsonschema.Performer, error) {
newPerformerJSON := jsonschema.Performer{ newPerformerJSON := jsonschema.Performer{
Name: performer.Name,
Gender: performer.Gender.String(),
URL: performer.URL,
Ethnicity: performer.Ethnicity,
Country: performer.Country,
EyeColor: performer.EyeColor,
Height: performer.Height,
Measurements: performer.Measurements,
FakeTits: performer.FakeTits,
CareerLength: performer.CareerLength,
Tattoos: performer.Tattoos,
Piercings: performer.Piercings,
Aliases: performer.Aliases,
Twitter: performer.Twitter,
Instagram: performer.Instagram,
Favorite: performer.Favorite,
Details: performer.Details,
HairColor: performer.HairColor,
IgnoreAutoTag: performer.IgnoreAutoTag, IgnoreAutoTag: performer.IgnoreAutoTag,
CreatedAt: json.JSONTime{Time: performer.CreatedAt.Timestamp}, CreatedAt: json.JSONTime{Time: performer.CreatedAt},
UpdatedAt: json.JSONTime{Time: performer.UpdatedAt.Timestamp}, UpdatedAt: json.JSONTime{Time: performer.UpdatedAt},
} }
if performer.Name.Valid { if performer.Birthdate != nil {
newPerformerJSON.Name = performer.Name.String newPerformerJSON.Birthdate = performer.Birthdate.String()
} }
if performer.Gender.Valid { if performer.Rating != nil {
newPerformerJSON.Gender = performer.Gender.String newPerformerJSON.Rating = *performer.Rating
} }
if performer.URL.Valid { if performer.DeathDate != nil {
newPerformerJSON.URL = performer.URL.String newPerformerJSON.DeathDate = performer.DeathDate.String()
} }
if performer.Birthdate.Valid { if performer.Weight != nil {
newPerformerJSON.Birthdate = utils.GetYMDFromDatabaseDate(performer.Birthdate.String) newPerformerJSON.Weight = *performer.Weight
}
if performer.Ethnicity.Valid {
newPerformerJSON.Ethnicity = performer.Ethnicity.String
}
if performer.Country.Valid {
newPerformerJSON.Country = performer.Country.String
}
if performer.EyeColor.Valid {
newPerformerJSON.EyeColor = performer.EyeColor.String
}
if performer.Height.Valid {
newPerformerJSON.Height = performer.Height.String
}
if performer.Measurements.Valid {
newPerformerJSON.Measurements = performer.Measurements.String
}
if performer.FakeTits.Valid {
newPerformerJSON.FakeTits = performer.FakeTits.String
}
if performer.CareerLength.Valid {
newPerformerJSON.CareerLength = performer.CareerLength.String
}
if performer.Tattoos.Valid {
newPerformerJSON.Tattoos = performer.Tattoos.String
}
if performer.Piercings.Valid {
newPerformerJSON.Piercings = performer.Piercings.String
}
if performer.Aliases.Valid {
newPerformerJSON.Aliases = performer.Aliases.String
}
if performer.Twitter.Valid {
newPerformerJSON.Twitter = performer.Twitter.String
}
if performer.Instagram.Valid {
newPerformerJSON.Instagram = performer.Instagram.String
}
if performer.Favorite.Valid {
newPerformerJSON.Favorite = performer.Favorite.Bool
}
if performer.Rating.Valid {
newPerformerJSON.Rating = int(performer.Rating.Int64)
}
if performer.Details.Valid {
newPerformerJSON.Details = performer.Details.String
}
if performer.DeathDate.Valid {
newPerformerJSON.DeathDate = utils.GetYMDFromDatabaseDate(performer.DeathDate.String)
}
if performer.HairColor.Valid {
newPerformerJSON.HairColor = performer.HairColor.String
}
if performer.Weight.Valid {
newPerformerJSON.Weight = int(performer.Weight.Int64)
} }
image, err := reader.GetImage(ctx, performer.ID) image, err := reader.GetImage(ctx, performer.ID)
@ -126,8 +90,8 @@ func GetIDs(performers []*models.Performer) []int {
func GetNames(performers []*models.Performer) []string { func GetNames(performers []*models.Performer) []string {
var results []string var results []string
for _, performer := range performers { for _, performer := range performers {
if performer.Name.Valid { if performer.Name != "" {
results = append(results, performer.Name.String) results = append(results, performer.Name)
} }
} }

View file

@ -1,7 +1,6 @@
package performer package performer
import ( import (
"database/sql"
"errors" "errors"
"github.com/stashapp/stash/pkg/hash/md5" "github.com/stashapp/stash/pkg/hash/md5"
@ -22,28 +21,32 @@ const (
) )
const ( const (
performerName = "testPerformer" performerName = "testPerformer"
url = "url" url = "url"
aliases = "aliases" aliases = "aliases"
careerLength = "careerLength" careerLength = "careerLength"
country = "country" country = "country"
ethnicity = "ethnicity" ethnicity = "ethnicity"
eyeColor = "eyeColor" eyeColor = "eyeColor"
fakeTits = "fakeTits" fakeTits = "fakeTits"
gender = "gender" gender = "gender"
height = "height" height = "height"
instagram = "instagram" instagram = "instagram"
measurements = "measurements" measurements = "measurements"
piercings = "piercings" piercings = "piercings"
tattoos = "tattoos" tattoos = "tattoos"
twitter = "twitter" twitter = "twitter"
rating = 5 details = "details"
details = "details" hairColor = "hairColor"
hairColor = "hairColor"
weight = 60
autoTagIgnored = true autoTagIgnored = true
) )
var (
rating = 5
weight = 60
)
var imageBytes = []byte("imageBytes") var imageBytes = []byte("imageBytes")
var stashID = models.StashID{ var stashID = models.StashID{
@ -56,14 +59,8 @@ var stashIDs = []models.StashID{
const image = "aW1hZ2VCeXRlcw==" const image = "aW1hZ2VCeXRlcw=="
var birthDate = models.SQLiteDate{ var birthDate = models.NewDate("2001-01-01")
String: "2001-01-01", var deathDate = models.NewDate("2021-02-02")
Valid: true,
}
var deathDate = models.SQLiteDate{
String: "2021-02-02",
Valid: true,
}
var ( var (
createTime = time.Date(2001, 01, 01, 0, 0, 0, 0, time.Local) createTime = time.Date(2001, 01, 01, 0, 0, 0, 0, time.Local)
@ -72,55 +69,41 @@ var (
func createFullPerformer(id int, name string) *models.Performer { func createFullPerformer(id int, name string) *models.Performer {
return &models.Performer{ return &models.Performer{
ID: id, ID: id,
Name: models.NullString(name), Name: name,
Checksum: md5.FromString(name), Checksum: md5.FromString(name),
URL: models.NullString(url), URL: url,
Aliases: models.NullString(aliases), Aliases: aliases,
Birthdate: birthDate, Birthdate: &birthDate,
CareerLength: models.NullString(careerLength), CareerLength: careerLength,
Country: models.NullString(country), Country: country,
Ethnicity: models.NullString(ethnicity), Ethnicity: ethnicity,
EyeColor: models.NullString(eyeColor), EyeColor: eyeColor,
FakeTits: models.NullString(fakeTits), FakeTits: fakeTits,
Favorite: sql.NullBool{ Favorite: true,
Bool: true, Gender: gender,
Valid: true, Height: height,
}, Instagram: instagram,
Gender: models.NullString(gender), Measurements: measurements,
Height: models.NullString(height), Piercings: piercings,
Instagram: models.NullString(instagram), Tattoos: tattoos,
Measurements: models.NullString(measurements), Twitter: twitter,
Piercings: models.NullString(piercings), CreatedAt: createTime,
Tattoos: models.NullString(tattoos), UpdatedAt: updateTime,
Twitter: models.NullString(twitter), Rating: &rating,
CreatedAt: models.SQLiteTimestamp{ Details: details,
Timestamp: createTime, DeathDate: &deathDate,
}, HairColor: hairColor,
UpdatedAt: models.SQLiteTimestamp{ Weight: &weight,
Timestamp: updateTime,
},
Rating: models.NullInt64(rating),
Details: models.NullString(details),
DeathDate: deathDate,
HairColor: models.NullString(hairColor),
Weight: sql.NullInt64{
Int64: weight,
Valid: true,
},
IgnoreAutoTag: autoTagIgnored, IgnoreAutoTag: autoTagIgnored,
} }
} }
func createEmptyPerformer(id int) models.Performer { func createEmptyPerformer(id int) models.Performer {
return models.Performer{ return models.Performer{
ID: id, ID: id,
CreatedAt: models.SQLiteTimestamp{ CreatedAt: createTime,
Timestamp: createTime, UpdatedAt: updateTime,
},
UpdatedAt: models.SQLiteTimestamp{
Timestamp: updateTime,
},
} }
} }
@ -129,7 +112,7 @@ func createFullJSONPerformer(name string, image string) *jsonschema.Performer {
Name: name, Name: name,
URL: url, URL: url,
Aliases: aliases, Aliases: aliases,
Birthdate: birthDate.String, Birthdate: birthDate.String(),
CareerLength: careerLength, CareerLength: careerLength,
Country: country, Country: country,
Ethnicity: ethnicity, Ethnicity: ethnicity,
@ -152,7 +135,7 @@ func createFullJSONPerformer(name string, image string) *jsonschema.Performer {
Rating: rating, Rating: rating,
Image: image, Image: image,
Details: details, Details: details,
DeathDate: deathDate.String, DeathDate: deathDate.String(),
HairColor: hairColor, HairColor: hairColor,
Weight: weight, Weight: weight,
StashIDs: []models.StashID{ StashIDs: []models.StashID{

View file

@ -2,7 +2,6 @@ package performer
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"strings" "strings"
@ -16,7 +15,7 @@ import (
type NameFinderCreatorUpdater interface { type NameFinderCreatorUpdater interface {
NameFinderCreator NameFinderCreator
UpdateFull(ctx context.Context, updatedPerformer models.Performer) (*models.Performer, error) Update(ctx context.Context, updatedPerformer *models.Performer) error
UpdateTags(ctx context.Context, performerID int, tagIDs []int) error UpdateTags(ctx context.Context, performerID int, tagIDs []int) error
UpdateImage(ctx context.Context, performerID int, image []byte) error UpdateImage(ctx context.Context, performerID int, image []byte) error
UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error
@ -164,19 +163,19 @@ func (i *Importer) FindExistingID(ctx context.Context) (*int, error) {
} }
func (i *Importer) Create(ctx context.Context) (*int, error) { func (i *Importer) Create(ctx context.Context) (*int, error) {
created, err := i.ReaderWriter.Create(ctx, i.performer) err := i.ReaderWriter.Create(ctx, &i.performer)
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating performer: %v", err) return nil, fmt.Errorf("error creating performer: %v", err)
} }
id := created.ID id := i.performer.ID
return &id, nil return &id, nil
} }
func (i *Importer) Update(ctx context.Context, id int) error { func (i *Importer) Update(ctx context.Context, id int) error {
performer := i.performer performer := i.performer
performer.ID = id performer.ID = id
_, err := i.ReaderWriter.UpdateFull(ctx, performer) err := i.ReaderWriter.Update(ctx, &performer)
if err != nil { if err != nil {
return fmt.Errorf("error updating existing performer: %v", err) return fmt.Errorf("error updating existing performer: %v", err)
} }
@ -188,75 +187,52 @@ func performerJSONToPerformer(performerJSON jsonschema.Performer) models.Perform
checksum := md5.FromString(performerJSON.Name) checksum := md5.FromString(performerJSON.Name)
newPerformer := models.Performer{ newPerformer := models.Performer{
Name: performerJSON.Name,
Checksum: checksum, Checksum: checksum,
Favorite: sql.NullBool{Bool: performerJSON.Favorite, Valid: true}, Gender: models.GenderEnum(performerJSON.Gender),
URL: performerJSON.URL,
Ethnicity: performerJSON.Ethnicity,
Country: performerJSON.Country,
EyeColor: performerJSON.EyeColor,
Height: performerJSON.Height,
Measurements: performerJSON.Measurements,
FakeTits: performerJSON.FakeTits,
CareerLength: performerJSON.CareerLength,
Tattoos: performerJSON.Tattoos,
Piercings: performerJSON.Piercings,
Aliases: performerJSON.Aliases,
Twitter: performerJSON.Twitter,
Instagram: performerJSON.Instagram,
Details: performerJSON.Details,
HairColor: performerJSON.HairColor,
Favorite: performerJSON.Favorite,
IgnoreAutoTag: performerJSON.IgnoreAutoTag, IgnoreAutoTag: performerJSON.IgnoreAutoTag,
CreatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.CreatedAt.GetTime()}, CreatedAt: performerJSON.CreatedAt.GetTime(),
UpdatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.UpdatedAt.GetTime()}, UpdatedAt: performerJSON.UpdatedAt.GetTime(),
} }
if performerJSON.Name != "" {
newPerformer.Name = sql.NullString{String: performerJSON.Name, Valid: true}
}
if performerJSON.Gender != "" {
newPerformer.Gender = sql.NullString{String: performerJSON.Gender, Valid: true}
}
if performerJSON.URL != "" {
newPerformer.URL = sql.NullString{String: performerJSON.URL, Valid: true}
}
if performerJSON.Birthdate != "" { if performerJSON.Birthdate != "" {
newPerformer.Birthdate = models.SQLiteDate{String: performerJSON.Birthdate, Valid: true} d, err := utils.ParseDateStringAsTime(performerJSON.Birthdate)
} if err == nil {
if performerJSON.Ethnicity != "" { newPerformer.Birthdate = &models.Date{
newPerformer.Ethnicity = sql.NullString{String: performerJSON.Ethnicity, Valid: true} Time: d,
} }
if performerJSON.Country != "" { }
newPerformer.Country = sql.NullString{String: performerJSON.Country, Valid: true}
}
if performerJSON.EyeColor != "" {
newPerformer.EyeColor = sql.NullString{String: performerJSON.EyeColor, Valid: true}
}
if performerJSON.Height != "" {
newPerformer.Height = sql.NullString{String: performerJSON.Height, Valid: true}
}
if performerJSON.Measurements != "" {
newPerformer.Measurements = sql.NullString{String: performerJSON.Measurements, Valid: true}
}
if performerJSON.FakeTits != "" {
newPerformer.FakeTits = sql.NullString{String: performerJSON.FakeTits, Valid: true}
}
if performerJSON.CareerLength != "" {
newPerformer.CareerLength = sql.NullString{String: performerJSON.CareerLength, Valid: true}
}
if performerJSON.Tattoos != "" {
newPerformer.Tattoos = sql.NullString{String: performerJSON.Tattoos, Valid: true}
}
if performerJSON.Piercings != "" {
newPerformer.Piercings = sql.NullString{String: performerJSON.Piercings, Valid: true}
}
if performerJSON.Aliases != "" {
newPerformer.Aliases = sql.NullString{String: performerJSON.Aliases, Valid: true}
}
if performerJSON.Twitter != "" {
newPerformer.Twitter = sql.NullString{String: performerJSON.Twitter, Valid: true}
}
if performerJSON.Instagram != "" {
newPerformer.Instagram = sql.NullString{String: performerJSON.Instagram, Valid: true}
} }
if performerJSON.Rating != 0 { if performerJSON.Rating != 0 {
newPerformer.Rating = sql.NullInt64{Int64: int64(performerJSON.Rating), Valid: true} newPerformer.Rating = &performerJSON.Rating
}
if performerJSON.Details != "" {
newPerformer.Details = sql.NullString{String: performerJSON.Details, Valid: true}
} }
if performerJSON.DeathDate != "" { if performerJSON.DeathDate != "" {
newPerformer.DeathDate = models.SQLiteDate{String: performerJSON.DeathDate, Valid: true} d, err := utils.ParseDateStringAsTime(performerJSON.DeathDate)
} if err == nil {
if performerJSON.HairColor != "" { newPerformer.DeathDate = &models.Date{
newPerformer.HairColor = sql.NullString{String: performerJSON.HairColor, Valid: true} Time: d,
}
}
} }
if performerJSON.Weight != 0 { if performerJSON.Weight != 0 {
newPerformer.Weight = sql.NullInt64{Int64: int64(performerJSON.Weight), Valid: true} newPerformer.Weight = &performerJSON.Weight
} }
return newPerformer return newPerformer

View file

@ -237,11 +237,11 @@ func TestCreate(t *testing.T) {
readerWriter := &mocks.PerformerReaderWriter{} readerWriter := &mocks.PerformerReaderWriter{}
performer := models.Performer{ performer := models.Performer{
Name: models.NullString(performerName), Name: performerName,
} }
performerErr := models.Performer{ performerErr := models.Performer{
Name: models.NullString(performerNameErr), Name: performerNameErr,
} }
i := Importer{ i := Importer{
@ -250,10 +250,11 @@ func TestCreate(t *testing.T) {
} }
errCreate := errors.New("Create error") errCreate := errors.New("Create error")
readerWriter.On("Create", testCtx, performer).Return(&models.Performer{ readerWriter.On("Create", testCtx, &performer).Run(func(args mock.Arguments) {
ID: performerID, arg := args.Get(1).(*models.Performer)
}, nil).Once() arg.ID = performerID
readerWriter.On("Create", testCtx, performerErr).Return(nil, errCreate).Once() }).Return(nil).Once()
readerWriter.On("Create", testCtx, &performerErr).Return(errCreate).Once()
id, err := i.Create(testCtx) id, err := i.Create(testCtx)
assert.Equal(t, performerID, *id) assert.Equal(t, performerID, *id)
@ -271,11 +272,11 @@ func TestUpdate(t *testing.T) {
readerWriter := &mocks.PerformerReaderWriter{} readerWriter := &mocks.PerformerReaderWriter{}
performer := models.Performer{ performer := models.Performer{
Name: models.NullString(performerName), Name: performerName,
} }
performerErr := models.Performer{ performerErr := models.Performer{
Name: models.NullString(performerNameErr), Name: performerNameErr,
} }
i := Importer{ i := Importer{
@ -287,7 +288,7 @@ func TestUpdate(t *testing.T) {
// id needs to be set for the mock input // id needs to be set for the mock input
performer.ID = performerID performer.ID = performerID
readerWriter.On("UpdateFull", testCtx, performer).Return(nil, nil).Once() readerWriter.On("Update", testCtx, &performer).Return(nil).Once()
err := i.Update(testCtx, performerID) err := i.Update(testCtx, performerID)
assert.Nil(t, err) assert.Nil(t, err)
@ -296,7 +297,7 @@ func TestUpdate(t *testing.T) {
// need to set id separately // need to set id separately
performerErr.ID = errImageID performerErr.ID = errImageID
readerWriter.On("UpdateFull", testCtx, performerErr).Return(nil, errUpdate).Once() readerWriter.On("Update", testCtx, &performerErr).Return(errUpdate).Once()
err = i.Update(testCtx, errImageID) err = i.Update(testCtx, errImageID)
assert.NotNil(t, err) assert.NotNil(t, err)

View file

@ -8,5 +8,5 @@ import (
type NameFinderCreator interface { type NameFinderCreator interface {
FindByNames(ctx context.Context, names []string, nocase bool) ([]*models.Performer, error) FindByNames(ctx context.Context, names []string, nocase bool) ([]*models.Performer, error)
Create(ctx context.Context, newPerformer models.Performer) (*models.Performer, error) Create(ctx context.Context, newPerformer *models.Performer) error
} }

View file

@ -14,11 +14,13 @@ func ValidateDeathDate(performer *models.Performer, birthdate *string, deathDate
} }
if performer != nil { if performer != nil {
if birthdate == nil && performer.Birthdate.Valid { if birthdate == nil && performer.Birthdate != nil {
birthdate = &performer.Birthdate.String s := performer.Birthdate.String()
birthdate = &s
} }
if deathDate == nil && performer.DeathDate.Valid { if deathDate == nil && performer.DeathDate != nil {
deathDate = &performer.DeathDate.String s := performer.DeathDate.String()
deathDate = &s
} }
} }

View file

@ -16,26 +16,17 @@ func TestValidateDeathDate(t *testing.T) {
date4 := "2004-01-01" date4 := "2004-01-01"
empty := "" empty := ""
md2 := models.NewDate(date2)
md3 := models.NewDate(date3)
emptyPerformer := models.Performer{} emptyPerformer := models.Performer{}
invalidPerformer := models.Performer{ invalidPerformer := models.Performer{
Birthdate: models.SQLiteDate{ Birthdate: &md3,
String: date3, DeathDate: &md2,
Valid: true,
},
DeathDate: models.SQLiteDate{
String: date2,
Valid: true,
},
} }
validPerformer := models.Performer{ validPerformer := models.Performer{
Birthdate: models.SQLiteDate{ Birthdate: &md2,
String: date2, DeathDate: &md3,
Valid: true,
},
DeathDate: models.SQLiteDate{
String: date3,
Valid: true,
},
} }
// nil values should always return nil // nil values should always return nil

View file

@ -231,10 +231,10 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
var pluckedNames []string var pluckedNames []string
for _, performer := range performers { for _, performer := range performers {
if !performer.Name.Valid { if performer.Name == "" {
continue continue
} }
pluckedNames = append(pluckedNames, performer.Name.String) pluckedNames = append(pluckedNames, performer.Name)
} }
missingPerformers := stringslice.StrFilter(names, func(name string) bool { missingPerformers := stringslice.StrFilter(names, func(name string) bool {
@ -271,12 +271,12 @@ func (i *Importer) createPerformers(ctx context.Context, names []string) ([]*mod
for _, name := range names { for _, name := range names {
newPerformer := *models.NewPerformer(name) newPerformer := *models.NewPerformer(name)
created, err := i.PerformerWriter.Create(ctx, newPerformer) err := i.PerformerWriter.Create(ctx, &newPerformer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret = append(ret, created) ret = append(ret, &newPerformer)
} }
return ret, nil return ret, nil

View file

@ -147,7 +147,7 @@ func TestImporterPreImportWithPerformer(t *testing.T) {
performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerName}, false).Return([]*models.Performer{ performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerName}, false).Return([]*models.Performer{
{ {
ID: existingPerformerID, ID: existingPerformerID,
Name: models.NullString(existingPerformerName), Name: existingPerformerName,
}, },
}, nil).Once() }, nil).Once()
performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once() performerReaderWriter.On("FindByNames", testCtx, []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once()
@ -177,9 +177,10 @@ func TestImporterPreImportWithMissingPerformer(t *testing.T) {
} }
performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Times(3) performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Times(3)
performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("models.Performer")).Return(&models.Performer{ performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("*models.Performer")).Run(func(args mock.Arguments) {
ID: existingPerformerID, p := args.Get(1).(*models.Performer)
}, nil) p.ID = existingPerformerID
}).Return(nil)
err := i.PreImport(testCtx) err := i.PreImport(testCtx)
assert.NotNil(t, err) assert.NotNil(t, err)
@ -210,7 +211,7 @@ func TestImporterPreImportWithMissingPerformerCreateErr(t *testing.T) {
} }
performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Once() performerReaderWriter.On("FindByNames", testCtx, []string{missingPerformerName}, false).Return(nil, nil).Once()
performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("models.Performer")).Return(nil, errors.New("Create error")) performerReaderWriter.On("Create", testCtx, mock.AnythingOfType("*models.Performer")).Return(errors.New("Create error"))
err := i.PreImport(testCtx) err := i.PreImport(testCtx)
assert.NotNil(t, err) assert.NotNil(t, err)

View file

@ -38,11 +38,12 @@ func autotagMatchPerformers(ctx context.Context, path string, performerReader ma
id := strconv.Itoa(pp.ID) id := strconv.Itoa(pp.ID)
sp := &models.ScrapedPerformer{ sp := &models.ScrapedPerformer{
Name: &pp.Name.String, Name: &pp.Name,
StoredID: &id, StoredID: &id,
} }
if pp.Gender.Valid { if pp.Gender.IsValid() {
sp.Gender = &pp.Gender.String v := pp.Gender.String()
sp.Gender = &v
} }
ret = append(ret, sp) ret = append(ret, sp)

View file

@ -379,7 +379,7 @@ func (c Client) FindStashBoxPerformersByNames(ctx context.Context, performerIDs
return fmt.Errorf("performer with id %d not found", performerID) return fmt.Errorf("performer with id %d not found", performerID)
} }
if performer.Name.Valid { if performer.Name != "" {
performers = append(performers, performer) performers = append(performers, performer)
} }
} }
@ -413,7 +413,7 @@ func (c Client) FindStashBoxPerformersByPerformerNames(ctx context.Context, perf
return fmt.Errorf("performer with id %d not found", performerID) return fmt.Errorf("performer with id %d not found", performerID)
} }
if performer.Name.Valid { if performer.Name != "" {
performers = append(performers, performer) performers = append(performers, performer)
} }
} }
@ -439,8 +439,8 @@ func (c Client) FindStashBoxPerformersByPerformerNames(ctx context.Context, perf
func (c Client) findStashBoxPerformersByNames(ctx context.Context, performers []*models.Performer) ([]*StashBoxPerformerQueryResult, error) { func (c Client) findStashBoxPerformersByNames(ctx context.Context, performers []*models.Performer) ([]*StashBoxPerformerQueryResult, error) {
var ret []*StashBoxPerformerQueryResult var ret []*StashBoxPerformerQueryResult
for _, performer := range performers { for _, performer := range performers {
if performer.Name.Valid { if performer.Name != "" {
performerResults, err := c.queryStashBoxPerformer(ctx, performer.Name.String) performerResults, err := c.queryStashBoxPerformer(ctx, performer.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -867,7 +867,7 @@ func (c Client) SubmitSceneDraft(ctx context.Context, scene *models.Scene, endpo
performers := []*graphql.DraftEntityInput{} performers := []*graphql.DraftEntityInput{}
for _, p := range scenePerformers { for _, p := range scenePerformers {
performerDraft := graphql.DraftEntityInput{ performerDraft := graphql.DraftEntityInput{
Name: p.Name.String, Name: p.Name,
} }
stashIDs, err := pqb.GetStashIDs(ctx, p.ID) stashIDs, err := pqb.GetStashIDs(ctx, p.ID)
@ -947,55 +947,57 @@ func (c Client) SubmitPerformerDraft(ctx context.Context, performer *models.Perf
image = bytes.NewReader(img) image = bytes.NewReader(img)
} }
if performer.Name.Valid { if performer.Name != "" {
draft.Name = performer.Name.String draft.Name = performer.Name
} }
if performer.Birthdate.Valid { if performer.Birthdate != nil {
draft.Birthdate = &performer.Birthdate.String d := performer.Birthdate.String()
draft.Birthdate = &d
} }
if performer.Country.Valid { if performer.Country != "" {
draft.Country = &performer.Country.String draft.Country = &performer.Country
} }
if performer.Ethnicity.Valid { if performer.Ethnicity != "" {
draft.Ethnicity = &performer.Ethnicity.String draft.Ethnicity = &performer.Ethnicity
} }
if performer.EyeColor.Valid { if performer.EyeColor != "" {
draft.EyeColor = &performer.EyeColor.String draft.EyeColor = &performer.EyeColor
} }
if performer.FakeTits.Valid { if performer.FakeTits != "" {
draft.BreastType = &performer.FakeTits.String draft.BreastType = &performer.FakeTits
} }
if performer.Gender.Valid { if performer.Gender.IsValid() {
draft.Gender = &performer.Gender.String v := performer.Gender.String()
draft.Gender = &v
} }
if performer.HairColor.Valid { if performer.HairColor != "" {
draft.HairColor = &performer.HairColor.String draft.HairColor = &performer.HairColor
} }
if performer.Height.Valid { if performer.Height != "" {
draft.Height = &performer.Height.String draft.Height = &performer.Height
} }
if performer.Measurements.Valid { if performer.Measurements != "" {
draft.Measurements = &performer.Measurements.String draft.Measurements = &performer.Measurements
} }
if performer.Piercings.Valid { if performer.Piercings != "" {
draft.Piercings = &performer.Piercings.String draft.Piercings = &performer.Piercings
} }
if performer.Tattoos.Valid { if performer.Tattoos != "" {
draft.Tattoos = &performer.Tattoos.String draft.Tattoos = &performer.Tattoos
} }
if performer.Aliases.Valid { if performer.Aliases != "" {
draft.Aliases = &performer.Aliases.String draft.Aliases = &performer.Aliases
} }
var urls []string var urls []string
if len(strings.TrimSpace(performer.Twitter.String)) > 0 { if len(strings.TrimSpace(performer.Twitter)) > 0 {
urls = append(urls, "https://twitter.com/"+strings.TrimSpace(performer.Twitter.String)) urls = append(urls, "https://twitter.com/"+strings.TrimSpace(performer.Twitter))
} }
if len(strings.TrimSpace(performer.Instagram.String)) > 0 { if len(strings.TrimSpace(performer.Instagram)) > 0 {
urls = append(urls, "https://instagram.com/"+strings.TrimSpace(performer.Instagram.String)) urls = append(urls, "https://instagram.com/"+strings.TrimSpace(performer.Instagram))
} }
if len(strings.TrimSpace(performer.URL.String)) > 0 { if len(strings.TrimSpace(performer.URL)) > 0 {
urls = append(urls, strings.TrimSpace(performer.URL.String)) urls = append(urls, strings.TrimSpace(performer.URL))
} }
if len(urls) > 0 { if len(urls) > 0 {
draft.Urls = urls draft.Urls = urls

View file

@ -61,11 +61,12 @@ func init() {
} }
type Database struct { type Database struct {
File *FileStore File *FileStore
Folder *FolderStore Folder *FolderStore
Image *ImageStore Image *ImageStore
Gallery *GalleryStore Gallery *GalleryStore
Scene *SceneStore Scene *SceneStore
Performer *PerformerStore
db *sqlx.DB db *sqlx.DB
dbPath string dbPath string
@ -80,11 +81,12 @@ func NewDatabase() *Database {
folderStore := NewFolderStore() folderStore := NewFolderStore()
ret := &Database{ ret := &Database{
File: fileStore, File: fileStore,
Folder: folderStore, Folder: folderStore,
Scene: NewSceneStore(fileStore), Scene: NewSceneStore(fileStore),
Image: NewImageStore(fileStore), Image: NewImageStore(fileStore),
Gallery: NewGalleryStore(fileStore, folderStore), Gallery: NewGalleryStore(fileStore, folderStore),
Performer: NewPerformerStore(),
} }
return ret return ret

View file

@ -3,15 +3,17 @@ package sqlite
import ( import (
"context" "context"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"strings" "strings"
"github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil/intslice" "github.com/stashapp/stash/pkg/sliceutil/intslice"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"gopkg.in/guregu/null.v4"
"gopkg.in/guregu/null.v4/zero"
) )
const performerTable = "performers" const performerTable = "performers"
@ -19,82 +21,227 @@ const performerIDColumn = "performer_id"
const performersTagsTable = "performers_tags" const performersTagsTable = "performers_tags"
const performersImageTable = "performers_image" // performer cover image const performersImageTable = "performers_image" // performer cover image
var countPerformersForTagQuery = ` type performerRow struct {
SELECT tag_id AS id FROM performers_tags ID int `db:"id" goqu:"skipinsert"`
WHERE performers_tags.tag_id = ? Checksum string `db:"checksum"`
GROUP BY performers_tags.performer_id Name zero.String `db:"name"`
` Gender zero.String `db:"gender"`
URL zero.String `db:"url"`
Twitter zero.String `db:"twitter"`
Instagram zero.String `db:"instagram"`
Birthdate models.SQLiteDate `db:"birthdate"`
Ethnicity zero.String `db:"ethnicity"`
Country zero.String `db:"country"`
EyeColor zero.String `db:"eye_color"`
Height zero.String `db:"height"`
Measurements zero.String `db:"measurements"`
FakeTits zero.String `db:"fake_tits"`
CareerLength zero.String `db:"career_length"`
Tattoos zero.String `db:"tattoos"`
Piercings zero.String `db:"piercings"`
Aliases zero.String `db:"aliases"`
Favorite sql.NullBool `db:"favorite"`
CreatedAt models.SQLiteTimestamp `db:"created_at"`
UpdatedAt models.SQLiteTimestamp `db:"updated_at"`
Rating null.Int `db:"rating"`
Details zero.String `db:"details"`
DeathDate models.SQLiteDate `db:"death_date"`
HairColor zero.String `db:"hair_color"`
Weight null.Int `db:"weight"`
IgnoreAutoTag bool `db:"ignore_auto_tag"`
}
type performerQueryBuilder struct { func (r *performerRow) fromPerformer(o models.Performer) {
r.ID = o.ID
r.Checksum = o.Checksum
r.Name = zero.StringFrom(o.Name)
if o.Gender.IsValid() {
r.Gender = zero.StringFrom(o.Gender.String())
}
r.URL = zero.StringFrom(o.URL)
r.Twitter = zero.StringFrom(o.Twitter)
r.Instagram = zero.StringFrom(o.Instagram)
if o.Birthdate != nil {
_ = r.Birthdate.Scan(o.Birthdate.Time)
}
r.Ethnicity = zero.StringFrom(o.Ethnicity)
r.Country = zero.StringFrom(o.Country)
r.EyeColor = zero.StringFrom(o.EyeColor)
r.Height = zero.StringFrom(o.Height)
r.Measurements = zero.StringFrom(o.Measurements)
r.FakeTits = zero.StringFrom(o.FakeTits)
r.CareerLength = zero.StringFrom(o.CareerLength)
r.Tattoos = zero.StringFrom(o.Tattoos)
r.Piercings = zero.StringFrom(o.Piercings)
r.Aliases = zero.StringFrom(o.Aliases)
r.Favorite = sql.NullBool{Bool: o.Favorite, Valid: true}
r.CreatedAt = models.SQLiteTimestamp{Timestamp: o.CreatedAt}
r.UpdatedAt = models.SQLiteTimestamp{Timestamp: o.UpdatedAt}
r.Rating = intFromPtr(o.Rating)
r.Details = zero.StringFrom(o.Details)
if o.DeathDate != nil {
_ = r.DeathDate.Scan(o.DeathDate.Time)
}
r.HairColor = zero.StringFrom(o.HairColor)
r.Weight = intFromPtr(o.Weight)
r.IgnoreAutoTag = o.IgnoreAutoTag
}
func (r *performerRow) resolve() *models.Performer {
ret := &models.Performer{
ID: r.ID,
Checksum: r.Checksum,
Name: r.Name.String,
Gender: models.GenderEnum(r.Gender.String),
URL: r.URL.String,
Twitter: r.Twitter.String,
Instagram: r.Instagram.String,
Birthdate: r.Birthdate.DatePtr(),
Ethnicity: r.Ethnicity.String,
Country: r.Country.String,
EyeColor: r.EyeColor.String,
Height: r.Height.String,
Measurements: r.Measurements.String,
FakeTits: r.FakeTits.String,
CareerLength: r.CareerLength.String,
Tattoos: r.Tattoos.String,
Piercings: r.Piercings.String,
Aliases: r.Aliases.String,
Favorite: r.Favorite.Bool,
CreatedAt: r.CreatedAt.Timestamp,
UpdatedAt: r.UpdatedAt.Timestamp,
Rating: nullIntPtr(r.Rating),
Details: r.Details.String,
DeathDate: r.DeathDate.DatePtr(),
HairColor: r.HairColor.String,
Weight: nullIntPtr(r.Weight),
IgnoreAutoTag: r.IgnoreAutoTag,
}
return ret
}
type performerRowRecord struct {
updateRecord
}
func (r *performerRowRecord) fromPartial(o models.PerformerPartial) {
r.setNullString("checksum", o.Checksum)
r.setNullString("name", o.Name)
r.setNullString("gender", o.Gender)
r.setNullString("url", o.URL)
r.setNullString("twitter", o.Twitter)
r.setNullString("instagram", o.Instagram)
r.setSQLiteDate("birthdate", o.Birthdate)
r.setNullString("ethnicity", o.Ethnicity)
r.setNullString("country", o.Country)
r.setNullString("eye_color", o.EyeColor)
r.setNullString("height", o.Height)
r.setNullString("measurements", o.Measurements)
r.setNullString("fake_tits", o.FakeTits)
r.setNullString("career_length", o.CareerLength)
r.setNullString("tattoos", o.Tattoos)
r.setNullString("piercings", o.Piercings)
r.setNullString("aliases", o.Aliases)
r.setBool("favorite", o.Favorite)
r.setSQLiteTimestamp("created_at", o.CreatedAt)
r.setSQLiteTimestamp("updated_at", o.UpdatedAt)
r.setNullInt("rating", o.Rating)
r.setNullString("details", o.Details)
r.setSQLiteDate("death_date", o.DeathDate)
r.setNullString("hair_color", o.HairColor)
r.setNullInt("weight", o.Weight)
r.setBool("ignore_auto_tag", o.IgnoreAutoTag)
}
type PerformerStore struct {
repository repository
tableMgr *table
} }
var PerformerReaderWriter = &performerQueryBuilder{ func NewPerformerStore() *PerformerStore {
repository{ return &PerformerStore{
tableName: performerTable, repository: repository{
idColumn: idColumn, tableName: performerTable,
}, idColumn: idColumn,
},
tableMgr: performerTableMgr,
}
} }
func (qb *performerQueryBuilder) Create(ctx context.Context, newObject models.Performer) (*models.Performer, error) { func (qb *PerformerStore) Create(ctx context.Context, newObject *models.Performer) error {
var ret models.Performer var r performerRow
if err := qb.insertObject(ctx, newObject, &ret); err != nil { r.fromPerformer(*newObject)
return nil, err
}
return &ret, nil id, err := qb.tableMgr.insertID(ctx, r)
}
func (qb *performerQueryBuilder) Update(ctx context.Context, updatedObject models.PerformerPartial) (*models.Performer, error) {
const partial = true
if err := qb.update(ctx, updatedObject.ID, updatedObject, partial); err != nil {
return nil, err
}
var ret models.Performer
if err := qb.getByID(ctx, updatedObject.ID, &ret); err != nil {
return nil, err
}
return &ret, nil
}
func (qb *performerQueryBuilder) UpdateFull(ctx context.Context, updatedObject models.Performer) (*models.Performer, error) {
const partial = false
if err := qb.update(ctx, updatedObject.ID, updatedObject, partial); err != nil {
return nil, err
}
var ret models.Performer
if err := qb.getByID(ctx, updatedObject.ID, &ret); err != nil {
return nil, err
}
return &ret, nil
}
func (qb *performerQueryBuilder) Destroy(ctx context.Context, id int) error {
// TODO - add on delete cascade to performers_scenes
_, err := qb.tx.Exec(ctx, "DELETE FROM performers_scenes WHERE performer_id = ?", id)
if err != nil { if err != nil {
return err return err
} }
updated, err := qb.Find(ctx, id)
if err != nil {
return fmt.Errorf("finding after create: %w", err)
}
*newObject = *updated
return nil
}
func (qb *PerformerStore) UpdatePartial(ctx context.Context, id int, updatedObject models.PerformerPartial) (*models.Performer, error) {
r := performerRowRecord{
updateRecord{
Record: make(exp.Record),
},
}
r.fromPartial(updatedObject)
if len(r.Record) > 0 {
if err := qb.tableMgr.updateByID(ctx, id, r.Record); err != nil {
return nil, err
}
}
return qb.Find(ctx, id)
}
func (qb *PerformerStore) Update(ctx context.Context, updatedObject *models.Performer) error {
var r performerRow
r.fromPerformer(*updatedObject)
if err := qb.tableMgr.updateByID(ctx, updatedObject.ID, r); err != nil {
return err
}
return nil
}
func (qb *PerformerStore) Destroy(ctx context.Context, id int) error {
return qb.destroyExisting(ctx, []int{id}) return qb.destroyExisting(ctx, []int{id})
} }
func (qb *performerQueryBuilder) Find(ctx context.Context, id int) (*models.Performer, error) { func (qb *PerformerStore) table() exp.IdentifierExpression {
var ret models.Performer return qb.tableMgr.table
if err := qb.getByID(ctx, id, &ret); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, err
}
return &ret, nil
} }
func (qb *performerQueryBuilder) FindMany(ctx context.Context, ids []int) ([]*models.Performer, error) { func (qb *PerformerStore) selectDataset() *goqu.SelectDataset {
return dialect.From(qb.table()).Select(qb.table().All())
}
func (qb *PerformerStore) Find(ctx context.Context, id int) (*models.Performer, error) {
q := qb.selectDataset().Where(qb.tableMgr.byID(id))
ret, err := qb.get(ctx, q)
if err != nil {
return nil, fmt.Errorf("getting scene by id %d: %w", id, err)
}
return ret, nil
}
func (qb *PerformerStore) FindMany(ctx context.Context, ids []int) ([]*models.Performer, error) {
tableMgr := performerTableMgr tableMgr := performerTableMgr
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(ids...)) q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(ids...))
unsorted, err := qb.getMany(ctx, q) unsorted, err := qb.getMany(ctx, q)
@ -118,16 +265,31 @@ func (qb *performerQueryBuilder) FindMany(ctx context.Context, ids []int) ([]*mo
return ret, nil return ret, nil
} }
func (qb *performerQueryBuilder) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*models.Performer, error) { func (qb *PerformerStore) get(ctx context.Context, q *goqu.SelectDataset) (*models.Performer, error) {
ret, err := qb.getMany(ctx, q)
if err != nil {
return nil, err
}
if len(ret) == 0 {
return nil, sql.ErrNoRows
}
return ret[0], nil
}
func (qb *PerformerStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*models.Performer, error) {
const single = false const single = false
var ret []*models.Performer var ret []*models.Performer
if err := queryFunc(ctx, q, single, func(r *sqlx.Rows) error { if err := queryFunc(ctx, q, single, func(r *sqlx.Rows) error {
var f models.Performer var f performerRow
if err := r.StructScan(&f); err != nil { if err := r.StructScan(&f); err != nil {
return err return err
} }
ret = append(ret, &f) s := f.resolve()
ret = append(ret, s)
return nil return nil
}); err != nil { }); err != nil {
return nil, err return nil, err
@ -136,95 +298,126 @@ func (qb *performerQueryBuilder) getMany(ctx context.Context, q *goqu.SelectData
return ret, nil return ret, nil
} }
func (qb *performerQueryBuilder) FindBySceneID(ctx context.Context, sceneID int) ([]*models.Performer, error) { func (qb *PerformerStore) findBySubquery(ctx context.Context, sq *goqu.SelectDataset) ([]*models.Performer, error) {
query := selectAll("performers") + ` table := qb.table()
LEFT JOIN performers_scenes as scenes_join on scenes_join.performer_id = performers.id
WHERE scenes_join.scene_id = ? q := qb.selectDataset().Where(
` table.Col(idColumn).Eq(
args := []interface{}{sceneID} sq,
return qb.queryPerformers(ctx, query, args) ),
)
return qb.getMany(ctx, q)
} }
func (qb *performerQueryBuilder) FindByImageID(ctx context.Context, imageID int) ([]*models.Performer, error) { func (qb *PerformerStore) FindBySceneID(ctx context.Context, sceneID int) ([]*models.Performer, error) {
query := selectAll("performers") + ` sq := dialect.From(scenesPerformersJoinTable).Select(scenesPerformersJoinTable.Col(performerIDColumn)).Where(
LEFT JOIN performers_images as images_join on images_join.performer_id = performers.id scenesPerformersJoinTable.Col(sceneIDColumn).Eq(sceneID),
WHERE images_join.image_id = ? )
` ret, err := qb.findBySubquery(ctx, sq)
args := []interface{}{imageID}
return qb.queryPerformers(ctx, query, args)
}
func (qb *performerQueryBuilder) FindByGalleryID(ctx context.Context, galleryID int) ([]*models.Performer, error) { if err != nil {
query := selectAll("performers") + ` return nil, fmt.Errorf("getting performers for scene %d: %w", sceneID, err)
LEFT JOIN performers_galleries as galleries_join on galleries_join.performer_id = performers.id
WHERE galleries_join.gallery_id = ?
`
args := []interface{}{galleryID}
return qb.queryPerformers(ctx, query, args)
}
func (qb *performerQueryBuilder) FindNamesBySceneID(ctx context.Context, sceneID int) ([]*models.Performer, error) {
query := `
SELECT performers.name FROM performers
LEFT JOIN performers_scenes as scenes_join on scenes_join.performer_id = performers.id
WHERE scenes_join.scene_id = ?
`
args := []interface{}{sceneID}
return qb.queryPerformers(ctx, query, args)
}
func (qb *performerQueryBuilder) FindByNames(ctx context.Context, names []string, nocase bool) ([]*models.Performer, error) {
query := "SELECT * FROM performers WHERE name"
if nocase {
query += " COLLATE NOCASE"
} }
query += " IN " + getInBinding(len(names))
return ret, nil
}
func (qb *PerformerStore) FindByImageID(ctx context.Context, imageID int) ([]*models.Performer, error) {
sq := dialect.From(performersImagesJoinTable).Select(performersImagesJoinTable.Col(performerIDColumn)).Where(
performersImagesJoinTable.Col(imageIDColumn).Eq(imageID),
)
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for image %d: %w", imageID, err)
}
return ret, nil
}
func (qb *PerformerStore) FindByGalleryID(ctx context.Context, galleryID int) ([]*models.Performer, error) {
sq := dialect.From(performersGalleriesJoinTable).Select(performersGalleriesJoinTable.Col(performerIDColumn)).Where(
performersGalleriesJoinTable.Col(galleryIDColumn).Eq(galleryID),
)
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for gallery %d: %w", galleryID, err)
}
return ret, nil
}
func (qb *PerformerStore) FindByNames(ctx context.Context, names []string, nocase bool) ([]*models.Performer, error) {
clause := "name "
if nocase {
clause += "COLLATE NOCASE "
}
clause += "IN " + getInBinding(len(names))
var args []interface{} var args []interface{}
for _, name := range names { for _, name := range names {
args = append(args, name) args = append(args, name)
} }
return qb.queryPerformers(ctx, query, args)
}
func (qb *performerQueryBuilder) CountByTagID(ctx context.Context, tagID int) (int, error) { sq := qb.selectDataset().Prepared(true).Where(
args := []interface{}{tagID} goqu.L(clause, args...),
return qb.runCountQuery(ctx, qb.buildCountQuery(countPerformersForTagQuery), args) )
} ret, err := qb.getMany(ctx, sq)
func (qb *performerQueryBuilder) Count(ctx context.Context) (int, error) { if err != nil {
return qb.runCountQuery(ctx, qb.buildCountQuery("SELECT performers.id FROM performers"), nil) return nil, fmt.Errorf("getting performers by names: %w", err)
}
func (qb *performerQueryBuilder) All(ctx context.Context) ([]*models.Performer, error) {
return qb.queryPerformers(ctx, selectAll("performers")+qb.getPerformerSort(nil), nil)
}
func (qb *performerQueryBuilder) QueryForAutoTag(ctx context.Context, words []string) ([]*models.Performer, error) {
// TODO - Query needs to be changed to support queries of this type, and
// this method should be removed
query := selectAll(performerTable)
var whereClauses []string
var args []interface{}
for _, w := range words {
whereClauses = append(whereClauses, "name like ?")
args = append(args, w+"%")
// TODO - commented out until alias matching works both ways
// whereClauses = append(whereClauses, "aliases like ?")
// args = append(args, w+"%")
} }
whereOr := "(" + strings.Join(whereClauses, " OR ") + ")" return ret, nil
where := strings.Join([]string{
"ignore_auto_tag = 0",
whereOr,
}, " AND ")
return qb.queryPerformers(ctx, query+" WHERE "+where, args)
} }
func (qb *performerQueryBuilder) validateFilter(filter *models.PerformerFilterType) error { func (qb *PerformerStore) CountByTagID(ctx context.Context, tagID int) (int, error) {
joinTable := performersTagsJoinTable
q := dialect.Select(goqu.COUNT("*")).From(joinTable).Where(joinTable.Col(tagIDColumn).Eq(tagID))
return count(ctx, q)
}
func (qb *PerformerStore) Count(ctx context.Context) (int, error) {
q := dialect.Select(goqu.COUNT("*")).From(qb.table())
return count(ctx, q)
}
func (qb *PerformerStore) All(ctx context.Context) ([]*models.Performer, error) {
return qb.getMany(ctx, qb.selectDataset())
}
func (qb *PerformerStore) QueryForAutoTag(ctx context.Context, words []string) ([]*models.Performer, error) {
// TODO - Query needs to be changed to support queries of this type, and
// this method should be removed
table := qb.table()
sq := dialect.From(table).Select(table.Col(idColumn)).Where()
var whereClauses []exp.Expression
for _, w := range words {
whereClauses = append(whereClauses, table.Col("name").Like(w+"%"))
// TODO - commented out until alias matching works both ways
// whereClauses = append(whereClauses, table.Col("aliases").Like(w+"%")
}
sq = sq.Where(
goqu.Or(whereClauses...),
table.Col("ignore_auto_tag").Eq(0),
)
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for autotag: %w", err)
}
return ret, nil
}
func (qb *PerformerStore) validateFilter(filter *models.PerformerFilterType) error {
const and = "AND" const and = "AND"
const or = "OR" const or = "OR"
const not = "NOT" const not = "NOT"
@ -255,7 +448,7 @@ func (qb *performerQueryBuilder) validateFilter(filter *models.PerformerFilterTy
return nil return nil
} }
func (qb *performerQueryBuilder) makeFilter(ctx context.Context, filter *models.PerformerFilterType) *filterBuilder { func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.PerformerFilterType) *filterBuilder {
query := &filterBuilder{} query := &filterBuilder{}
if filter.And != nil { if filter.And != nil {
@ -322,7 +515,7 @@ func (qb *performerQueryBuilder) makeFilter(ctx context.Context, filter *models.
return query return query
} }
func (qb *performerQueryBuilder) Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error) { func (qb *PerformerStore) Query(ctx context.Context, performerFilter *models.PerformerFilterType, findFilter *models.FindFilterType) ([]*models.Performer, int, error) {
if performerFilter == nil { if performerFilter == nil {
performerFilter = &models.PerformerFilterType{} performerFilter = &models.PerformerFilterType{}
} }
@ -359,7 +552,7 @@ func (qb *performerQueryBuilder) Query(ctx context.Context, performerFilter *mod
return performers, countResult, nil return performers, countResult, nil
} }
func performerIsMissingCriterionHandler(qb *performerQueryBuilder, isMissing *string) criterionHandlerFunc { func performerIsMissingCriterionHandler(qb *PerformerStore, isMissing *string) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) { return func(ctx context.Context, f *filterBuilder) {
if isMissing != nil && *isMissing != "" { if isMissing != nil && *isMissing != "" {
switch *isMissing { switch *isMissing {
@ -400,7 +593,7 @@ func performerAgeFilterCriterionHandler(age *models.IntCriterionInput) criterion
} }
} }
func performerTagsCriterionHandler(qb *performerQueryBuilder, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc { func performerTagsCriterionHandler(qb *PerformerStore, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
h := joinedHierarchicalMultiCriterionHandlerBuilder{ h := joinedHierarchicalMultiCriterionHandlerBuilder{
tx: qb.tx, tx: qb.tx,
@ -417,7 +610,7 @@ func performerTagsCriterionHandler(qb *performerQueryBuilder, tags *models.Hiera
return h.handler(tags) return h.handler(tags)
} }
func performerTagCountCriterionHandler(qb *performerQueryBuilder, count *models.IntCriterionInput) criterionHandlerFunc { func performerTagCountCriterionHandler(qb *PerformerStore, count *models.IntCriterionInput) criterionHandlerFunc {
h := countCriterionHandlerBuilder{ h := countCriterionHandlerBuilder{
primaryTable: performerTable, primaryTable: performerTable,
joinTable: performersTagsTable, joinTable: performersTagsTable,
@ -427,7 +620,7 @@ func performerTagCountCriterionHandler(qb *performerQueryBuilder, count *models.
return h.handler(count) return h.handler(count)
} }
func performerSceneCountCriterionHandler(qb *performerQueryBuilder, count *models.IntCriterionInput) criterionHandlerFunc { func performerSceneCountCriterionHandler(qb *PerformerStore, count *models.IntCriterionInput) criterionHandlerFunc {
h := countCriterionHandlerBuilder{ h := countCriterionHandlerBuilder{
primaryTable: performerTable, primaryTable: performerTable,
joinTable: performersScenesTable, joinTable: performersScenesTable,
@ -437,7 +630,7 @@ func performerSceneCountCriterionHandler(qb *performerQueryBuilder, count *model
return h.handler(count) return h.handler(count)
} }
func performerImageCountCriterionHandler(qb *performerQueryBuilder, count *models.IntCriterionInput) criterionHandlerFunc { func performerImageCountCriterionHandler(qb *PerformerStore, count *models.IntCriterionInput) criterionHandlerFunc {
h := countCriterionHandlerBuilder{ h := countCriterionHandlerBuilder{
primaryTable: performerTable, primaryTable: performerTable,
joinTable: performersImagesTable, joinTable: performersImagesTable,
@ -447,7 +640,7 @@ func performerImageCountCriterionHandler(qb *performerQueryBuilder, count *model
return h.handler(count) return h.handler(count)
} }
func performerGalleryCountCriterionHandler(qb *performerQueryBuilder, count *models.IntCriterionInput) criterionHandlerFunc { func performerGalleryCountCriterionHandler(qb *PerformerStore, count *models.IntCriterionInput) criterionHandlerFunc {
h := countCriterionHandlerBuilder{ h := countCriterionHandlerBuilder{
primaryTable: performerTable, primaryTable: performerTable,
joinTable: performersGalleriesTable, joinTable: performersGalleriesTable,
@ -457,7 +650,7 @@ func performerGalleryCountCriterionHandler(qb *performerQueryBuilder, count *mod
return h.handler(count) return h.handler(count)
} }
func performerStudiosCriterionHandler(qb *performerQueryBuilder, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc { func performerStudiosCriterionHandler(qb *PerformerStore, studios *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) { return func(ctx context.Context, f *filterBuilder) {
if studios != nil { if studios != nil {
formatMaps := []utils.StrFormatMap{ formatMaps := []utils.StrFormatMap{
@ -534,7 +727,7 @@ func performerStudiosCriterionHandler(qb *performerQueryBuilder, studios *models
} }
} }
func (qb *performerQueryBuilder) getPerformerSort(findFilter *models.FindFilterType) string { func (qb *PerformerStore) getPerformerSort(findFilter *models.FindFilterType) string {
var sort string var sort string
var direction string var direction string
if findFilter == nil { if findFilter == nil {
@ -561,16 +754,7 @@ func (qb *performerQueryBuilder) getPerformerSort(findFilter *models.FindFilterT
return getSort(sort, direction, "performers") return getSort(sort, direction, "performers")
} }
func (qb *performerQueryBuilder) queryPerformers(ctx context.Context, query string, args []interface{}) ([]*models.Performer, error) { func (qb *PerformerStore) tagsRepository() *joinRepository {
var ret models.Performers
if err := qb.query(ctx, query, args, &ret); err != nil {
return nil, err
}
return []*models.Performer(ret), nil
}
func (qb *performerQueryBuilder) tagsRepository() *joinRepository {
return &joinRepository{ return &joinRepository{
repository: repository{ repository: repository{
tx: qb.tx, tx: qb.tx,
@ -581,16 +765,16 @@ func (qb *performerQueryBuilder) tagsRepository() *joinRepository {
} }
} }
func (qb *performerQueryBuilder) GetTagIDs(ctx context.Context, id int) ([]int, error) { func (qb *PerformerStore) GetTagIDs(ctx context.Context, id int) ([]int, error) {
return qb.tagsRepository().getIDs(ctx, id) return qb.tagsRepository().getIDs(ctx, id)
} }
func (qb *performerQueryBuilder) UpdateTags(ctx context.Context, id int, tagIDs []int) error { func (qb *PerformerStore) UpdateTags(ctx context.Context, id int, tagIDs []int) error {
// Delete the existing joins and then create new ones // Delete the existing joins and then create new ones
return qb.tagsRepository().replace(ctx, id, tagIDs) return qb.tagsRepository().replace(ctx, id, tagIDs)
} }
func (qb *performerQueryBuilder) imageRepository() *imageRepository { func (qb *PerformerStore) imageRepository() *imageRepository {
return &imageRepository{ return &imageRepository{
repository: repository{ repository: repository{
tx: qb.tx, tx: qb.tx,
@ -601,19 +785,19 @@ func (qb *performerQueryBuilder) imageRepository() *imageRepository {
} }
} }
func (qb *performerQueryBuilder) GetImage(ctx context.Context, performerID int) ([]byte, error) { func (qb *PerformerStore) GetImage(ctx context.Context, performerID int) ([]byte, error) {
return qb.imageRepository().get(ctx, performerID) return qb.imageRepository().get(ctx, performerID)
} }
func (qb *performerQueryBuilder) UpdateImage(ctx context.Context, performerID int, image []byte) error { func (qb *PerformerStore) UpdateImage(ctx context.Context, performerID int, image []byte) error {
return qb.imageRepository().replace(ctx, performerID, image) return qb.imageRepository().replace(ctx, performerID, image)
} }
func (qb *performerQueryBuilder) DestroyImage(ctx context.Context, performerID int) error { func (qb *PerformerStore) DestroyImage(ctx context.Context, performerID int) error {
return qb.imageRepository().destroy(ctx, []int{performerID}) return qb.imageRepository().destroy(ctx, []int{performerID})
} }
func (qb *performerQueryBuilder) stashIDRepository() *stashIDRepository { func (qb *PerformerStore) stashIDRepository() *stashIDRepository {
return &stashIDRepository{ return &stashIDRepository{
repository{ repository{
tx: qb.tx, tx: qb.tx,
@ -623,40 +807,51 @@ func (qb *performerQueryBuilder) stashIDRepository() *stashIDRepository {
} }
} }
func (qb *performerQueryBuilder) GetStashIDs(ctx context.Context, performerID int) ([]models.StashID, error) { func (qb *PerformerStore) GetStashIDs(ctx context.Context, performerID int) ([]models.StashID, error) {
return qb.stashIDRepository().get(ctx, performerID) return qb.stashIDRepository().get(ctx, performerID)
} }
func (qb *performerQueryBuilder) UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error { func (qb *PerformerStore) UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error {
return qb.stashIDRepository().replace(ctx, performerID, stashIDs) return qb.stashIDRepository().replace(ctx, performerID, stashIDs)
} }
func (qb *performerQueryBuilder) FindByStashID(ctx context.Context, stashID models.StashID) ([]*models.Performer, error) { func (qb *PerformerStore) FindByStashID(ctx context.Context, stashID models.StashID) ([]*models.Performer, error) {
query := selectAll("performers") + ` sq := dialect.From(performersStashIDsJoinTable).Select(performersStashIDsJoinTable.Col(performerIDColumn)).Where(
LEFT JOIN performer_stash_ids on performer_stash_ids.performer_id = performers.id performersStashIDsJoinTable.Col("stash_id").Eq(stashID.StashID),
WHERE performer_stash_ids.stash_id = ? performersStashIDsJoinTable.Col("endpoint").Eq(stashID.Endpoint),
AND performer_stash_ids.endpoint = ? )
` ret, err := qb.findBySubquery(ctx, sq)
args := []interface{}{stashID.StashID, stashID.Endpoint}
return qb.queryPerformers(ctx, query, args)
}
func (qb *performerQueryBuilder) FindByStashIDStatus(ctx context.Context, hasStashID bool, stashboxEndpoint string) ([]*models.Performer, error) { if err != nil {
query := selectAll("performers") + ` return nil, fmt.Errorf("getting performers for stash ID %s: %w", stashID.StashID, err)
LEFT JOIN performer_stash_ids on performer_stash_ids.performer_id = performers.id
`
if hasStashID {
query += `
WHERE performer_stash_ids.stash_id IS NOT NULL
AND performer_stash_ids.endpoint = ?
`
} else {
query += `
WHERE performer_stash_ids.stash_id IS NULL
`
} }
args := []interface{}{stashboxEndpoint} return ret, nil
return qb.queryPerformers(ctx, query, args) }
func (qb *PerformerStore) FindByStashIDStatus(ctx context.Context, hasStashID bool, stashboxEndpoint string) ([]*models.Performer, error) {
table := qb.table()
sq := dialect.From(table).LeftJoin(
performersStashIDsJoinTable,
goqu.On(table.Col(idColumn).Eq(performersStashIDsJoinTable.Col(performerIDColumn))),
).Select(table.Col(idColumn))
if hasStashID {
sq = sq.Where(
performersStashIDsJoinTable.Col("stash_id").IsNotNull(),
performersStashIDsJoinTable.Col("endpoint").Eq(stashboxEndpoint),
)
} else {
sq = sq.Where(
performersStashIDsJoinTable.Col("stash_id").IsNull(),
)
}
ret, err := qb.findBySubquery(ctx, sq)
if err != nil {
return nil, fmt.Errorf("getting performers for stash-box endpoint %s: %w", stashboxEndpoint, err)
}
return ret, nil
} }

File diff suppressed because it is too large Load diff

View file

@ -317,12 +317,6 @@ func (qb *SceneStore) Update(ctx context.Context, updatedObject *models.Scene) e
} }
func (qb *SceneStore) Destroy(ctx context.Context, id int) error { func (qb *SceneStore) Destroy(ctx context.Context, id int) error {
// delete all related table rows
// TODO - this should be handled by a delete cascade
if err := qb.performersRepository().destroy(ctx, []int{id}); err != nil {
return err
}
// scene markers should be handled prior to calling destroy // scene markers should be handled prior to calling destroy
// galleries should be handled prior to calling destroy // galleries should be handled prior to calling destroy

View file

@ -552,7 +552,7 @@ func populateDB() error {
return fmt.Errorf("error creating movies: %s", err.Error()) return fmt.Errorf("error creating movies: %s", err.Error())
} }
if err := createPerformers(ctx, sqlite.PerformerReaderWriter, performersNameCase, performersNameNoCase); err != nil { if err := createPerformers(ctx, performersNameCase, performersNameNoCase); err != nil {
return fmt.Errorf("error creating performers: %s", err.Error()) return fmt.Errorf("error creating performers: %s", err.Error())
} }
@ -584,7 +584,7 @@ func populateDB() error {
return fmt.Errorf("error creating saved filters: %s", err.Error()) return fmt.Errorf("error creating saved filters: %s", err.Error())
} }
if err := linkPerformerTags(ctx, sqlite.PerformerReaderWriter); err != nil { if err := linkPerformerTags(ctx); err != nil {
return fmt.Errorf("error linking performer tags: %s", err.Error()) return fmt.Errorf("error linking performer tags: %s", err.Error())
} }
@ -1226,8 +1226,10 @@ func getPerformerStringValue(index int, field string) string {
return getPrefixedStringValue("performer", index, field) return getPrefixedStringValue("performer", index, field)
} }
func getPerformerNullStringValue(index int, field string) sql.NullString { func getPerformerNullStringValue(index int, field string) string {
return getPrefixedNullStringValue("performer", index, field) ret := getPrefixedNullStringValue("performer", index, field)
return ret.String
} }
func getPerformerBoolValue(index int) bool { func getPerformerBoolValue(index int) bool {
@ -1235,24 +1237,29 @@ func getPerformerBoolValue(index int) bool {
return index == 1 return index == 1
} }
func getPerformerBirthdate(index int) string { func getPerformerBirthdate(index int) *models.Date {
const minAge = 18 const minAge = 18
birthdate := time.Now() birthdate := time.Now()
birthdate = birthdate.AddDate(-minAge-index, -1, -1) birthdate = birthdate.AddDate(-minAge-index, -1, -1)
return birthdate.Format("2006-01-02")
ret := models.Date{
Time: birthdate,
}
return &ret
} }
func getPerformerDeathDate(index int) models.SQLiteDate { func getPerformerDeathDate(index int) *models.Date {
if index != 5 { if index != 5 {
return models.SQLiteDate{} return nil
} }
deathDate := time.Now() deathDate := time.Now()
deathDate = deathDate.AddDate(-index+1, -1, -1) deathDate = deathDate.AddDate(-index+1, -1, -1)
return models.SQLiteDate{
String: deathDate.Format("2006-01-02"), ret := models.Date{
Valid: true, Time: deathDate,
} }
return &ret
} }
func getPerformerCareerLength(index int) *string { func getPerformerCareerLength(index int) *string {
@ -1268,8 +1275,17 @@ func getIgnoreAutoTag(index int) bool {
return index%5 == 0 return index%5 == 0
} }
func performerStashID(i int) models.StashID {
return models.StashID{
StashID: getPerformerStringValue(i, "stashid"),
Endpoint: getPerformerStringValue(i, "endpoint"),
}
}
// createPerformers creates n performers with plain Name and o performers with camel cased NaMe included // createPerformers creates n performers with plain Name and o performers with camel cased NaMe included
func createPerformers(ctx context.Context, pqb models.PerformerReaderWriter, n int, o int) error { func createPerformers(ctx context.Context, n int, o int) error {
pqb := db.Performer
const namePlain = "Name" const namePlain = "Name"
const nameNoCase = "NaMe" const nameNoCase = "NaMe"
@ -1285,34 +1301,39 @@ func createPerformers(ctx context.Context, pqb models.PerformerReaderWriter, n i
// performers [ i ] and [ n + o - i - 1 ] should have similar names with only the Name!=NaMe part different // performers [ i ] and [ n + o - i - 1 ] should have similar names with only the Name!=NaMe part different
performer := models.Performer{ performer := models.Performer{
Name: sql.NullString{String: getPerformerStringValue(index, name), Valid: true}, Name: getPerformerStringValue(index, name),
Checksum: getPerformerStringValue(i, checksumField), Checksum: getPerformerStringValue(i, checksumField),
URL: getPerformerNullStringValue(i, urlField), URL: getPerformerNullStringValue(i, urlField),
Favorite: sql.NullBool{Bool: getPerformerBoolValue(i), Valid: true}, Favorite: getPerformerBoolValue(i),
Birthdate: models.SQLiteDate{ Birthdate: getPerformerBirthdate(i),
String: getPerformerBirthdate(i),
Valid: true,
},
DeathDate: getPerformerDeathDate(i), DeathDate: getPerformerDeathDate(i),
Details: sql.NullString{String: getPerformerStringValue(i, "Details"), Valid: true}, Details: getPerformerStringValue(i, "Details"),
Ethnicity: sql.NullString{String: getPerformerStringValue(i, "Ethnicity"), Valid: true}, Ethnicity: getPerformerStringValue(i, "Ethnicity"),
Rating: getRating(i), Rating: getIntPtr(getRating(i)),
IgnoreAutoTag: getIgnoreAutoTag(i), IgnoreAutoTag: getIgnoreAutoTag(i),
} }
careerLength := getPerformerCareerLength(i) careerLength := getPerformerCareerLength(i)
if careerLength != nil { if careerLength != nil {
performer.CareerLength = models.NullString(*careerLength) performer.CareerLength = *careerLength
} }
created, err := pqb.Create(ctx, performer) err := pqb.Create(ctx, &performer)
if err != nil { if err != nil {
return fmt.Errorf("Error creating performer %v+: %s", performer, err.Error()) return fmt.Errorf("Error creating performer %v+: %s", performer, err.Error())
} }
performerIDs = append(performerIDs, created.ID) if (index+1)%5 != 0 {
performerNames = append(performerNames, created.Name.String) if err := pqb.UpdateStashIDs(ctx, performer.ID, []models.StashID{
performerStashID(i),
}); err != nil {
return fmt.Errorf("setting performer stash ids: %w", err)
}
}
performerIDs = append(performerIDs, performer.ID)
performerNames = append(performerNames, performer.Name)
} }
return nil return nil
@ -1581,7 +1602,8 @@ func doLinks(links [][2]int, fn func(idx1, idx2 int) error) error {
return nil return nil
} }
func linkPerformerTags(ctx context.Context, qb models.PerformerReaderWriter) error { func linkPerformerTags(ctx context.Context) error {
qb := db.Performer
return doLinks(performerTagLinks, func(performerIndex, tagIndex int) error { return doLinks(performerTagLinks, func(performerIndex, tagIndex int) error {
performerID := performerIDs[performerIndex] performerID := performerIDs[performerIndex]
tagID := tagIDs[tagIndex] tagID := tagIDs[tagIndex]

View file

@ -24,6 +24,9 @@ var (
scenesPerformersJoinTable = goqu.T(performersScenesTable) scenesPerformersJoinTable = goqu.T(performersScenesTable)
scenesStashIDsJoinTable = goqu.T("scene_stash_ids") scenesStashIDsJoinTable = goqu.T("scene_stash_ids")
scenesMoviesJoinTable = goqu.T(moviesScenesTable) scenesMoviesJoinTable = goqu.T(moviesScenesTable)
performersTagsJoinTable = goqu.T(performersTagsTable)
performersStashIDsJoinTable = goqu.T("performer_stash_ids")
) )
var ( var (

View file

@ -1011,7 +1011,7 @@ func TestTagMerge(t *testing.T) {
assert.Contains(g.TagIDs.List(), destID) assert.Contains(g.TagIDs.List(), destID)
// ensure performer points to new tag // ensure performer points to new tag
performerTagIDs, err := sqlite.PerformerReaderWriter.GetTagIDs(ctx, performerIDs[performerIdxWithTwoTags]) performerTagIDs, err := db.Performer.GetTagIDs(ctx, performerIDs[performerIdxWithTwoTags])
if err != nil { if err != nil {
return err return err
} }

View file

@ -108,7 +108,7 @@ func (db *Database) TxnRepository() models.Repository {
Gallery: db.Gallery, Gallery: db.Gallery,
Image: db.Image, Image: db.Image,
Movie: MovieReaderWriter, Movie: MovieReaderWriter,
Performer: PerformerReaderWriter, Performer: db.Performer,
Scene: db.Scene, Scene: db.Scene,
SceneMarker: SceneMarkerReaderWriter, SceneMarker: SceneMarkerReaderWriter,
ScrapedItem: ScrapedItemReaderWriter, ScrapedItem: ScrapedItemReaderWriter,