stash/internal/api/resolver_mutation_performer.go
WithoutPants 270bc317cb
Performer refactor (#3057)
* Separate performer model from sqlite model
* Use GenderEnum for gender
2022-10-31 14:58:01 +11:00

450 lines
13 KiB
Go

package api
import (
"context"
"fmt"
"strconv"
"time"
"github.com/stashapp/stash/pkg/hash/md5"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/utils"
)
func (r *mutationResolver) getPerformer(ctx context.Context, id int) (ret *models.Performer, err error) {
if err := r.withTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Performer.Find(ctx, id)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func stashIDPtrSliceToSlice(v []*models.StashID) []models.StashID {
ret := make([]models.StashID, len(v))
for i, vv := range v {
c := vv
ret[i] = *c
}
return ret
}
func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerCreateInput) (*models.Performer, error) {
// generate checksum from performer name rather than image
checksum := md5.FromString(input.Name)
var imageData []byte
var err error
if input.Image != nil {
imageData, err = utils.ProcessImageInput(ctx, *input.Image)
}
if err != nil {
return nil, err
}
// Populate a new performer from the input
currentTime := time.Now()
newPerformer := models.Performer{
Name: input.Name,
Checksum: checksum,
CreatedAt: currentTime,
UpdatedAt: currentTime,
}
if input.URL != nil {
newPerformer.URL = *input.URL
}
if input.Gender != nil {
newPerformer.Gender = *input.Gender
}
if input.Birthdate != nil {
d := models.NewDate(*input.Birthdate)
newPerformer.Birthdate = &d
}
if input.Ethnicity != nil {
newPerformer.Ethnicity = *input.Ethnicity
}
if input.Country != nil {
newPerformer.Country = *input.Country
}
if input.EyeColor != nil {
newPerformer.EyeColor = *input.EyeColor
}
if input.Height != nil {
newPerformer.Height = *input.Height
}
if input.Measurements != nil {
newPerformer.Measurements = *input.Measurements
}
if input.FakeTits != nil {
newPerformer.FakeTits = *input.FakeTits
}
if input.CareerLength != nil {
newPerformer.CareerLength = *input.CareerLength
}
if input.Tattoos != nil {
newPerformer.Tattoos = *input.Tattoos
}
if input.Piercings != nil {
newPerformer.Piercings = *input.Piercings
}
if input.Aliases != nil {
newPerformer.Aliases = *input.Aliases
}
if input.Twitter != nil {
newPerformer.Twitter = *input.Twitter
}
if input.Instagram != nil {
newPerformer.Instagram = *input.Instagram
}
if input.Favorite != nil {
newPerformer.Favorite = *input.Favorite
}
if input.Rating != nil {
newPerformer.Rating = input.Rating
}
if input.Details != nil {
newPerformer.Details = *input.Details
}
if input.DeathDate != nil {
d := models.NewDate(*input.DeathDate)
newPerformer.DeathDate = &d
}
if input.HairColor != nil {
newPerformer.HairColor = *input.HairColor
}
if input.Weight != nil {
newPerformer.Weight = input.Weight
}
if input.IgnoreAutoTag != nil {
newPerformer.IgnoreAutoTag = *input.IgnoreAutoTag
}
if err := performer.ValidateDeathDate(nil, input.Birthdate, input.DeathDate); err != nil {
if err != nil {
return nil, err
}
}
// Start the transaction and save the performer
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Performer
err = qb.Create(ctx, &newPerformer)
if err != nil {
return err
}
if len(input.TagIds) > 0 {
if err := r.updatePerformerTags(ctx, newPerformer.ID, input.TagIds); err != nil {
return err
}
}
// update image table
if len(imageData) > 0 {
if err := qb.UpdateImage(ctx, newPerformer.ID, imageData); err != nil {
return err
}
}
// Save the stash_ids
if input.StashIds != nil {
stashIDJoins := stashIDPtrSliceToSlice(input.StashIds)
if err := qb.UpdateStashIDs(ctx, newPerformer.ID, stashIDJoins); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
r.hookExecutor.ExecutePostHooks(ctx, newPerformer.ID, plugin.PerformerCreatePost, input, nil)
return r.getPerformer(ctx, newPerformer.ID)
}
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerUpdateInput) (*models.Performer, error) {
// Populate performer from the input
performerID, _ := strconv.Atoi(input.ID)
updatedPerformer := models.NewPerformerPartial()
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
var imageData []byte
var err error
imageIncluded := translator.hasField("image")
if input.Image != nil {
imageData, err = utils.ProcessImageInput(ctx, *input.Image)
if err != nil {
return nil, err
}
}
if input.Name != nil {
// generate checksum from performer name rather than image
checksum := md5.FromString(*input.Name)
updatedPerformer.Name = models.NewOptionalString(*input.Name)
updatedPerformer.Checksum = models.NewOptionalString(checksum)
}
updatedPerformer.URL = translator.optionalString(input.URL, "url")
if translator.hasField("gender") {
if input.Gender != nil {
updatedPerformer.Gender = models.NewOptionalString(input.Gender.String())
} else {
updatedPerformer.Gender = models.NewOptionalStringPtr(nil)
}
}
updatedPerformer.Birthdate = translator.optionalDate(input.Birthdate, "birthdate")
updatedPerformer.Country = translator.optionalString(input.Country, "country")
updatedPerformer.EyeColor = translator.optionalString(input.EyeColor, "eye_color")
updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements")
updatedPerformer.Height = translator.optionalString(input.Height, "height")
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
updatedPerformer.Aliases = translator.optionalString(input.Aliases, "aliases")
updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter")
updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram")
updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
updatedPerformer.Rating = translator.optionalInt(input.Rating, "rating")
updatedPerformer.Details = translator.optionalString(input.Details, "details")
updatedPerformer.DeathDate = translator.optionalDate(input.DeathDate, "death_date")
updatedPerformer.HairColor = translator.optionalString(input.HairColor, "hair_color")
updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight")
updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
// Start the transaction and save the p
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Performer
// need to get existing performer
existing, err := qb.Find(ctx, performerID)
if err != nil {
return err
}
if existing == nil {
return fmt.Errorf("performer with id %d not found", performerID)
}
if err := performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate); err != nil {
if err != nil {
return err
}
}
_, err = qb.UpdatePartial(ctx, performerID, updatedPerformer)
if err != nil {
return err
}
// Save the tags
if translator.hasField("tag_ids") {
if err := r.updatePerformerTags(ctx, performerID, input.TagIds); err != nil {
return err
}
}
// update image table
if len(imageData) > 0 {
if err := qb.UpdateImage(ctx, performerID, imageData); err != nil {
return err
}
} else if imageIncluded {
// must be unsetting
if err := qb.DestroyImage(ctx, performerID); err != nil {
return err
}
}
// Save the stash_ids
if translator.hasField("stash_ids") {
stashIDJoins := stashIDPtrSliceToSlice(input.StashIds)
if err := qb.UpdateStashIDs(ctx, performerID, stashIDJoins); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
r.hookExecutor.ExecutePostHooks(ctx, performerID, plugin.PerformerUpdatePost, input, translator.getFields())
return r.getPerformer(ctx, performerID)
}
func (r *mutationResolver) updatePerformerTags(ctx context.Context, performerID int, tagsIDs []string) error {
ids, err := stringslice.StringSliceToIntSlice(tagsIDs)
if err != nil {
return err
}
return r.repository.Performer.UpdateTags(ctx, performerID, ids)
}
func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPerformerUpdateInput) ([]*models.Performer, error) {
performerIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil {
return nil, err
}
// Populate performer from the input
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
updatedPerformer := models.NewPerformerPartial()
updatedPerformer.URL = translator.optionalString(input.URL, "url")
updatedPerformer.Birthdate = translator.optionalDate(input.Birthdate, "birthdate")
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
updatedPerformer.Country = translator.optionalString(input.Country, "country")
updatedPerformer.EyeColor = translator.optionalString(input.EyeColor, "eye_color")
updatedPerformer.Height = translator.optionalString(input.Height, "height")
updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements")
updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
updatedPerformer.Aliases = translator.optionalString(input.Aliases, "aliases")
updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter")
updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram")
updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
updatedPerformer.Rating = translator.optionalInt(input.Rating, "rating")
updatedPerformer.Details = translator.optionalString(input.Details, "details")
updatedPerformer.DeathDate = translator.optionalDate(input.DeathDate, "death_date")
updatedPerformer.HairColor = translator.optionalString(input.HairColor, "hair_color")
updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight")
updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
if translator.hasField("gender") {
if input.Gender != nil {
updatedPerformer.Gender = models.NewOptionalString(input.Gender.String())
} else {
updatedPerformer.Gender = models.NewOptionalStringPtr(nil)
}
}
ret := []*models.Performer{}
// Start the transaction and save the scene marker
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Performer
for _, performerID := range performerIDs {
updatedPerformer.ID = performerID
// need to get existing performer
existing, err := qb.Find(ctx, performerID)
if err != nil {
return err
}
if existing == nil {
return fmt.Errorf("performer with id %d not found", performerID)
}
if err := performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate); err != nil {
return err
}
performer, err := qb.UpdatePartial(ctx, performerID, updatedPerformer)
if err != nil {
return err
}
ret = append(ret, performer)
// Save the tags
if translator.hasField("tag_ids") {
tagIDs, err := adjustTagIDs(ctx, qb, performerID, *input.TagIds)
if err != nil {
return err
}
if err := qb.UpdateTags(ctx, performerID, tagIDs); err != nil {
return err
}
}
}
return nil
}); err != nil {
return nil, err
}
// execute post hooks outside of txn
var newRet []*models.Performer
for _, performer := range ret {
r.hookExecutor.ExecutePostHooks(ctx, performer.ID, plugin.ImageUpdatePost, input, translator.getFields())
performer, err = r.getPerformer(ctx, performer.ID)
if err != nil {
return nil, err
}
newRet = append(newRet, performer)
}
return newRet, nil
}
func (r *mutationResolver) PerformerDestroy(ctx context.Context, input PerformerDestroyInput) (bool, error) {
id, err := strconv.Atoi(input.ID)
if err != nil {
return false, err
}
if err := r.withTxn(ctx, func(ctx context.Context) error {
return r.repository.Performer.Destroy(ctx, id)
}); err != nil {
return false, err
}
r.hookExecutor.ExecutePostHooks(ctx, id, plugin.PerformerDestroyPost, input, nil)
return true, nil
}
func (r *mutationResolver) PerformersDestroy(ctx context.Context, performerIDs []string) (bool, error) {
ids, err := stringslice.StringSliceToIntSlice(performerIDs)
if err != nil {
return false, err
}
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.Performer
for _, id := range ids {
if err := qb.Destroy(ctx, id); err != nil {
return err
}
}
return nil
}); err != nil {
return false, err
}
for _, id := range ids {
r.hookExecutor.ExecutePostHooks(ctx, id, plugin.PerformerDestroyPost, performerIDs, nil)
}
return true, nil
}