From 24e4719abc37a2d081d7755463f274c72c21c492 Mon Sep 17 00:00:00 2001 From: DingDongSoLong4 <99329275+DingDongSoLong4@users.noreply.github.com> Date: Mon, 11 Sep 2023 04:24:15 +0200 Subject: [PATCH] Model refactor, part 2 (#4092) * Move conversions into changesetTranslator * Improve mutation error messages * Use models.New and models.NewPartial everywhere * Replace getStashIDsFor functions * Remove ImageCreateInput * Remove unused parameters * Refactor matching functions --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com> --- internal/api/changeset_translator.go | 172 +++++++++- internal/api/resolver_model_scene.go | 10 - internal/api/resolver_mutation_file.go | 6 +- internal/api/resolver_mutation_gallery.go | 161 +++++----- internal/api/resolver_mutation_image.go | 98 +++--- internal/api/resolver_mutation_job.go | 7 +- internal/api/resolver_mutation_movie.go | 57 ++-- internal/api/resolver_mutation_performer.go | 245 +++++++-------- .../api/resolver_mutation_saved_filter.go | 5 +- internal/api/resolver_mutation_scene.go | 294 +++++++----------- internal/api/resolver_mutation_stash_box.go | 4 +- internal/api/resolver_mutation_studio.go | 163 ++++------ internal/api/resolver_mutation_tag.go | 37 +-- internal/api/types.go | 34 +- internal/autotag/gallery_test.go | 59 +++- internal/autotag/image_test.go | 59 +++- internal/autotag/integration_test.go | 5 +- internal/autotag/performer_test.go | 54 ++-- internal/autotag/scene_test.go | 59 +++- internal/autotag/studio.go | 30 +- internal/autotag/studio_test.go | 39 ++- internal/autotag/tag_test.go | 52 ++-- internal/identify/scene.go | 10 +- internal/identify/studio.go | 19 +- internal/manager/task_clean.go | 21 +- internal/manager/task_generate_screenshot.go | 4 +- internal/manager/task_stash_box_tag.go | 54 ++-- pkg/gallery/import.go | 17 +- pkg/gallery/scan.go | 20 +- pkg/gallery/update.go | 30 +- pkg/image/import.go | 22 +- pkg/image/scan.go | 82 +++-- pkg/image/update.go | 25 +- pkg/models/mocks/ImageReaderWriter.go | 10 +- pkg/models/model_gallery.go | 80 +++-- pkg/models/model_gallery_chapter.go | 12 +- pkg/models/model_image.go | 73 ++--- pkg/models/model_joins.go | 24 +- pkg/models/model_movie.go | 33 +- pkg/models/model_performer.go | 81 +++-- pkg/models/model_saved_filter.go | 10 - pkg/models/model_scene.go | 125 +++----- pkg/models/model_scene_marker.go | 12 +- pkg/models/model_scraped_item.go | 93 +++--- pkg/models/model_studio.go | 57 ++-- pkg/models/model_tag.go | 47 +-- pkg/models/performer.go | 73 +++++ pkg/models/repository_image.go | 2 +- pkg/models/scene.go | 58 ++++ pkg/models/studio.go | 29 ++ pkg/movie/import.go | 7 +- pkg/performer/import.go | 7 +- pkg/scene/import.go | 22 +- pkg/scene/scan.go | 14 +- pkg/scene/update.go | 36 +-- pkg/sqlite/image.go | 10 +- pkg/sqlite/image_test.go | 5 +- pkg/sqlite/setup_test.go | 5 +- pkg/sqlite/tag.go | 4 +- pkg/studio/import.go | 7 +- pkg/tag/import.go | 5 +- 61 files changed, 1501 insertions(+), 1394 deletions(-) diff --git a/internal/api/changeset_translator.go b/internal/api/changeset_translator.go index e40b8fe0e48..6d2590a3da4 100644 --- a/internal/api/changeset_translator.go +++ b/internal/api/changeset_translator.go @@ -7,7 +7,9 @@ import ( "strings" "github.com/99designs/gqlgen/graphql" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) const updateInputField = "input" @@ -91,7 +93,7 @@ func (t changesetTranslator) getFields() []string { return ret } -func (t changesetTranslator) string(value *string, field string) string { +func (t changesetTranslator) string(value *string) string { if value == nil { return "" } @@ -127,7 +129,7 @@ func (t changesetTranslator) optionalDate(value *string, field string) (models.O return models.NewOptionalDate(date), nil } -func (t changesetTranslator) datePtr(value *string, field string) (*models.Date, error) { +func (t changesetTranslator) datePtr(value *string) (*models.Date, error) { if value == nil || *value == "" { return nil, nil } @@ -139,7 +141,7 @@ func (t changesetTranslator) datePtr(value *string, field string) (*models.Date, return &date, nil } -func (t changesetTranslator) intPtrFromString(value *string, field string) (*int, error) { +func (t changesetTranslator) intPtrFromString(value *string) (*int, error) { if value == nil || *value == "" { return nil, nil } @@ -151,35 +153,35 @@ func (t changesetTranslator) intPtrFromString(value *string, field string) (*int return &vv, nil } -func (t changesetTranslator) ratingConversionInt(legacyValue *int, rating100Value *int) *int { +func (t changesetTranslator) ratingConversion(legacyValue *int, rating100Value *int) *int { const ( legacyField = "rating" rating100Field = "rating100" ) legacyRating := t.optionalInt(legacyValue, legacyField) - if legacyRating.Set && !(legacyRating.Null) { - ret := int(models.Rating5To100(int(legacyRating.Value))) + if legacyRating.Set && !legacyRating.Null { + ret := models.Rating5To100(legacyRating.Value) return &ret } o := t.optionalInt(rating100Value, rating100Field) - if o.Set && !(o.Null) { + if o.Set && !o.Null { return &o.Value } return nil } -func (t changesetTranslator) ratingConversionOptional(legacyValue *int, rating100Value *int) models.OptionalInt { +func (t changesetTranslator) optionalRatingConversion(legacyValue *int, rating100Value *int) models.OptionalInt { const ( legacyField = "rating" rating100Field = "rating100" ) legacyRating := t.optionalInt(legacyValue, legacyField) - if legacyRating.Set && !(legacyRating.Null) { - legacyRating.Value = int(models.Rating5To100(int(legacyRating.Value))) + if legacyRating.Set && !legacyRating.Null { + legacyRating.Value = models.Rating5To100(legacyRating.Value) return legacyRating } return t.optionalInt(rating100Value, rating100Field) @@ -212,7 +214,7 @@ func (t changesetTranslator) optionalIntFromString(value *string, field string) return models.NewOptionalInt(vv), nil } -func (t changesetTranslator) bool(value *bool, field string) bool { +func (t changesetTranslator) bool(value *bool) bool { if value == nil { return false } @@ -235,3 +237,151 @@ func (t changesetTranslator) optionalFloat64(value *float64, field string) model return models.NewOptionalFloat64Ptr(value) } + +func (t changesetTranslator) fileIDPtrFromString(value *string) (*models.FileID, error) { + if value == nil || *value == "" { + return nil, nil + } + + vv, err := strconv.Atoi(*value) + if err != nil { + return nil, fmt.Errorf("converting %v to int: %w", *value, err) + } + + id := models.FileID(vv) + return &id, nil +} + +func (t changesetTranslator) fileIDSliceFromStringSlice(value []string) ([]models.FileID, error) { + ints, err := stringslice.StringSliceToIntSlice(value) + if err != nil { + return nil, err + } + + fileIDs := make([]models.FileID, len(ints)) + for i, v := range ints { + fileIDs[i] = models.FileID(v) + } + + return fileIDs, nil +} + +func (t changesetTranslator) relatedIds(value []string) (models.RelatedIDs, error) { + ids, err := stringslice.StringSliceToIntSlice(value) + if err != nil { + return models.RelatedIDs{}, err + } + + return models.NewRelatedIDs(ids), nil +} + +func (t changesetTranslator) updateIds(value []string, field string) (*models.UpdateIDs, error) { + if !t.hasField(field) { + return nil, nil + } + + ids, err := stringslice.StringSliceToIntSlice(value) + if err != nil { + return nil, err + } + + return &models.UpdateIDs{ + IDs: ids, + Mode: models.RelationshipUpdateModeSet, + }, nil +} + +func (t changesetTranslator) updateIdsBulk(value *BulkUpdateIds, field string) (*models.UpdateIDs, error) { + if !t.hasField(field) || value == nil { + return nil, nil + } + + ids, err := stringslice.StringSliceToIntSlice(value.Ids) + if err != nil { + return nil, fmt.Errorf("converting ids [%v]: %w", value.Ids, err) + } + + return &models.UpdateIDs{ + IDs: ids, + Mode: value.Mode, + }, nil +} + +func (t changesetTranslator) updateStrings(value []string, field string) *models.UpdateStrings { + if !t.hasField(field) { + return nil + } + + return &models.UpdateStrings{ + Values: value, + Mode: models.RelationshipUpdateModeSet, + } +} + +func (t changesetTranslator) updateStringsBulk(value *BulkUpdateStrings, field string) *models.UpdateStrings { + if !t.hasField(field) || value == nil { + return nil + } + + return &models.UpdateStrings{ + Values: value.Values, + Mode: value.Mode, + } +} + +func (t changesetTranslator) updateStashIDs(value []models.StashID, field string) *models.UpdateStashIDs { + if !t.hasField(field) { + return nil + } + + return &models.UpdateStashIDs{ + StashIDs: value, + Mode: models.RelationshipUpdateModeSet, + } +} + +func (t changesetTranslator) relatedMovies(value []models.SceneMovieInput) (models.RelatedMovies, error) { + moviesScenes, err := models.MoviesScenesFromInput(value) + if err != nil { + return models.RelatedMovies{}, err + } + + return models.NewRelatedMovies(moviesScenes), nil +} + +func (t changesetTranslator) updateMovieIDs(value []models.SceneMovieInput, field string) (*models.UpdateMovieIDs, error) { + if !t.hasField(field) { + return nil, nil + } + + moviesScenes, err := models.MoviesScenesFromInput(value) + if err != nil { + return nil, err + } + + return &models.UpdateMovieIDs{ + Movies: moviesScenes, + Mode: models.RelationshipUpdateModeSet, + }, nil +} + +func (t changesetTranslator) updateMovieIDsBulk(value *BulkUpdateIds, field string) (*models.UpdateMovieIDs, error) { + if !t.hasField(field) || value == nil { + return nil, nil + } + + ids, err := stringslice.StringSliceToIntSlice(value.Ids) + if err != nil { + return nil, fmt.Errorf("converting ids [%v]: %w", value.Ids, err) + } + + movies := make([]models.MoviesScenes, len(value.Ids)) + for _, id := range ids { + movies = append(movies, models.MoviesScenes{MovieID: id}) + } + + return &models.UpdateMovieIDs{ + Movies: movies, + Mode: value.Mode, + }, nil +} diff --git a/internal/api/resolver_model_scene.go b/internal/api/resolver_model_scene.go index 27ccaf33b85..2593555472f 100644 --- a/internal/api/resolver_model_scene.go +++ b/internal/api/resolver_model_scene.go @@ -275,16 +275,6 @@ func (r *sceneResolver) Performers(ctx context.Context, obj *models.Scene) (ret return ret, firstError(errs) } -func stashIDsSliceToPtrSlice(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 *sceneResolver) StashIds(ctx context.Context, obj *models.Scene) (ret []*models.StashID, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { return obj.LoadStashIDs(ctx, r.repository.Scene) diff --git a/internal/api/resolver_mutation_file.go b/internal/api/resolver_mutation_file.go index 2fcf66fcf19..e8fecef80a8 100644 --- a/internal/api/resolver_mutation_file.go +++ b/internal/api/resolver_mutation_file.go @@ -26,7 +26,7 @@ func (r *mutationResolver) MoveFiles(ctx context.Context, input MoveFilesInput) fileIDs, err := stringslice.StringSliceToIntSlice(input.Ids) if err != nil { - return fmt.Errorf("converting file ids: %w", err) + return fmt.Errorf("converting ids: %w", err) } switch { @@ -35,7 +35,7 @@ func (r *mutationResolver) MoveFiles(ctx context.Context, input MoveFilesInput) folderID, err := strconv.Atoi(*input.DestinationFolderID) if err != nil { - return fmt.Errorf("invalid folder id %s: %w", *input.DestinationFolderID, err) + return fmt.Errorf("converting destination folder id: %w", err) } folder, err = folderStore.Find(ctx, models.FolderID(folderID)) @@ -146,7 +146,7 @@ func (r *mutationResolver) validateFileExtensionList(exts []string, oldBasename, func (r *mutationResolver) DeleteFiles(ctx context.Context, ids []string) (ret bool, err error) { fileIDs, err := stringslice.StringSliceToIntSlice(ids) if err != nil { - return false, err + return false, fmt.Errorf("converting ids: %w", err) } fileDeleter := file.NewDeleter() diff --git a/internal/api/resolver_mutation_gallery.go b/internal/api/resolver_mutation_gallery.go index ebdb94e647c..c7dc8d70f64 100644 --- a/internal/api/resolver_mutation_gallery.go +++ b/internal/api/resolver_mutation_gallery.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "strconv" - "time" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/pkg/file" @@ -18,6 +17,7 @@ import ( "github.com/stashapp/stash/pkg/utils" ) +// used to refetch gallery after hooks run func (r *mutationResolver) getGallery(ctx context.Context, id int) (ret *models.Gallery, err error) { if err := r.withTxn(ctx, func(ctx context.Context) error { ret, err = r.repository.Gallery.Find(ctx, id) @@ -39,40 +39,36 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input GalleryCreat inputMap: getUpdateInputMap(ctx), } - performerIDs, err := stringslice.StringSliceToIntSlice(input.PerformerIds) + // Populate a new gallery from the input + newGallery := models.NewGallery() + + newGallery.Title = input.Title + newGallery.URL = translator.string(input.URL) + newGallery.Details = translator.string(input.Details) + newGallery.Rating = translator.ratingConversion(input.Rating, input.Rating100) + + var err error + + newGallery.Date, err = translator.datePtr(input.Date) if err != nil { - return nil, fmt.Errorf("converting performer ids: %w", err) + return nil, fmt.Errorf("converting date: %w", err) } - tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds) + newGallery.StudioID, err = translator.intPtrFromString(input.StudioID) if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) + return nil, fmt.Errorf("converting studio id: %w", err) } - sceneIDs, err := stringslice.StringSliceToIntSlice(input.SceneIds) + + newGallery.PerformerIDs, err = translator.relatedIds(input.PerformerIds) if err != nil { - return nil, fmt.Errorf("converting scene ids: %w", err) + return nil, fmt.Errorf("converting performer ids: %w", err) } - - // Populate a new gallery from the input - currentTime := time.Now() - newGallery := models.Gallery{ - Title: input.Title, - URL: translator.string(input.URL, "url"), - Details: translator.string(input.Details, "details"), - Rating: translator.ratingConversionInt(input.Rating, input.Rating100), - PerformerIDs: models.NewRelatedIDs(performerIDs), - TagIDs: models.NewRelatedIDs(tagIDs), - SceneIDs: models.NewRelatedIDs(sceneIDs), - CreatedAt: currentTime, - UpdatedAt: currentTime, - } - - newGallery.Date, err = translator.datePtr(input.Date, "date") + newGallery.TagIDs, err = translator.relatedIds(input.TagIds) if err != nil { - return nil, fmt.Errorf("converting date: %w", err) + return nil, fmt.Errorf("converting tag ids: %w", err) } - newGallery.StudioID, err = translator.intPtrFromString(input.StudioID, "studio_id") + newGallery.SceneIDs, err = translator.relatedIds(input.SceneIds) if err != nil { - return nil, fmt.Errorf("converting studio id: %w", err) + return nil, fmt.Errorf("converting scene ids: %w", err) } // Start the transaction and save the gallery @@ -140,6 +136,7 @@ func (r *mutationResolver) GalleriesUpdate(ctx context.Context, input []*models. } r.hookExecutor.ExecutePostHooks(ctx, gallery.ID, plugin.GalleryUpdatePost, input, translator.getFields()) + gallery, err = r.getGallery(ctx, gallery.ID) if err != nil { return nil, err @@ -154,7 +151,7 @@ func (r *mutationResolver) GalleriesUpdate(ctx context.Context, input []*models. func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.GalleryUpdateInput, translator changesetTranslator) (*models.Gallery, error) { galleryID, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } qb := r.repository.Gallery @@ -182,25 +179,24 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle updatedGallery.Details = translator.optionalString(input.Details, "details") updatedGallery.URL = translator.optionalString(input.URL, "url") + updatedGallery.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) + updatedGallery.Organized = translator.optionalBool(input.Organized, "organized") + updatedGallery.Date, err = translator.optionalDate(input.Date, "date") if err != nil { return nil, fmt.Errorf("converting date: %w", err) } - updatedGallery.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) updatedGallery.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) } - updatedGallery.Organized = translator.optionalBool(input.Organized, "organized") - if input.PrimaryFileID != nil { - primaryFileID, err := strconv.Atoi(*input.PrimaryFileID) - if err != nil { - return nil, fmt.Errorf("converting primary file id: %w", err) - } - - converted := models.FileID(primaryFileID) - updatedGallery.PrimaryFileID = &converted + updatedGallery.PrimaryFileID, err = translator.fileIDPtrFromString(input.PrimaryFileID) + if err != nil { + return nil, fmt.Errorf("converting primary file id: %w", err) + } + if updatedGallery.PrimaryFileID != nil { + primaryFileID := *updatedGallery.PrimaryFileID if err := originalGallery.LoadFiles(ctx, r.repository.Gallery); err != nil { return nil, err @@ -209,35 +205,27 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle // ensure that new primary file is associated with gallery var f models.File for _, ff := range originalGallery.Files.List() { - if ff.Base().ID == converted { + if ff.Base().ID == primaryFileID { f = ff } } if f == nil { - return nil, fmt.Errorf("file with id %d not associated with gallery", converted) + return nil, fmt.Errorf("file with id %d not associated with gallery", primaryFileID) } } - if translator.hasField("performer_ids") { - updatedGallery.PerformerIDs, err = translateUpdateIDs(input.PerformerIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting performer ids: %w", err) - } + updatedGallery.PerformerIDs, err = translator.updateIds(input.PerformerIds, "performer_ids") + if err != nil { + return nil, fmt.Errorf("converting performer ids: %w", err) } - - if translator.hasField("tag_ids") { - updatedGallery.TagIDs, err = translateUpdateIDs(input.TagIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } + updatedGallery.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids") + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) } - - if translator.hasField("scene_ids") { - updatedGallery.SceneIDs, err = translateUpdateIDs(input.SceneIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting scene ids: %w", err) - } + updatedGallery.SceneIDs, err = translator.updateIds(input.SceneIds, "scene_ids") + if err != nil { + return nil, fmt.Errorf("converting scene ids: %w", err) } // gallery scene is set from the scene only @@ -253,7 +241,7 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input BulkGalleryUpdateInput) ([]*models.Gallery, error) { galleryIDs, err := stringslice.StringSliceToIntSlice(input.Ids) if err != nil { - return nil, err + return nil, fmt.Errorf("converting ids: %w", err) } translator := changesetTranslator{ @@ -265,36 +253,29 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input BulkGall updatedGallery.Details = translator.optionalString(input.Details, "details") updatedGallery.URL = translator.optionalString(input.URL, "url") + updatedGallery.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) + updatedGallery.Organized = translator.optionalBool(input.Organized, "organized") + updatedGallery.Date, err = translator.optionalDate(input.Date, "date") if err != nil { return nil, fmt.Errorf("converting date: %w", err) } - updatedGallery.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) updatedGallery.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) } - updatedGallery.Organized = translator.optionalBool(input.Organized, "organized") - if translator.hasField("performer_ids") { - updatedGallery.PerformerIDs, err = translateUpdateIDs(input.PerformerIds.Ids, input.PerformerIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting performer ids: %w", err) - } + updatedGallery.PerformerIDs, err = translator.updateIdsBulk(input.PerformerIds, "performer_ids") + if err != nil { + return nil, fmt.Errorf("converting performer ids: %w", err) } - - if translator.hasField("tag_ids") { - updatedGallery.TagIDs, err = translateUpdateIDs(input.TagIds.Ids, input.TagIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } + updatedGallery.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids") + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) } - - if translator.hasField("scene_ids") { - updatedGallery.SceneIDs, err = translateUpdateIDs(input.SceneIds.Ids, input.SceneIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting scene ids: %w", err) - } + updatedGallery.SceneIDs, err = translator.updateIdsBulk(input.SceneIds, "scene_ids") + if err != nil { + return nil, fmt.Errorf("converting scene ids: %w", err) } ret := []*models.Gallery{} @@ -336,7 +317,7 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input BulkGall func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.GalleryDestroyInput) (bool, error) { galleryIDs, err := stringslice.StringSliceToIntSlice(input.Ids) if err != nil { - return false, err + return false, fmt.Errorf("converting ids: %w", err) } var galleries []*models.Gallery @@ -427,12 +408,12 @@ func isStashPath(path string) bool { func (r *mutationResolver) AddGalleryImages(ctx context.Context, input GalleryAddInput) (bool, error) { galleryID, err := strconv.Atoi(input.GalleryID) if err != nil { - return false, err + return false, fmt.Errorf("converting gallery id: %w", err) } imageIDs, err := stringslice.StringSliceToIntSlice(input.ImageIds) if err != nil { - return false, err + return false, fmt.Errorf("converting image ids: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -457,12 +438,12 @@ func (r *mutationResolver) AddGalleryImages(ctx context.Context, input GalleryAd func (r *mutationResolver) RemoveGalleryImages(ctx context.Context, input GalleryRemoveInput) (bool, error) { galleryID, err := strconv.Atoi(input.GalleryID) if err != nil { - return false, err + return false, fmt.Errorf("converting gallery id: %w", err) } imageIDs, err := stringslice.StringSliceToIntSlice(input.ImageIds) if err != nil { - return false, err + return false, fmt.Errorf("converting image ids: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -501,14 +482,12 @@ func (r *mutationResolver) GalleryChapterCreate(ctx context.Context, input Galle return nil, fmt.Errorf("converting gallery id: %w", err) } - currentTime := time.Now() - newChapter := models.GalleryChapter{ - Title: input.Title, - ImageIndex: input.ImageIndex, - GalleryID: galleryID, - CreatedAt: currentTime, - UpdatedAt: currentTime, - } + // Populate a new gallery chapter from the input + newChapter := models.NewGalleryChapter() + + newChapter.Title = input.Title + newChapter.ImageIndex = input.ImageIndex + newChapter.GalleryID = galleryID // Start the transaction and save the gallery chapter if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -534,7 +513,7 @@ func (r *mutationResolver) GalleryChapterCreate(ctx context.Context, input Galle func (r *mutationResolver) GalleryChapterUpdate(ctx context.Context, input GalleryChapterUpdateInput) (*models.GalleryChapter, error) { chapterID, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } translator := changesetTranslator{ @@ -600,7 +579,7 @@ func (r *mutationResolver) GalleryChapterUpdate(ctx context.Context, input Galle func (r *mutationResolver) GalleryChapterDestroy(ctx context.Context, id string) (bool, error) { chapterID, err := strconv.Atoi(id) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_mutation_image.go b/internal/api/resolver_mutation_image.go index 6ea58e211f3..d6ac1f8cd8f 100644 --- a/internal/api/resolver_mutation_image.go +++ b/internal/api/resolver_mutation_image.go @@ -15,6 +15,7 @@ import ( "github.com/stashapp/stash/pkg/utils" ) +// used to refetch image after hooks run func (r *mutationResolver) getImage(ctx context.Context, id int) (ret *models.Image, err error) { if err := r.withTxn(ctx, func(ctx context.Context) error { ret, err = r.repository.Image.Find(ctx, id) @@ -75,6 +76,7 @@ func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*ImageUpdat } r.hookExecutor.ExecutePostHooks(ctx, image.ID, plugin.ImageUpdatePost, input, translator.getFields()) + image, err = r.getImage(ctx, image.ID) if err != nil { return nil, err @@ -89,7 +91,7 @@ func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*ImageUpdat func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInput, translator changesetTranslator) (*models.Image, error) { imageID, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } i, err := r.repository.Image.Find(ctx, imageID) @@ -105,8 +107,10 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp updatedImage := models.NewImagePartial() updatedImage.Title = translator.optionalString(input.Title, "title") - updatedImage.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) + updatedImage.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) updatedImage.URL = translator.optionalString(input.URL, "url") + updatedImage.Organized = translator.optionalBool(input.Organized, "organized") + updatedImage.Date, err = translator.optionalDate(input.Date, "date") if err != nil { return nil, fmt.Errorf("converting date: %w", err) @@ -115,16 +119,13 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) } - updatedImage.Organized = translator.optionalBool(input.Organized, "organized") - if input.PrimaryFileID != nil { - primaryFileID, err := strconv.Atoi(*input.PrimaryFileID) - if err != nil { - return nil, fmt.Errorf("converting primary file id: %w", err) - } - - converted := models.FileID(primaryFileID) - updatedImage.PrimaryFileID = &converted + updatedImage.PrimaryFileID, err = translator.fileIDPtrFromString(input.PrimaryFileID) + if err != nil { + return nil, fmt.Errorf("converting primary file id: %w", err) + } + if updatedImage.PrimaryFileID != nil { + primaryFileID := *updatedImage.PrimaryFileID if err := i.LoadFiles(ctx, r.repository.Image); err != nil { return nil, err @@ -133,24 +134,23 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp // ensure that new primary file is associated with image var f models.File for _, ff := range i.Files.List() { - if ff.Base().ID == converted { + if ff.Base().ID == primaryFileID { f = ff } } if f == nil { - return nil, fmt.Errorf("file with id %d not associated with image", converted) + return nil, fmt.Errorf("file with id %d not associated with image", primaryFileID) } } var updatedGalleryIDs []int - if translator.hasField("gallery_ids") { - updatedImage.GalleryIDs, err = translateUpdateIDs(input.GalleryIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting gallery ids: %w", err) - } - + updatedImage.GalleryIDs, err = translator.updateIds(input.GalleryIds, "gallery_ids") + if err != nil { + return nil, fmt.Errorf("converting gallery ids: %w", err) + } + if updatedImage.GalleryIDs != nil { // ensure gallery IDs are loaded if err := i.LoadGalleryIDs(ctx, r.repository.Image); err != nil { return nil, err @@ -163,18 +163,13 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp updatedGalleryIDs = updatedImage.GalleryIDs.ImpactedIDs(i.GalleryIDs.List()) } - if translator.hasField("performer_ids") { - updatedImage.PerformerIDs, err = translateUpdateIDs(input.PerformerIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting performer ids: %w", err) - } + updatedImage.PerformerIDs, err = translator.updateIds(input.PerformerIds, "performer_ids") + if err != nil { + return nil, fmt.Errorf("converting performer ids: %w", err) } - - if translator.hasField("tag_ids") { - updatedImage.TagIDs, err = translateUpdateIDs(input.TagIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } + updatedImage.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids") + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) } qb := r.repository.Image @@ -196,7 +191,7 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageUpdateInput) (ret []*models.Image, err error) { imageIDs, err := stringslice.StringSliceToIntSlice(input.Ids) if err != nil { - return nil, err + return nil, fmt.Errorf("converting ids: %w", err) } translator := changesetTranslator{ @@ -207,8 +202,10 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageU updatedImage := models.NewImagePartial() updatedImage.Title = translator.optionalString(input.Title, "title") - updatedImage.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) + updatedImage.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) updatedImage.URL = translator.optionalString(input.URL, "url") + updatedImage.Organized = translator.optionalBool(input.Organized, "organized") + updatedImage.Date, err = translator.optionalDate(input.Date, "date") if err != nil { return nil, fmt.Errorf("converting date: %w", err) @@ -217,27 +214,18 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageU if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) } - updatedImage.Organized = translator.optionalBool(input.Organized, "organized") - if translator.hasField("gallery_ids") { - updatedImage.GalleryIDs, err = translateUpdateIDs(input.GalleryIds.Ids, input.GalleryIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting gallery ids: %w", err) - } + updatedImage.GalleryIDs, err = translator.updateIdsBulk(input.GalleryIds, "gallery_ids") + if err != nil { + return nil, fmt.Errorf("converting gallery ids: %w", err) } - - if translator.hasField("performer_ids") { - updatedImage.PerformerIDs, err = translateUpdateIDs(input.PerformerIds.Ids, input.PerformerIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting performer ids: %w", err) - } + updatedImage.PerformerIDs, err = translator.updateIdsBulk(input.PerformerIds, "performer_ids") + if err != nil { + return nil, fmt.Errorf("converting performer ids: %w", err) } - - if translator.hasField("tag_ids") { - updatedImage.TagIDs, err = translateUpdateIDs(input.TagIds.Ids, input.TagIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } + updatedImage.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids") + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) } // Start the transaction and save the images @@ -308,7 +296,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageU func (r *mutationResolver) ImageDestroy(ctx context.Context, input models.ImageDestroyInput) (ret bool, err error) { imageID, err := strconv.Atoi(input.ID) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } var i *models.Image @@ -348,7 +336,7 @@ func (r *mutationResolver) ImageDestroy(ctx context.Context, input models.ImageD func (r *mutationResolver) ImagesDestroy(ctx context.Context, input models.ImagesDestroyInput) (ret bool, err error) { imageIDs, err := stringslice.StringSliceToIntSlice(input.Ids) if err != nil { - return false, err + return false, fmt.Errorf("converting ids: %w", err) } var images []*models.Image @@ -400,7 +388,7 @@ func (r *mutationResolver) ImagesDestroy(ctx context.Context, input models.Image func (r *mutationResolver) ImageIncrementO(ctx context.Context, id string) (ret int, err error) { imageID, err := strconv.Atoi(id) if err != nil { - return 0, err + return 0, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -418,7 +406,7 @@ func (r *mutationResolver) ImageIncrementO(ctx context.Context, id string) (ret func (r *mutationResolver) ImageDecrementO(ctx context.Context, id string) (ret int, err error) { imageID, err := strconv.Atoi(id) if err != nil { - return 0, err + return 0, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -436,7 +424,7 @@ func (r *mutationResolver) ImageDecrementO(ctx context.Context, id string) (ret func (r *mutationResolver) ImageResetO(ctx context.Context, id string) (ret int, err error) { imageID, err := strconv.Atoi(id) if err != nil { - return 0, err + return 0, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_mutation_job.go b/internal/api/resolver_mutation_job.go index 5417468339d..74ced81d5f7 100644 --- a/internal/api/resolver_mutation_job.go +++ b/internal/api/resolver_mutation_job.go @@ -2,17 +2,18 @@ package api import ( "context" + "fmt" "strconv" "github.com/stashapp/stash/internal/manager" ) func (r *mutationResolver) StopJob(ctx context.Context, jobID string) (bool, error) { - idInt, err := strconv.Atoi(jobID) + id, err := strconv.Atoi(jobID) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } - manager.GetInstance().JobManager.CancelJob(idInt) + manager.GetInstance().JobManager.CancelJob(id) return true, nil } diff --git a/internal/api/resolver_mutation_movie.go b/internal/api/resolver_mutation_movie.go index b06d84a7f90..ef2d2405afe 100644 --- a/internal/api/resolver_mutation_movie.go +++ b/internal/api/resolver_mutation_movie.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "strconv" - "time" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/plugin" @@ -12,6 +11,7 @@ import ( "github.com/stashapp/stash/pkg/utils" ) +// used to refetch movie after hooks run func (r *mutationResolver) getMovie(ctx context.Context, id int) (ret *models.Movie, err error) { if err := r.withTxn(ctx, func(ctx context.Context) error { ret, err = r.repository.Movie.Find(ctx, id) @@ -29,26 +29,23 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp } // Populate a new movie from the input - currentTime := time.Now() - newMovie := models.Movie{ - Name: input.Name, - CreatedAt: currentTime, - UpdatedAt: currentTime, - Aliases: translator.string(input.Aliases, "aliases"), - Duration: input.Duration, - Rating: translator.ratingConversionInt(input.Rating, input.Rating100), - Director: translator.string(input.Director, "director"), - Synopsis: translator.string(input.Synopsis, "synopsis"), - URL: translator.string(input.URL, "url"), - } + newMovie := models.NewMovie() + + newMovie.Name = input.Name + newMovie.Aliases = translator.string(input.Aliases) + newMovie.Duration = input.Duration + newMovie.Rating = translator.ratingConversion(input.Rating, input.Rating100) + newMovie.Director = translator.string(input.Director) + newMovie.Synopsis = translator.string(input.Synopsis) + newMovie.URL = translator.string(input.URL) var err error - newMovie.Date, err = translator.datePtr(input.Date, "date") + newMovie.Date, err = translator.datePtr(input.Date) if err != nil { return nil, fmt.Errorf("converting date: %w", err) } - newMovie.StudioID, err = translator.intPtrFromString(input.StudioID, "studio_id") + newMovie.StudioID, err = translator.intPtrFromString(input.StudioID) if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) } @@ -64,7 +61,7 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp if input.FrontImage != nil { frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage) if err != nil { - return nil, err + return nil, fmt.Errorf("processing front image: %w", err) } } @@ -73,7 +70,7 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp if input.BackImage != nil { backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage) if err != nil { - return nil, err + return nil, fmt.Errorf("processing back image: %w", err) } } @@ -111,7 +108,7 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInput) (*models.Movie, error) { movieID, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } translator := changesetTranslator{ @@ -124,14 +121,15 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp updatedMovie.Name = translator.optionalString(input.Name, "name") updatedMovie.Aliases = translator.optionalString(input.Aliases, "aliases") updatedMovie.Duration = translator.optionalInt(input.Duration, "duration") + updatedMovie.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) + updatedMovie.Director = translator.optionalString(input.Director, "director") + updatedMovie.Synopsis = translator.optionalString(input.Synopsis, "synopsis") + updatedMovie.URL = translator.optionalString(input.URL, "url") + updatedMovie.Date, err = translator.optionalDate(input.Date, "date") if err != nil { return nil, fmt.Errorf("converting date: %w", err) } - updatedMovie.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) - updatedMovie.Director = translator.optionalString(input.Director, "director") - updatedMovie.Synopsis = translator.optionalString(input.Synopsis, "synopsis") - updatedMovie.URL = translator.optionalString(input.URL, "url") updatedMovie.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) @@ -142,7 +140,7 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp if input.FrontImage != nil { frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage) if err != nil { - return nil, err + return nil, fmt.Errorf("processing front image: %w", err) } } @@ -151,7 +149,7 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp if input.BackImage != nil { backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage) if err != nil { - return nil, err + return nil, fmt.Errorf("processing back image: %w", err) } } @@ -189,18 +187,19 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieUpdateInput) ([]*models.Movie, error) { movieIDs, err := stringslice.StringSliceToIntSlice(input.Ids) if err != nil { - return nil, err + return nil, fmt.Errorf("converting ids: %w", err) } translator := changesetTranslator{ inputMap: getUpdateInputMap(ctx), } - // populate movie from the input + // Populate movie from the input updatedMovie := models.NewMoviePartial() - updatedMovie.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) + updatedMovie.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) updatedMovie.Director = translator.optionalString(input.Director, "director") + updatedMovie.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) @@ -243,7 +242,7 @@ func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieU func (r *mutationResolver) MovieDestroy(ctx context.Context, input MovieDestroyInput) (bool, error) { id, err := strconv.Atoi(input.ID) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -260,7 +259,7 @@ func (r *mutationResolver) MovieDestroy(ctx context.Context, input MovieDestroyI func (r *mutationResolver) MoviesDestroy(ctx context.Context, movieIDs []string) (bool, error) { ids, err := stringslice.StringSliceToIntSlice(movieIDs) if err != nil { - return false, err + return false, fmt.Errorf("converting ids: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_mutation_performer.go b/internal/api/resolver_mutation_performer.go index dbdfe2160fc..9e40e7a01bf 100644 --- a/internal/api/resolver_mutation_performer.go +++ b/internal/api/resolver_mutation_performer.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "strconv" - "time" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/performer" @@ -13,6 +12,7 @@ import ( "github.com/stashapp/stash/pkg/utils" ) +// used to refetch performer after hooks run 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) @@ -24,62 +24,45 @@ func (r *mutationResolver) getPerformer(ctx context.Context, id int) (ret *model 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) { +func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.PerformerCreateInput) (*models.Performer, error) { translator := changesetTranslator{ inputMap: getUpdateInputMap(ctx), } - tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } - // Populate a new performer from the input - currentTime := time.Now() - newPerformer := models.Performer{ - Name: input.Name, - Disambiguation: translator.string(input.Disambiguation, "disambiguation"), - URL: translator.string(input.URL, "url"), - Gender: input.Gender, - Ethnicity: translator.string(input.Ethnicity, "ethnicity"), - Country: translator.string(input.Country, "country"), - EyeColor: translator.string(input.EyeColor, "eye_color"), - Measurements: translator.string(input.Measurements, "measurements"), - FakeTits: translator.string(input.FakeTits, "fake_tits"), - PenisLength: input.PenisLength, - Circumcised: input.Circumcised, - CareerLength: translator.string(input.CareerLength, "career_length"), - Tattoos: translator.string(input.Tattoos, "tattoos"), - Piercings: translator.string(input.Piercings, "piercings"), - Twitter: translator.string(input.Twitter, "twitter"), - Instagram: translator.string(input.Instagram, "instagram"), - Favorite: translator.bool(input.Favorite, "favorite"), - Rating: translator.ratingConversionInt(input.Rating, input.Rating100), - Details: translator.string(input.Details, "details"), - HairColor: translator.string(input.HairColor, "hair_color"), - Weight: input.Weight, - IgnoreAutoTag: translator.bool(input.IgnoreAutoTag, "ignore_auto_tag"), - CreatedAt: currentTime, - UpdatedAt: currentTime, - TagIDs: models.NewRelatedIDs(tagIDs), - StashIDs: models.NewRelatedStashIDs(stashIDPtrSliceToSlice(input.StashIds)), - } - - newPerformer.Birthdate, err = translator.datePtr(input.Birthdate, "birthdate") + newPerformer := models.NewPerformer() + + newPerformer.Name = input.Name + newPerformer.Disambiguation = translator.string(input.Disambiguation) + newPerformer.URL = translator.string(input.URL) + newPerformer.Gender = input.Gender + newPerformer.Ethnicity = translator.string(input.Ethnicity) + newPerformer.Country = translator.string(input.Country) + newPerformer.EyeColor = translator.string(input.EyeColor) + newPerformer.Measurements = translator.string(input.Measurements) + newPerformer.FakeTits = translator.string(input.FakeTits) + newPerformer.PenisLength = input.PenisLength + newPerformer.Circumcised = input.Circumcised + newPerformer.CareerLength = translator.string(input.CareerLength) + newPerformer.Tattoos = translator.string(input.Tattoos) + newPerformer.Piercings = translator.string(input.Piercings) + newPerformer.Twitter = translator.string(input.Twitter) + newPerformer.Instagram = translator.string(input.Instagram) + newPerformer.Favorite = translator.bool(input.Favorite) + newPerformer.Rating = translator.ratingConversion(input.Rating, input.Rating100) + newPerformer.Details = translator.string(input.Details) + newPerformer.HairColor = translator.string(input.HairColor) + newPerformer.Weight = input.Weight + newPerformer.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag) + newPerformer.StashIDs = models.NewRelatedStashIDs(input.StashIds) + + var err error + + newPerformer.Birthdate, err = translator.datePtr(input.Birthdate) if err != nil { return nil, fmt.Errorf("converting birthdate: %w", err) } - newPerformer.DeathDate, err = translator.datePtr(input.DeathDate, "death_date") + newPerformer.DeathDate, err = translator.datePtr(input.DeathDate) if err != nil { return nil, fmt.Errorf("converting death date: %w", err) } @@ -88,18 +71,24 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC if input.HeightCm != nil { newPerformer.Height = input.HeightCm } else { - newPerformer.Height, err = translator.intPtrFromString(input.Height, "height") + newPerformer.Height, err = translator.intPtrFromString(input.Height) if err != nil { return nil, fmt.Errorf("converting height: %w", err) } } + // prefer alias_list over aliases if input.AliasList != nil { newPerformer.Aliases = models.NewRelatedStrings(input.AliasList) } else if input.Aliases != nil { newPerformer.Aliases = models.NewRelatedStrings(stringslice.FromString(*input.Aliases, ",")) } + newPerformer.TagIDs, err = translator.relatedIds(input.TagIds) + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) + } + if err := performer.ValidateDeathDate(nil, input.Birthdate, input.DeathDate); err != nil { if err != nil { return nil, err @@ -111,7 +100,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC if input.Image != nil { imageData, err = utils.ProcessImageInput(ctx, *input.Image) if err != nil { - return nil, err + return nil, fmt.Errorf("processing image: %w", err) } } @@ -140,42 +129,27 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC 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 models.PerformerUpdateInput) (*models.Performer, error) { performerID, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } - // Populate performer from the input translator := changesetTranslator{ inputMap: getUpdateInputMap(ctx), } + // Populate performer from the input updatedPerformer := models.NewPerformerPartial() updatedPerformer.Name = translator.optionalString(input.Name, "name") updatedPerformer.Disambiguation = translator.optionalString(input.Disambiguation, "disambiguation") updatedPerformer.URL = translator.optionalString(input.URL, "url") updatedPerformer.Gender = translator.optionalString((*string)(input.Gender), "gender") - updatedPerformer.Birthdate, err = translator.optionalDate(input.Birthdate, "birthdate") - if err != nil { - return nil, fmt.Errorf("converting birthdate: %w", err) - } updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity") updatedPerformer.Country = translator.optionalString(input.Country, "country") updatedPerformer.EyeColor = translator.optionalString(input.EyeColor, "eye_color") updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements") - - // prefer height_cm over height - if translator.hasField("height_cm") { - updatedPerformer.Height = translator.optionalInt(input.HeightCm, "height_cm") - } else if translator.hasField("height") { - updatedPerformer.Height, err = translator.optionalIntFromString(input.Height, "height") - if err != nil { - return nil, err - } - } - updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits") updatedPerformer.PenisLength = translator.optionalFloat64(input.PenisLength, "penis_length") updatedPerformer.Circumcised = translator.optionalString((*string)(input.Circumcised), "circumcised") @@ -185,45 +159,46 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter") updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram") updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite") - updatedPerformer.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) + updatedPerformer.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) updatedPerformer.Details = translator.optionalString(input.Details, "details") - updatedPerformer.DeathDate, err = translator.optionalDate(input.DeathDate, "death_date") - if err != nil { - return nil, fmt.Errorf("converting death date: %w", err) - } updatedPerformer.HairColor = translator.optionalString(input.HairColor, "hair_color") updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight") updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag") + updatedPerformer.StashIDs = translator.updateStashIDs(input.StashIds, "stash_ids") - if translator.hasField("alias_list") { - updatedPerformer.Aliases = &models.UpdateStrings{ - Values: input.AliasList, - Mode: models.RelationshipUpdateModeSet, - } - } else if translator.hasField("aliases") { - var values []string - if input.Aliases != nil { - values = stringslice.FromString(*input.Aliases, ",") - } - updatedPerformer.Aliases = &models.UpdateStrings{ - Values: values, - Mode: models.RelationshipUpdateModeSet, - } + updatedPerformer.Birthdate, err = translator.optionalDate(input.Birthdate, "birthdate") + if err != nil { + return nil, fmt.Errorf("converting birthdate: %w", err) + } + updatedPerformer.DeathDate, err = translator.optionalDate(input.DeathDate, "death_date") + if err != nil { + return nil, fmt.Errorf("converting death date: %w", err) } - if translator.hasField("tag_ids") { - updatedPerformer.TagIDs, err = translateUpdateIDs(input.TagIds, models.RelationshipUpdateModeSet) + // prefer height_cm over height + if translator.hasField("height_cm") { + updatedPerformer.Height = translator.optionalInt(input.HeightCm, "height_cm") + } else if translator.hasField("height") { + updatedPerformer.Height, err = translator.optionalIntFromString(input.Height, "height") if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) + return nil, fmt.Errorf("converting height: %w", err) } } - // Save the stash_ids - if translator.hasField("stash_ids") { - updatedPerformer.StashIDs = &models.UpdateStashIDs{ - StashIDs: stashIDPtrSliceToSlice(input.StashIds), - Mode: models.RelationshipUpdateModeSet, + // prefer alias_list over aliases + if translator.hasField("alias_list") { + updatedPerformer.Aliases = translator.updateStrings(input.AliasList, "alias_list") + } else if translator.hasField("aliases") { + var aliasList []string + if input.Aliases != nil { + aliasList = stringslice.FromString(*input.Aliases, ",") } + updatedPerformer.Aliases = translator.updateStrings(aliasList, "aliases") + } + + updatedPerformer.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids") + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) } var imageData []byte @@ -231,7 +206,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU if input.Image != nil { imageData, err = utils.ProcessImageInput(ctx, *input.Image) if err != nil { - return nil, err + return nil, fmt.Errorf("processing image: %w", err) } } @@ -250,9 +225,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU } if err := performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate); err != nil { - if err != nil { - return err - } + return err } _, err = qb.UpdatePartial(ctx, performerID, updatedPerformer) @@ -279,37 +252,22 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPerformerUpdateInput) ([]*models.Performer, error) { performerIDs, err := stringslice.StringSliceToIntSlice(input.Ids) if err != nil { - return nil, err + return nil, fmt.Errorf("converting ids: %w", err) } - // Populate performer from the input translator := changesetTranslator{ inputMap: getUpdateInputMap(ctx), } + // Populate performer from the input updatedPerformer := models.NewPerformerPartial() updatedPerformer.Disambiguation = translator.optionalString(input.Disambiguation, "disambiguation") updatedPerformer.URL = translator.optionalString(input.URL, "url") updatedPerformer.Gender = translator.optionalString((*string)(input.Gender), "gender") - updatedPerformer.Birthdate, err = translator.optionalDate(input.Birthdate, "birthdate") - if err != nil { - return nil, fmt.Errorf("converting birthdate: %w", err) - } updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity") updatedPerformer.Country = translator.optionalString(input.Country, "country") updatedPerformer.EyeColor = translator.optionalString(input.EyeColor, "eye_color") - - // prefer height_cm over height - if translator.hasField("height_cm") { - updatedPerformer.Height = translator.optionalInt(input.HeightCm, "height_cm") - } else if translator.hasField("height") { - updatedPerformer.Height, err = translator.optionalIntFromString(input.Height, "height") - if err != nil { - return nil, err - } - } - updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements") updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits") updatedPerformer.PenisLength = translator.optionalFloat64(input.PenisLength, "penis_length") @@ -320,37 +278,45 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter") updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram") updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite") - updatedPerformer.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) + updatedPerformer.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) updatedPerformer.Details = translator.optionalString(input.Details, "details") + updatedPerformer.HairColor = translator.optionalString(input.HairColor, "hair_color") + updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight") + updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag") + + updatedPerformer.Birthdate, err = translator.optionalDate(input.Birthdate, "birthdate") + if err != nil { + return nil, fmt.Errorf("converting birthdate: %w", err) + } updatedPerformer.DeathDate, err = translator.optionalDate(input.DeathDate, "death_date") if err != nil { return nil, fmt.Errorf("converting death date: %w", err) } - 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("alias_list") { - updatedPerformer.Aliases = &models.UpdateStrings{ - Values: input.AliasList.Values, - Mode: input.AliasList.Mode, + // prefer height_cm over height + if translator.hasField("height_cm") { + updatedPerformer.Height = translator.optionalInt(input.HeightCm, "height_cm") + } else if translator.hasField("height") { + updatedPerformer.Height, err = translator.optionalIntFromString(input.Height, "height") + if err != nil { + return nil, fmt.Errorf("converting height: %w", err) } + } + + // prefer alias_list over aliases + if translator.hasField("alias_list") { + updatedPerformer.Aliases = translator.updateStringsBulk(input.AliasList, "alias_list") } else if translator.hasField("aliases") { - var values []string + var aliasList []string if input.Aliases != nil { - values = stringslice.FromString(*input.Aliases, ",") - } - updatedPerformer.Aliases = &models.UpdateStrings{ - Values: values, - Mode: models.RelationshipUpdateModeSet, + aliasList = stringslice.FromString(*input.Aliases, ",") } + updatedPerformer.Aliases = translator.updateStrings(aliasList, "aliases") } - if translator.hasField("tag_ids") { - updatedPerformer.TagIDs, err = translateUpdateIDs(input.TagIds.Ids, input.TagIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } + updatedPerformer.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids") + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) } ret := []*models.Performer{} @@ -370,7 +336,8 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe return fmt.Errorf("performer with id %d not found", performerID) } - if err := performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate); err != nil { + err = performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate) + if err != nil { return err } @@ -406,7 +373,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe func (r *mutationResolver) PerformerDestroy(ctx context.Context, input PerformerDestroyInput) (bool, error) { id, err := strconv.Atoi(input.ID) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -423,7 +390,7 @@ func (r *mutationResolver) PerformerDestroy(ctx context.Context, input Performer func (r *mutationResolver) PerformersDestroy(ctx context.Context, performerIDs []string) (bool, error) { ids, err := stringslice.StringSliceToIntSlice(performerIDs) if err != nil { - return false, err + return false, fmt.Errorf("converting ids: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_mutation_saved_filter.go b/internal/api/resolver_mutation_saved_filter.go index 89062227068..13b5d87fafa 100644 --- a/internal/api/resolver_mutation_saved_filter.go +++ b/internal/api/resolver_mutation_saved_filter.go @@ -3,6 +3,7 @@ package api import ( "context" "errors" + "fmt" "strconv" "strings" @@ -18,7 +19,7 @@ func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput if input.ID != nil { idv, err := strconv.Atoi(*input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } id = &idv } @@ -53,7 +54,7 @@ func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput func (r *mutationResolver) DestroySavedFilter(ctx context.Context, input DestroyFilterInput) (bool, error) { id, err := strconv.Atoi(input.ID) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_mutation_scene.go b/internal/api/resolver_mutation_scene.go index 4c338586c5e..7652827f33f 100644 --- a/internal/api/resolver_mutation_scene.go +++ b/internal/api/resolver_mutation_scene.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "strconv" - "time" "github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/pkg/file" @@ -17,6 +16,7 @@ import ( "github.com/stashapp/stash/pkg/utils" ) +// used to refetch scene after hooks run func (r *mutationResolver) getScene(ctx context.Context, id int) (ret *models.Scene, err error) { if err := r.withTxn(ctx, func(ctx context.Context) error { ret, err = r.repository.Scene.Find(ctx, id) @@ -28,59 +28,32 @@ func (r *mutationResolver) getScene(ctx context.Context, id int) (ret *models.Sc return ret, nil } -func (r *mutationResolver) SceneCreate(ctx context.Context, input SceneCreateInput) (ret *models.Scene, err error) { +func (r *mutationResolver) SceneCreate(ctx context.Context, input models.SceneCreateInput) (ret *models.Scene, err error) { translator := changesetTranslator{ inputMap: getUpdateInputMap(ctx), } - performerIDs, err := stringslice.StringSliceToIntSlice(input.PerformerIds) - if err != nil { - return nil, fmt.Errorf("converting performer ids: %w", err) - } - tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } - galleryIDs, err := stringslice.StringSliceToIntSlice(input.GalleryIds) - if err != nil { - return nil, fmt.Errorf("converting gallery ids: %w", err) - } - - moviesScenes, err := models.MoviesScenesFromInput(input.Movies) - if err != nil { - return nil, fmt.Errorf("converting movies scenes: %w", err) - } - - fileIDsInt, err := stringslice.StringSliceToIntSlice(input.FileIds) + fileIDs, err := translator.fileIDSliceFromStringSlice(input.FileIds) if err != nil { return nil, fmt.Errorf("converting file ids: %w", err) } - fileIDs := make([]models.FileID, len(fileIDsInt)) - for i, v := range fileIDsInt { - fileIDs[i] = models.FileID(v) - } - // Populate a new scene from the input - newScene := models.Scene{ - Title: translator.string(input.Title, "title"), - Code: translator.string(input.Code, "code"), - Details: translator.string(input.Details, "details"), - Director: translator.string(input.Director, "director"), - Rating: translator.ratingConversionInt(input.Rating, input.Rating100), - Organized: translator.bool(input.Organized, "organized"), - PerformerIDs: models.NewRelatedIDs(performerIDs), - TagIDs: models.NewRelatedIDs(tagIDs), - GalleryIDs: models.NewRelatedIDs(galleryIDs), - Movies: models.NewRelatedMovies(moviesScenes), - StashIDs: models.NewRelatedStashIDs(stashIDPtrSliceToSlice(input.StashIds)), - } - - newScene.Date, err = translator.datePtr(input.Date, "date") + newScene := models.NewScene() + + newScene.Title = translator.string(input.Title) + newScene.Code = translator.string(input.Code) + newScene.Details = translator.string(input.Details) + newScene.Director = translator.string(input.Director) + newScene.Rating = translator.ratingConversion(input.Rating, input.Rating100) + newScene.Organized = translator.bool(input.Organized) + newScene.StashIDs = models.NewRelatedStashIDs(input.StashIds) + + newScene.Date, err = translator.datePtr(input.Date) if err != nil { return nil, fmt.Errorf("converting date: %w", err) } - newScene.StudioID, err = translator.intPtrFromString(input.StudioID, "studio_id") + newScene.StudioID, err = translator.intPtrFromString(input.StudioID) if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) } @@ -91,12 +64,30 @@ func (r *mutationResolver) SceneCreate(ctx context.Context, input SceneCreateInp newScene.URLs = models.NewRelatedStrings([]string{*input.URL}) } + newScene.PerformerIDs, err = translator.relatedIds(input.PerformerIds) + if err != nil { + return nil, fmt.Errorf("converting performer ids: %w", err) + } + newScene.TagIDs, err = translator.relatedIds(input.TagIds) + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) + } + newScene.GalleryIDs, err = translator.relatedIds(input.GalleryIds) + if err != nil { + return nil, fmt.Errorf("converting gallery ids: %w", err) + } + + newScene.Movies, err = translator.relatedMovies(input.Movies) + if err != nil { + return nil, fmt.Errorf("converting movies: %w", err) + } + var coverImageData []byte if input.CoverImage != nil { var err error coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage) if err != nil { - return nil, err + return nil, fmt.Errorf("processing cover image: %w", err) } } @@ -173,88 +164,60 @@ func (r *mutationResolver) ScenesUpdate(ctx context.Context, input []*models.Sce func scenePartialFromInput(input models.SceneUpdateInput, translator changesetTranslator) (*models.ScenePartial, error) { updatedScene := models.NewScenePartial() - var err error - updatedScene.Title = translator.optionalString(input.Title, "title") updatedScene.Code = translator.optionalString(input.Code, "code") updatedScene.Details = translator.optionalString(input.Details, "details") updatedScene.Director = translator.optionalString(input.Director, "director") + updatedScene.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) + updatedScene.OCounter = translator.optionalInt(input.OCounter, "o_counter") + updatedScene.PlayCount = translator.optionalInt(input.PlayCount, "play_count") + updatedScene.PlayDuration = translator.optionalFloat64(input.PlayDuration, "play_duration") + updatedScene.Organized = translator.optionalBool(input.Organized, "organized") + updatedScene.StashIDs = translator.updateStashIDs(input.StashIds, "stash_ids") + + var err error + updatedScene.Date, err = translator.optionalDate(input.Date, "date") if err != nil { return nil, fmt.Errorf("converting date: %w", err) } - updatedScene.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) - updatedScene.OCounter = translator.optionalInt(input.OCounter, "o_counter") - updatedScene.PlayCount = translator.optionalInt(input.PlayCount, "play_count") - updatedScene.PlayDuration = translator.optionalFloat64(input.PlayDuration, "play_duration") updatedScene.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) } - updatedScene.Organized = translator.optionalBool(input.Organized, "organized") - + // prefer urls over url if translator.hasField("urls") { - updatedScene.URLs = &models.UpdateStrings{ - Values: input.Urls, - Mode: models.RelationshipUpdateModeSet, - } + updatedScene.URLs = translator.updateStrings(input.Urls, "urls") } else if translator.hasField("url") { - var values []string + var urls []string if input.URL != nil { - values = []string{*input.URL} - } - updatedScene.URLs = &models.UpdateStrings{ - Values: values, - Mode: models.RelationshipUpdateModeSet, - } - } - - if input.PrimaryFileID != nil { - primaryFileID, err := strconv.Atoi(*input.PrimaryFileID) - if err != nil { - return nil, fmt.Errorf("converting primary file id: %w", err) + urls = []string{*input.URL} } - - converted := models.FileID(primaryFileID) - updatedScene.PrimaryFileID = &converted + updatedScene.URLs = translator.updateStrings(urls, "url") } - if translator.hasField("performer_ids") { - updatedScene.PerformerIDs, err = translateUpdateIDs(input.PerformerIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting performer ids: %w", err) - } + updatedScene.PrimaryFileID, err = translator.fileIDPtrFromString(input.PrimaryFileID) + if err != nil { + return nil, fmt.Errorf("converting primary file id: %w", err) } - if translator.hasField("tag_ids") { - updatedScene.TagIDs, err = translateUpdateIDs(input.TagIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } + updatedScene.PerformerIDs, err = translator.updateIds(input.PerformerIds, "performer_ids") + if err != nil { + return nil, fmt.Errorf("converting performer ids: %w", err) } - - if translator.hasField("gallery_ids") { - updatedScene.GalleryIDs, err = translateUpdateIDs(input.GalleryIds, models.RelationshipUpdateModeSet) - if err != nil { - return nil, fmt.Errorf("converting gallery ids: %w", err) - } + updatedScene.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids") + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) } - - // Save the movies - if translator.hasField("movies") { - updatedScene.MovieIDs, err = models.UpdateMovieIDsFromInput(input.Movies) - if err != nil { - return nil, fmt.Errorf("converting movie ids: %w", err) - } + updatedScene.GalleryIDs, err = translator.updateIds(input.GalleryIds, "gallery_ids") + if err != nil { + return nil, fmt.Errorf("converting gallery ids: %w", err) } - // Save the stash_ids - if translator.hasField("stash_ids") { - updatedScene.StashIDs = &models.UpdateStashIDs{ - StashIDs: input.StashIds, - Mode: models.RelationshipUpdateModeSet, - } + updatedScene.MovieIDs, err = translator.updateMovieIDs(input.Movies, "movies") + if err != nil { + return nil, fmt.Errorf("converting movies: %w", err) } return &updatedScene, nil @@ -263,7 +226,7 @@ func scenePartialFromInput(input models.SceneUpdateInput, translator changesetTr func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUpdateInput, translator changesetTranslator) (*models.Scene, error) { sceneID, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } qb := r.repository.Scene @@ -321,7 +284,7 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp var err error coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage) if err != nil { - return nil, err + return nil, fmt.Errorf("processing cover image: %w", err) } } @@ -353,7 +316,7 @@ func (r *mutationResolver) sceneUpdateCoverImage(ctx context.Context, s *models. func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneUpdateInput) ([]*models.Scene, error) { sceneIDs, err := stringslice.StringSliceToIntSlice(input.Ids) if err != nil { - return nil, err + return nil, fmt.Errorf("converting ids: %w", err) } translator := changesetTranslator{ @@ -367,61 +330,45 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneU updatedScene.Code = translator.optionalString(input.Code, "code") updatedScene.Details = translator.optionalString(input.Details, "details") updatedScene.Director = translator.optionalString(input.Director, "director") + updatedScene.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) + updatedScene.Organized = translator.optionalBool(input.Organized, "organized") + updatedScene.Date, err = translator.optionalDate(input.Date, "date") if err != nil { return nil, fmt.Errorf("converting date: %w", err) } - updatedScene.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100) updatedScene.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) } - updatedScene.Organized = translator.optionalBool(input.Organized, "organized") - + // prefer urls over url if translator.hasField("urls") { - updatedScene.URLs = &models.UpdateStrings{ - Values: input.Urls.Values, - Mode: input.Urls.Mode, - } + updatedScene.URLs = translator.updateStringsBulk(input.Urls, "urls") } else if translator.hasField("url") { - var values []string + var urls []string if input.URL != nil { - values = []string{*input.URL} - } - updatedScene.URLs = &models.UpdateStrings{ - Values: values, - Mode: models.RelationshipUpdateModeSet, + urls = []string{*input.URL} } + updatedScene.URLs = translator.updateStrings(urls, "url") } - if translator.hasField("performer_ids") { - updatedScene.PerformerIDs, err = translateUpdateIDs(input.PerformerIds.Ids, input.PerformerIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting performer ids: %w", err) - } + updatedScene.PerformerIDs, err = translator.updateIdsBulk(input.PerformerIds, "performer_ids") + if err != nil { + return nil, fmt.Errorf("converting performer ids: %w", err) } - - if translator.hasField("tag_ids") { - updatedScene.TagIDs, err = translateUpdateIDs(input.TagIds.Ids, input.TagIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting tag ids: %w", err) - } + updatedScene.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids") + if err != nil { + return nil, fmt.Errorf("converting tag ids: %w", err) } - - if translator.hasField("gallery_ids") { - updatedScene.GalleryIDs, err = translateUpdateIDs(input.GalleryIds.Ids, input.GalleryIds.Mode) - if err != nil { - return nil, fmt.Errorf("converting gallery ids: %w", err) - } + updatedScene.GalleryIDs, err = translator.updateIdsBulk(input.GalleryIds, "gallery_ids") + if err != nil { + return nil, fmt.Errorf("converting gallery ids: %w", err) } - // Save the movies - if translator.hasField("movie_ids") { - updatedScene.MovieIDs, err = translateSceneMovieIDs(*input.MovieIds) - if err != nil { - return nil, fmt.Errorf("converting movie ids: %w", err) - } + updatedScene.MovieIDs, err = translator.updateMovieIDsBulk(input.MovieIds, "movie_ids") + if err != nil { + return nil, fmt.Errorf("converting movie ids: %w", err) } ret := []*models.Scene{} @@ -463,7 +410,7 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneU func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneDestroyInput) (bool, error) { sceneID, err := strconv.Atoi(input.ID) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm() @@ -514,6 +461,11 @@ func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneD } func (r *mutationResolver) ScenesDestroy(ctx context.Context, input models.ScenesDestroyInput) (bool, error) { + sceneIDs, err := stringslice.StringSliceToIntSlice(input.Ids) + if err != nil { + return false, fmt.Errorf("converting ids: %w", err) + } + var scenes []*models.Scene fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm() @@ -529,23 +481,21 @@ func (r *mutationResolver) ScenesDestroy(ctx context.Context, input models.Scene if err := r.withTxn(ctx, func(ctx context.Context) error { qb := r.repository.Scene - for _, id := range input.Ids { - sceneID, _ := strconv.Atoi(id) - - s, err := qb.Find(ctx, sceneID) + for _, id := range sceneIDs { + scene, err := qb.Find(ctx, id) if err != nil { return err } - if s == nil { - return fmt.Errorf("scene with id %d not found", sceneID) + if scene == nil { + return fmt.Errorf("scene with id %d not found", id) } - scenes = append(scenes, s) + scenes = append(scenes, scene) // kill any running encoders - manager.KillRunningStreams(s, fileNamingAlgo) + manager.KillRunningStreams(scene, fileNamingAlgo) - if err := r.sceneService.Destroy(ctx, s, fileDeleter, deleteGenerated, deleteFile); err != nil { + if err := r.sceneService.Destroy(ctx, scene, fileDeleter, deleteGenerated, deleteFile); err != nil { return err } } @@ -575,18 +525,16 @@ func (r *mutationResolver) ScenesDestroy(ctx context.Context, input models.Scene func (r *mutationResolver) SceneAssignFile(ctx context.Context, input AssignSceneFileInput) (bool, error) { sceneID, err := strconv.Atoi(input.SceneID) if err != nil { - return false, fmt.Errorf("converting scene ID: %w", err) + return false, fmt.Errorf("converting scene id: %w", err) } - fileIDInt, err := strconv.Atoi(input.FileID) + fileID, err := strconv.Atoi(input.FileID) if err != nil { - return false, fmt.Errorf("converting file ID: %w", err) + return false, fmt.Errorf("converting file id: %w", err) } - fileID := models.FileID(fileIDInt) - if err := r.withTxn(ctx, func(ctx context.Context) error { - return r.Resolver.sceneService.AssignFile(ctx, sceneID, fileID) + return r.Resolver.sceneService.AssignFile(ctx, sceneID, models.FileID(fileID)) }); err != nil { return false, fmt.Errorf("assigning file to scene: %w", err) } @@ -597,12 +545,12 @@ func (r *mutationResolver) SceneAssignFile(ctx context.Context, input AssignScen func (r *mutationResolver) SceneMerge(ctx context.Context, input SceneMergeInput) (*models.Scene, error) { srcIDs, err := stringslice.StringSliceToIntSlice(input.Source) if err != nil { - return nil, fmt.Errorf("converting source IDs: %w", err) + return nil, fmt.Errorf("converting source ids: %w", err) } destID, err := strconv.Atoi(input.Destination) if err != nil { - return nil, fmt.Errorf("converting destination ID %s: %w", input.Destination, err) + return nil, fmt.Errorf("converting destination id: %w", err) } var values *models.ScenePartial @@ -625,7 +573,7 @@ func (r *mutationResolver) SceneMerge(ctx context.Context, input SceneMergeInput var err error coverImageData, err = utils.ProcessImageInput(ctx, *input.Values.CoverImage) if err != nil { - return nil, err + return nil, fmt.Errorf("processing cover image: %w", err) } } @@ -673,15 +621,13 @@ func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input SceneMar return nil, fmt.Errorf("converting primary tag id: %w", err) } - currentTime := time.Now() - newMarker := models.SceneMarker{ - Title: input.Title, - Seconds: input.Seconds, - PrimaryTagID: primaryTagID, - SceneID: sceneID, - CreatedAt: currentTime, - UpdatedAt: currentTime, - } + // Populate a new scene marker from the input + newMarker := models.NewSceneMarker() + + newMarker.Title = input.Title + newMarker.Seconds = input.Seconds + newMarker.PrimaryTagID = primaryTagID + newMarker.SceneID = sceneID tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds) if err != nil { @@ -711,7 +657,7 @@ func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input SceneMar func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMarkerUpdateInput) (*models.SceneMarker, error) { markerID, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } translator := changesetTranslator{ @@ -809,7 +755,7 @@ func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMar func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (bool, error) { markerID, err := strconv.Atoi(id) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm() @@ -860,7 +806,7 @@ func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (b func (r *mutationResolver) SceneSaveActivity(ctx context.Context, id string, resumeTime *float64, playDuration *float64) (ret bool, err error) { sceneID, err := strconv.Atoi(id) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -878,7 +824,7 @@ func (r *mutationResolver) SceneSaveActivity(ctx context.Context, id string, res func (r *mutationResolver) SceneIncrementPlayCount(ctx context.Context, id string) (ret int, err error) { sceneID, err := strconv.Atoi(id) if err != nil { - return 0, err + return 0, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -896,7 +842,7 @@ func (r *mutationResolver) SceneIncrementPlayCount(ctx context.Context, id strin func (r *mutationResolver) SceneIncrementO(ctx context.Context, id string) (ret int, err error) { sceneID, err := strconv.Atoi(id) if err != nil { - return 0, err + return 0, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -914,7 +860,7 @@ func (r *mutationResolver) SceneIncrementO(ctx context.Context, id string) (ret func (r *mutationResolver) SceneDecrementO(ctx context.Context, id string) (ret int, err error) { sceneID, err := strconv.Atoi(id) if err != nil { - return 0, err + return 0, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -932,7 +878,7 @@ func (r *mutationResolver) SceneDecrementO(ctx context.Context, id string) (ret func (r *mutationResolver) SceneResetO(ctx context.Context, id string) (ret int, err error) { sceneID, err := strconv.Atoi(id) if err != nil { - return 0, err + return 0, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_mutation_stash_box.go b/internal/api/resolver_mutation_stash_box.go index cbcfc53401b..2f8593097f9 100644 --- a/internal/api/resolver_mutation_stash_box.go +++ b/internal/api/resolver_mutation_stash_box.go @@ -53,7 +53,7 @@ func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input S id, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } var res *string @@ -95,7 +95,7 @@ func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, inp id, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } var res *string diff --git a/internal/api/resolver_mutation_studio.go b/internal/api/resolver_mutation_studio.go index 626e0d4f481..db314d26109 100644 --- a/internal/api/resolver_mutation_studio.go +++ b/internal/api/resolver_mutation_studio.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "strconv" - "time" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/plugin" @@ -13,19 +12,48 @@ import ( "github.com/stashapp/stash/pkg/utils" ) -func (r *mutationResolver) StudioCreate(ctx context.Context, input StudioCreateInput) (*models.Studio, error) { - s, err := studioFromStudioCreateInput(ctx, input) - if err != nil { +// used to refetch studio after hooks run +func (r *mutationResolver) getStudio(ctx context.Context, id int) (ret *models.Studio, err error) { + if err := r.withTxn(ctx, func(ctx context.Context) error { + ret, err = r.repository.Studio.Find(ctx, id) + return err + }); err != nil { return nil, err } + return ret, nil +} + +func (r *mutationResolver) StudioCreate(ctx context.Context, input models.StudioCreateInput) (*models.Studio, error) { + translator := changesetTranslator{ + inputMap: getUpdateInputMap(ctx), + } + + // Populate a new studio from the input + newStudio := models.NewStudio() + + newStudio.Name = input.Name + newStudio.URL = translator.string(input.URL) + newStudio.Rating = translator.ratingConversion(input.Rating, input.Rating100) + newStudio.Details = translator.string(input.Details) + newStudio.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag) + newStudio.Aliases = models.NewRelatedStrings(input.Aliases) + newStudio.StashIDs = models.NewRelatedStashIDs(input.StashIds) + + var err error + + newStudio.ParentID, err = translator.intPtrFromString(input.ParentID) + if err != nil { + return nil, fmt.Errorf("converting parent id: %w", err) + } + // Process the base 64 encoded image string var imageData []byte if input.Image != nil { var err error imageData, err = utils.ProcessImageInput(ctx, *input.Image) if err != nil { - return nil, err + return nil, fmt.Errorf("processing image: %w", err) } } @@ -33,19 +61,19 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input StudioCreateI if err := r.withTxn(ctx, func(ctx context.Context) error { qb := r.repository.Studio - if s.Aliases.Loaded() && len(s.Aliases.List()) > 0 { - if err := studio.EnsureAliasesUnique(ctx, 0, s.Aliases.List(), qb); err != nil { + if len(input.Aliases) > 0 { + if err := studio.EnsureAliasesUnique(ctx, 0, input.Aliases, qb); err != nil { return err } } - err = qb.Create(ctx, s) + err = qb.Create(ctx, &newStudio) if err != nil { return err } if len(imageData) > 0 { - if err := qb.UpdateImage(ctx, s.ID, imageData); err != nil { + if err := qb.UpdateImage(ctx, newStudio.ID, imageData); err != nil { return err } } @@ -55,53 +83,37 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input StudioCreateI return nil, err } - r.hookExecutor.ExecutePostHooks(ctx, s.ID, plugin.StudioCreatePost, input, nil) - - return s, nil + r.hookExecutor.ExecutePostHooks(ctx, newStudio.ID, plugin.StudioCreatePost, input, nil) + return r.getStudio(ctx, newStudio.ID) } -func studioFromStudioCreateInput(ctx context.Context, input StudioCreateInput) (*models.Studio, error) { - translator := changesetTranslator{ - inputMap: getUpdateInputMap(ctx), +func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.StudioUpdateInput) (*models.Studio, error) { + studioID, err := strconv.Atoi(input.ID) + if err != nil { + return nil, fmt.Errorf("converting id: %w", err) } - // Populate a new studio from the input - currentTime := time.Now() - newStudio := models.Studio{ - Name: input.Name, - CreatedAt: currentTime, - UpdatedAt: currentTime, - URL: translator.string(input.URL, "url"), - Rating: translator.ratingConversionInt(input.Rating, input.Rating100), - Details: translator.string(input.Details, "details"), - IgnoreAutoTag: translator.bool(input.IgnoreAutoTag, "ignore_auto_tag"), + translator := changesetTranslator{ + inputMap: getUpdateInputMap(ctx), } - var err error - newStudio.ParentID, err = translator.intPtrFromString(input.ParentID, "parent_id") + // Populate studio from the input + updatedStudio := models.NewStudioPartial() + + updatedStudio.ID = studioID + updatedStudio.Name = translator.optionalString(input.Name, "name") + updatedStudio.URL = translator.optionalString(input.URL, "url") + updatedStudio.Details = translator.optionalString(input.Details, "details") + updatedStudio.Rating = translator.optionalRatingConversion(input.Rating, input.Rating100) + updatedStudio.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag") + updatedStudio.Aliases = translator.updateStrings(input.Aliases, "aliases") + updatedStudio.StashIDs = translator.updateStashIDs(input.StashIds, "stash_ids") + + updatedStudio.ParentID, err = translator.optionalIntFromString(input.ParentID, "parent_id") if err != nil { return nil, fmt.Errorf("converting parent id: %w", err) } - if input.Aliases != nil { - newStudio.Aliases = models.NewRelatedStrings(input.Aliases) - } - if input.StashIds != nil { - newStudio.StashIDs = models.NewRelatedStashIDs(stashIDPtrSliceToSlice(input.StashIds)) - } - - return &newStudio, nil -} - -func (r *mutationResolver) StudioUpdate(ctx context.Context, input StudioUpdateInput) (*models.Studio, error) { - var updatedStudio *models.Studio - var err error - - translator := changesetTranslator{ - inputMap: getNamedUpdateInputMap(ctx, updateInputField), - } - s := studioPartialFromStudioUpdateInput(input, &input.ID, translator) - // Process the base 64 encoded image string var imageData []byte imageIncluded := translator.hasField("image") @@ -109,7 +121,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input StudioUpdateI var err error imageData, err = utils.ProcessImageInput(ctx, *input.Image) if err != nil { - return nil, err + return nil, fmt.Errorf("processing image: %w", err) } } @@ -117,17 +129,17 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input StudioUpdateI if err := r.withTxn(ctx, func(ctx context.Context) error { qb := r.repository.Studio - if err := studio.ValidateModify(ctx, *s, qb); err != nil { + if err := studio.ValidateModify(ctx, updatedStudio, qb); err != nil { return err } - updatedStudio, err = qb.UpdatePartial(ctx, *s) + _, err = qb.UpdatePartial(ctx, updatedStudio) if err != nil { return err } if imageIncluded { - if err := qb.UpdateImage(ctx, s.ID, imageData); err != nil { + if err := qb.UpdateImage(ctx, studioID, imageData); err != nil { return err } } @@ -137,57 +149,14 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input StudioUpdateI return nil, err } - r.hookExecutor.ExecutePostHooks(ctx, updatedStudio.ID, plugin.StudioUpdatePost, input, translator.getFields()) - - return updatedStudio, nil -} - -// This is slightly different to studioPartialFromStudioCreateInput in that Name is handled differently -// and ImageIncluded is not hardcoded to true -func studioPartialFromStudioUpdateInput(input StudioUpdateInput, id *string, translator changesetTranslator) *models.StudioPartial { - // Populate studio from the input - updatedStudio := models.StudioPartial{ - Name: translator.optionalString(input.Name, "name"), - URL: translator.optionalString(input.URL, "url"), - Details: translator.optionalString(input.Details, "details"), - Rating: translator.ratingConversionOptional(input.Rating, input.Rating100), - IgnoreAutoTag: translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag"), - UpdatedAt: models.NewOptionalTime(time.Now()), - } - - updatedStudio.ID, _ = strconv.Atoi(*id) - - if input.ParentID != nil { - parentID, _ := strconv.Atoi(*input.ParentID) - if parentID > 0 { - // This is to be set directly as we know it has a value and the translator won't have the field - updatedStudio.ParentID = models.NewOptionalInt(parentID) - } - } else { - updatedStudio.ParentID = translator.optionalInt(nil, "parent_id") - } - - if translator.hasField("aliases") { - updatedStudio.Aliases = &models.UpdateStrings{ - Values: input.Aliases, - Mode: models.RelationshipUpdateModeSet, - } - } - - if translator.hasField("stash_ids") { - updatedStudio.StashIDs = &models.UpdateStashIDs{ - StashIDs: stashIDPtrSliceToSlice(input.StashIds), - Mode: models.RelationshipUpdateModeSet, - } - } - - return &updatedStudio + r.hookExecutor.ExecutePostHooks(ctx, studioID, plugin.StudioUpdatePost, input, translator.getFields()) + return r.getStudio(ctx, studioID) } func (r *mutationResolver) StudioDestroy(ctx context.Context, input StudioDestroyInput) (bool, error) { id, err := strconv.Atoi(input.ID) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -204,7 +173,7 @@ func (r *mutationResolver) StudioDestroy(ctx context.Context, input StudioDestro func (r *mutationResolver) StudiosDestroy(ctx context.Context, studioIDs []string) (bool, error) { ids, err := stringslice.StringSliceToIntSlice(studioIDs) if err != nil { - return false, err + return false, fmt.Errorf("converting ids: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { diff --git a/internal/api/resolver_mutation_tag.go b/internal/api/resolver_mutation_tag.go index 51c9fa7ab26..cec4a77726c 100644 --- a/internal/api/resolver_mutation_tag.go +++ b/internal/api/resolver_mutation_tag.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "strconv" - "time" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" @@ -31,14 +30,11 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput) } // Populate a new tag from the input - currentTime := time.Now() - newTag := models.Tag{ - Name: input.Name, - CreatedAt: currentTime, - UpdatedAt: currentTime, - Description: translator.string(input.Description, "description"), - IgnoreAutoTag: translator.bool(input.IgnoreAutoTag, "ignore_auto_tag"), - } + newTag := models.NewTag() + + newTag.Name = input.Name + newTag.Description = translator.string(input.Description) + newTag.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag) var err error @@ -46,7 +42,7 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput) if len(input.ParentIds) > 0 { parentIDs, err = stringslice.StringSliceToIntSlice(input.ParentIds) if err != nil { - return nil, err + return nil, fmt.Errorf("converting parent ids: %w", err) } } @@ -54,7 +50,7 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput) if len(input.ChildIds) > 0 { childIDs, err = stringslice.StringSliceToIntSlice(input.ChildIds) if err != nil { - return nil, err + return nil, fmt.Errorf("converting child ids: %w", err) } } @@ -63,7 +59,7 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput) if input.Image != nil { imageData, err = utils.ProcessImageInput(ctx, *input.Image) if err != nil { - return nil, err + return nil, fmt.Errorf("processing image: %w", err) } } @@ -130,7 +126,7 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput) func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput) (*models.Tag, error) { tagID, err := strconv.Atoi(input.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("converting id: %w", err) } translator := changesetTranslator{ @@ -147,7 +143,7 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput) if translator.hasField("parent_ids") { parentIDs, err = stringslice.StringSliceToIntSlice(input.ParentIds) if err != nil { - return nil, err + return nil, fmt.Errorf("converting parent ids: %w", err) } } @@ -155,7 +151,7 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput) if translator.hasField("child_ids") { childIDs, err = stringslice.StringSliceToIntSlice(input.ChildIds) if err != nil { - return nil, err + return nil, fmt.Errorf("converting child ids: %w", err) } } @@ -164,7 +160,7 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput) if input.Image != nil { imageData, err = utils.ProcessImageInput(ctx, *input.Image) if err != nil { - return nil, err + return nil, fmt.Errorf("processing image: %w", err) } } @@ -246,7 +242,7 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput) func (r *mutationResolver) TagDestroy(ctx context.Context, input TagDestroyInput) (bool, error) { tagID, err := strconv.Atoi(input.ID) if err != nil { - return false, err + return false, fmt.Errorf("converting id: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -263,7 +259,7 @@ func (r *mutationResolver) TagDestroy(ctx context.Context, input TagDestroyInput func (r *mutationResolver) TagsDestroy(ctx context.Context, tagIDs []string) (bool, error) { ids, err := stringslice.StringSliceToIntSlice(tagIDs) if err != nil { - return false, err + return false, fmt.Errorf("converting ids: %w", err) } if err := r.withTxn(ctx, func(ctx context.Context) error { @@ -289,12 +285,12 @@ func (r *mutationResolver) TagsDestroy(ctx context.Context, tagIDs []string) (bo func (r *mutationResolver) TagsMerge(ctx context.Context, input TagsMergeInput) (*models.Tag, error) { source, err := stringslice.StringSliceToIntSlice(input.Source) if err != nil { - return nil, err + return nil, fmt.Errorf("converting source ids: %w", err) } destination, err := strconv.Atoi(input.Destination) if err != nil { - return nil, err + return nil, fmt.Errorf("converting destination id: %w", err) } if len(source) == 0 { @@ -345,5 +341,6 @@ func (r *mutationResolver) TagsMerge(ctx context.Context, input TagsMergeInput) } r.hookExecutor.ExecutePostHooks(ctx, t.ID, plugin.TagMergePost, input, nil) + return t, nil } diff --git a/internal/api/types.go b/internal/api/types.go index 79b4aa02002..372c094b8b4 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -1,11 +1,9 @@ package api import ( - "fmt" "math" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) // #1572 - Inf and NaN values cause the JSON marshaller to fail @@ -18,32 +16,12 @@ func handleFloat64(v float64) *float64 { return &v } -func translateUpdateIDs(strIDs []string, mode models.RelationshipUpdateMode) (*models.UpdateIDs, error) { - ids, err := stringslice.StringSliceToIntSlice(strIDs) - if err != nil { - return nil, fmt.Errorf("converting ids [%v]: %w", strIDs, err) - } - return &models.UpdateIDs{ - IDs: ids, - Mode: mode, - }, nil -} - -func translateSceneMovieIDs(input BulkUpdateIds) (*models.UpdateMovieIDs, error) { - ids, err := stringslice.StringSliceToIntSlice(input.Ids) - if err != nil { - return nil, fmt.Errorf("converting ids [%v]: %w", input.Ids, err) - } - - ret := &models.UpdateMovieIDs{ - Mode: input.Mode, - } - - for _, id := range ids { - ret.Movies = append(ret.Movies, models.MoviesScenes{ - MovieID: id, - }) +func stashIDsSliceToPtrSlice(v []models.StashID) []*models.StashID { + ret := make([]*models.StashID, len(v)) + for i, vv := range v { + c := vv + ret[i] = &c } - return ret, nil + return ret } diff --git a/internal/autotag/gallery_test.go b/internal/autotag/gallery_test.go index b617791abea..23c3d931ee6 100644 --- a/internal/autotag/gallery_test.go +++ b/internal/autotag/gallery_test.go @@ -14,6 +14,19 @@ const galleryExt = "zip" var testCtx = context.Background() +// returns got == expected +// ignores expected.UpdatedAt, but ensures that got.UpdatedAt is set and not null +func galleryPartialsEqual(got, expected models.GalleryPartial) bool { + // updated at should be set and not null + if !got.UpdatedAt.Set || got.UpdatedAt.Null { + return false + } + // else ignore the exact value + got.UpdatedAt = models.OptionalTime{} + + return assert.ObjectsAreEqual(got, expected) +} + func TestGalleryPerformers(t *testing.T) { t.Parallel() @@ -46,12 +59,17 @@ func TestGalleryPerformers(t *testing.T) { mockPerformerReader.On("QueryForAutoTag", testCtx, mock.Anything).Return([]*models.Performer{&performer, &reversedPerformer}, nil).Once() if test.Matches { - mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.GalleryPartial) bool { + expected := models.GalleryPartial{ + PerformerIDs: &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return galleryPartialsEqual(got, expected) + }) + mockGalleryReader.On("UpdatePartial", testCtx, galleryID, matchPartial).Return(nil, nil).Once() } gallery := models.Gallery{ @@ -91,10 +109,14 @@ func TestGalleryStudios(t *testing.T) { doTest := func(mockStudioReader *mocks.StudioReaderWriter, mockGalleryReader *mocks.GalleryReaderWriter, test pathTestTable) { if test.Matches { - expectedStudioID := studioID - mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{ - StudioID: models.NewOptionalInt(expectedStudioID), - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.GalleryPartial) bool { + expected := models.GalleryPartial{ + StudioID: models.NewOptionalInt(studioID), + } + + return galleryPartialsEqual(got, expected) + }) + mockGalleryReader.On("UpdatePartial", testCtx, galleryID, matchPartial).Return(nil, nil).Once() } gallery := models.Gallery{ @@ -162,12 +184,17 @@ func TestGalleryTags(t *testing.T) { doTest := func(mockTagReader *mocks.TagReaderWriter, mockGalleryReader *mocks.GalleryReaderWriter, test pathTestTable) { if test.Matches { - mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.GalleryPartial) bool { + expected := models.GalleryPartial{ + TagIDs: &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return galleryPartialsEqual(got, expected) + }) + mockGalleryReader.On("UpdatePartial", testCtx, galleryID, matchPartial).Return(nil, nil).Once() } gallery := models.Gallery{ diff --git a/internal/autotag/image_test.go b/internal/autotag/image_test.go index 3ced047f7e2..06991beea1f 100644 --- a/internal/autotag/image_test.go +++ b/internal/autotag/image_test.go @@ -11,6 +11,19 @@ import ( const imageExt = "jpg" +// returns got == expected +// ignores expected.UpdatedAt, but ensures that got.UpdatedAt is set and not null +func imagePartialsEqual(got, expected models.ImagePartial) bool { + // updated at should be set and not null + if !got.UpdatedAt.Set || got.UpdatedAt.Null { + return false + } + // else ignore the exact value + got.UpdatedAt = models.OptionalTime{} + + return assert.ObjectsAreEqual(got, expected) +} + func TestImagePerformers(t *testing.T) { t.Parallel() @@ -43,12 +56,17 @@ func TestImagePerformers(t *testing.T) { mockPerformerReader.On("QueryForAutoTag", testCtx, mock.Anything).Return([]*models.Performer{&performer, &reversedPerformer}, nil).Once() if test.Matches { - mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.ImagePartial) bool { + expected := models.ImagePartial{ + PerformerIDs: &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return imagePartialsEqual(got, expected) + }) + mockImageReader.On("UpdatePartial", testCtx, imageID, matchPartial).Return(nil, nil).Once() } image := models.Image{ @@ -88,10 +106,14 @@ func TestImageStudios(t *testing.T) { doTest := func(mockStudioReader *mocks.StudioReaderWriter, mockImageReader *mocks.ImageReaderWriter, test pathTestTable) { if test.Matches { - expectedStudioID := studioID - mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{ - StudioID: models.NewOptionalInt(expectedStudioID), - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.ImagePartial) bool { + expected := models.ImagePartial{ + StudioID: models.NewOptionalInt(studioID), + } + + return imagePartialsEqual(got, expected) + }) + mockImageReader.On("UpdatePartial", testCtx, imageID, matchPartial).Return(nil, nil).Once() } image := models.Image{ @@ -159,12 +181,17 @@ func TestImageTags(t *testing.T) { doTest := func(mockTagReader *mocks.TagReaderWriter, mockImageReader *mocks.ImageReaderWriter, test pathTestTable) { if test.Matches { - mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.ImagePartial) bool { + expected := models.ImagePartial{ + TagIDs: &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return imagePartialsEqual(got, expected) + }) + mockImageReader.On("UpdatePartial", testCtx, imageID, matchPartial).Return(nil, nil).Once() } image := models.Image{ diff --git a/internal/autotag/integration_test.go b/internal/autotag/integration_test.go index 774a7738bab..84ae016987c 100644 --- a/internal/autotag/integration_test.go +++ b/internal/autotag/integration_test.go @@ -362,10 +362,7 @@ func makeImage(expectedResult bool) *models.Image { } func createImage(ctx context.Context, w models.ImageWriter, o *models.Image, f *models.ImageFile) error { - err := w.Create(ctx, &models.ImageCreateInput{ - Image: o, - FileIDs: []models.FileID{f.ID}, - }) + err := w.Create(ctx, o, []models.FileID{f.ID}) if err != nil { return fmt.Errorf("Failed to create image with path '%s': %s", f.Path, err.Error()) diff --git a/internal/autotag/performer_test.go b/internal/autotag/performer_test.go index 5f7b12c228d..aa0a43d92f8 100644 --- a/internal/autotag/performer_test.go +++ b/internal/autotag/performer_test.go @@ -89,12 +89,18 @@ func testPerformerScenes(t *testing.T, performerName, expectedRegex string) { for i := range matchingPaths { sceneID := i + 1 - mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + + matchPartial := mock.MatchedBy(func(got models.ScenePartial) bool { + expected := models.ScenePartial{ + PerformerIDs: &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return scenePartialsEqual(got, expected) + }) + mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, matchPartial).Return(nil, nil).Once() } tagger := Tagger{ @@ -178,12 +184,18 @@ func testPerformerImages(t *testing.T, performerName, expectedRegex string) { for i := range matchingPaths { imageID := i + 1 - mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + + matchPartial := mock.MatchedBy(func(got models.ImagePartial) bool { + expected := models.ImagePartial{ + PerformerIDs: &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return imagePartialsEqual(got, expected) + }) + mockImageReader.On("UpdatePartial", mock.Anything, imageID, matchPartial).Return(nil, nil).Once() } tagger := Tagger{ @@ -267,12 +279,18 @@ func testPerformerGalleries(t *testing.T, performerName, expectedRegex string) { for i := range matchingPaths { galleryID := i + 1 - mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + + matchPartial := mock.MatchedBy(func(got models.GalleryPartial) bool { + expected := models.GalleryPartial{ + PerformerIDs: &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return galleryPartialsEqual(got, expected) + }) + mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, matchPartial).Return(nil, nil).Once() } tagger := Tagger{ diff --git a/internal/autotag/scene_test.go b/internal/autotag/scene_test.go index 19ae15c9cce..a714c364c41 100644 --- a/internal/autotag/scene_test.go +++ b/internal/autotag/scene_test.go @@ -29,6 +29,19 @@ var testEndSeparators = []string{ ",", } +// asserts that got == expected +// ignores expected.UpdatedAt, but ensures that got.UpdatedAt is set and not null +func scenePartialsEqual(got, expected models.ScenePartial) bool { + // updated at should be set and not null + if !got.UpdatedAt.Set || got.UpdatedAt.Null { + return false + } + // else ignore the exact value + got.UpdatedAt = models.OptionalTime{} + + return assert.ObjectsAreEqual(got, expected) +} + func generateNamePatterns(name, separator, ext string) []string { var ret []string ret = append(ret, fmt.Sprintf("%s%saaa.%s", name, separator, ext)) @@ -182,12 +195,17 @@ func TestScenePerformers(t *testing.T) { } if test.Matches { - mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.ScenePartial) bool { + expected := models.ScenePartial{ + PerformerIDs: &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return scenePartialsEqual(got, expected) + }) + mockSceneReader.On("UpdatePartial", testCtx, sceneID, matchPartial).Return(nil, nil).Once() } err := ScenePerformers(testCtx, &scene, mockSceneReader, mockPerformerReader, nil) @@ -224,10 +242,14 @@ func TestSceneStudios(t *testing.T) { doTest := func(mockStudioReader *mocks.StudioReaderWriter, mockSceneReader *mocks.SceneReaderWriter, test pathTestTable) { if test.Matches { - expectedStudioID := studioID - mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{ - StudioID: models.NewOptionalInt(expectedStudioID), - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.ScenePartial) bool { + expected := models.ScenePartial{ + StudioID: models.NewOptionalInt(studioID), + } + + return scenePartialsEqual(got, expected) + }) + mockSceneReader.On("UpdatePartial", testCtx, sceneID, matchPartial).Return(nil, nil).Once() } scene := models.Scene{ @@ -295,12 +317,17 @@ func TestSceneTags(t *testing.T) { doTest := func(mockTagReader *mocks.TagReaderWriter, mockSceneReader *mocks.SceneReaderWriter, test pathTestTable) { if test.Matches { - mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.ScenePartial) bool { + expected := models.ScenePartial{ + TagIDs: &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return scenePartialsEqual(got, expected) + }) + mockSceneReader.On("UpdatePartial", testCtx, sceneID, matchPartial).Return(nil, nil).Once() } scene := models.Scene{ diff --git a/internal/autotag/studio.go b/internal/autotag/studio.go index ef5a6f0da9c..8312e0edf61 100644 --- a/internal/autotag/studio.go +++ b/internal/autotag/studio.go @@ -18,9 +18,8 @@ func addSceneStudio(ctx context.Context, sceneWriter models.SceneUpdater, o *mod } // set the studio id - scenePartial := models.ScenePartial{ - StudioID: models.NewOptionalInt(studioID), - } + scenePartial := models.NewScenePartial() + scenePartial.StudioID = models.NewOptionalInt(studioID) if _, err := sceneWriter.UpdatePartial(ctx, o.ID, scenePartial); err != nil { return false, err @@ -35,9 +34,8 @@ func addImageStudio(ctx context.Context, imageWriter models.ImageUpdater, i *mod } // set the studio id - imagePartial := models.ImagePartial{ - StudioID: models.NewOptionalInt(studioID), - } + imagePartial := models.NewImagePartial() + imagePartial.StudioID = models.NewOptionalInt(studioID) if _, err := imageWriter.UpdatePartial(ctx, i.ID, imagePartial); err != nil { return false, err @@ -52,9 +50,8 @@ func addGalleryStudio(ctx context.Context, galleryWriter GalleryFinderUpdater, o } // set the studio id - galleryPartial := models.GalleryPartial{ - StudioID: models.NewOptionalInt(studioID), - } + galleryPartial := models.NewGalleryPartial() + galleryPartial.StudioID = models.NewOptionalInt(studioID) if _, err := galleryWriter.UpdatePartial(ctx, o.ID, galleryPartial); err != nil { return false, err @@ -93,9 +90,8 @@ func (tagger *Tagger) StudioScenes(ctx context.Context, p *models.Studio, paths } // set the studio id - scenePartial := models.ScenePartial{ - StudioID: models.NewOptionalInt(p.ID), - } + scenePartial := models.NewScenePartial() + scenePartial.StudioID = models.NewOptionalInt(p.ID) if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error { _, err := rw.UpdatePartial(ctx, o.ID, scenePartial) @@ -124,9 +120,8 @@ func (tagger *Tagger) StudioImages(ctx context.Context, p *models.Studio, paths } // set the studio id - imagePartial := models.ImagePartial{ - StudioID: models.NewOptionalInt(p.ID), - } + imagePartial := models.NewImagePartial() + imagePartial.StudioID = models.NewOptionalInt(p.ID) if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error { _, err := rw.UpdatePartial(ctx, i.ID, imagePartial) @@ -155,9 +150,8 @@ func (tagger *Tagger) StudioGalleries(ctx context.Context, p *models.Studio, pat } // set the studio id - galleryPartial := models.GalleryPartial{ - StudioID: models.NewOptionalInt(p.ID), - } + galleryPartial := models.NewGalleryPartial() + galleryPartial.StudioID = models.NewOptionalInt(p.ID) if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error { _, err := rw.UpdatePartial(ctx, o.ID, galleryPartial) diff --git a/internal/autotag/studio_test.go b/internal/autotag/studio_test.go index 3e9eae5f5fb..aa52c9c5179 100644 --- a/internal/autotag/studio_test.go +++ b/internal/autotag/studio_test.go @@ -151,10 +151,15 @@ func testStudioScenes(t *testing.T, tc testStudioCase) { for i := range matchingPaths { sceneID := i + 1 - expectedStudioID := studioID - mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{ - StudioID: models.NewOptionalInt(expectedStudioID), - }).Return(nil, nil).Once() + + matchPartial := mock.MatchedBy(func(got models.ScenePartial) bool { + expected := models.ScenePartial{ + StudioID: models.NewOptionalInt(studioID), + } + + return scenePartialsEqual(got, expected) + }) + mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, matchPartial).Return(nil, nil).Once() } tagger := Tagger{ @@ -249,10 +254,15 @@ func testStudioImages(t *testing.T, tc testStudioCase) { for i := range matchingPaths { imageID := i + 1 - expectedStudioID := studioID - mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{ - StudioID: models.NewOptionalInt(expectedStudioID), - }).Return(nil, nil).Once() + + matchPartial := mock.MatchedBy(func(got models.ImagePartial) bool { + expected := models.ImagePartial{ + StudioID: models.NewOptionalInt(studioID), + } + + return imagePartialsEqual(got, expected) + }) + mockImageReader.On("UpdatePartial", mock.Anything, imageID, matchPartial).Return(nil, nil).Once() } tagger := Tagger{ @@ -346,10 +356,15 @@ func testStudioGalleries(t *testing.T, tc testStudioCase) { for i := range matchingPaths { galleryID := i + 1 - expectedStudioID := studioID - mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{ - StudioID: models.NewOptionalInt(expectedStudioID), - }).Return(nil, nil).Once() + + matchPartial := mock.MatchedBy(func(got models.GalleryPartial) bool { + expected := models.GalleryPartial{ + StudioID: models.NewOptionalInt(studioID), + } + + return galleryPartialsEqual(got, expected) + }) + mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, matchPartial).Return(nil, nil).Once() } tagger := Tagger{ diff --git a/internal/autotag/tag_test.go b/internal/autotag/tag_test.go index 04f10875c2e..4b183200490 100644 --- a/internal/autotag/tag_test.go +++ b/internal/autotag/tag_test.go @@ -151,12 +151,18 @@ func testTagScenes(t *testing.T, tc testTagCase) { for i := range matchingPaths { sceneID := i + 1 - mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + + matchPartial := mock.MatchedBy(func(got models.ScenePartial) bool { + expected := models.ScenePartial{ + TagIDs: &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return scenePartialsEqual(got, expected) + }) + mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, matchPartial).Return(nil, nil).Once() } tagger := Tagger{ @@ -253,12 +259,17 @@ func testTagImages(t *testing.T, tc testTagCase) { for i := range matchingPaths { imageID := i + 1 - mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.ImagePartial) bool { + expected := models.ImagePartial{ + TagIDs: &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return imagePartialsEqual(got, expected) + }) + mockImageReader.On("UpdatePartial", mock.Anything, imageID, matchPartial).Return(nil, nil).Once() } tagger := Tagger{ @@ -355,12 +366,17 @@ func testTagGalleries(t *testing.T, tc testTagCase) { for i := range matchingPaths { galleryID := i + 1 - mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }).Return(nil, nil).Once() + matchPartial := mock.MatchedBy(func(got models.GalleryPartial) bool { + expected := models.GalleryPartial{ + TagIDs: &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + }, + } + + return galleryPartialsEqual(got, expected) + }) + mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, matchPartial).Return(nil, nil).Once() } diff --git a/internal/identify/scene.go b/internal/identify/scene.go index 9a951c13b18..eec8ce6edc2 100644 --- a/internal/identify/scene.go +++ b/internal/identify/scene.go @@ -7,7 +7,6 @@ import ( "fmt" "strconv" "strings" - "time" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" @@ -164,12 +163,9 @@ func (g sceneRelationships) tags(ctx context.Context) ([]int, error) { tagIDs = intslice.IntAppendUnique(tagIDs, int(tagID)) } else if createMissing { - now := time.Now() - newTag := models.Tag{ - Name: t.Name, - CreatedAt: now, - UpdatedAt: now, - } + newTag := models.NewTag() + newTag.Name = t.Name + err := g.tagCreator.Create(ctx, &newTag) if err != nil { return nil, fmt.Errorf("error creating tag: %w", err) diff --git a/internal/identify/studio.go b/internal/identify/studio.go index c822afa991e..d05967bc4f2 100644 --- a/internal/identify/studio.go +++ b/internal/identify/studio.go @@ -39,7 +39,13 @@ func createMissingStudio(ctx context.Context, endpoint string, w models.StudioRe s.Parent.StoredID = &storedId } else { // The parent studio matched an existing one and the user has chosen in the UI to link and/or update it - existingStashIDs := getStashIDsForStudio(ctx, *s.Parent.StoredID, w) + storedID, _ := strconv.Atoi(*s.Parent.StoredID) + + existingStashIDs, err := w.GetStashIDs(ctx, storedID) + if err != nil { + return nil, err + } + studioPartial := s.Parent.ToPartial(s.Parent.StoredID, endpoint, nil, existingStashIDs) parentImage, err := s.Parent.GetImage(ctx, nil) if err != nil { @@ -83,14 +89,3 @@ func createMissingStudio(ctx context.Context, endpoint string, w models.StudioRe return &newStudio.ID, nil } - -func getStashIDsForStudio(ctx context.Context, studioID string, w models.StudioReaderWriter) []models.StashID { - id, _ := strconv.Atoi(studioID) - tempStudio := &models.Studio{ID: id} - - err := tempStudio.LoadStashIDs(ctx, w) - if err != nil { - return nil - } - return tempStudio.StashIDs.List() -} diff --git a/internal/manager/task_clean.go b/internal/manager/task_clean.go index f5c3e1d547b..207c6381866 100644 --- a/internal/manager/task_clean.go +++ b/internal/manager/task_clean.go @@ -321,9 +321,10 @@ func (h *cleanHandler) handleRelatedScenes(ctx context.Context, fileDeleter *fil } } - if _, err := mgr.Repository.Scene.UpdatePartial(ctx, scene.ID, models.ScenePartial{ - PrimaryFileID: &newPrimaryID, - }); err != nil { + scenePartial := models.NewScenePartial() + scenePartial.PrimaryFileID = &newPrimaryID + + if _, err := mgr.Repository.Scene.UpdatePartial(ctx, scene.ID, scenePartial); err != nil { return err } } @@ -366,9 +367,10 @@ func (h *cleanHandler) handleRelatedGalleries(ctx context.Context, fileID models } } - if _, err := mgr.Repository.Gallery.UpdatePartial(ctx, g.ID, models.GalleryPartial{ - PrimaryFileID: &newPrimaryID, - }); err != nil { + galleryPartial := models.NewGalleryPartial() + galleryPartial.PrimaryFileID = &newPrimaryID + + if _, err := mgr.Repository.Gallery.UpdatePartial(ctx, g.ID, galleryPartial); err != nil { return err } } @@ -439,9 +441,10 @@ func (h *cleanHandler) handleRelatedImages(ctx context.Context, fileDeleter *fil } } - if _, err := mgr.Repository.Image.UpdatePartial(ctx, i.ID, models.ImagePartial{ - PrimaryFileID: &newPrimaryID, - }); err != nil { + imagePartial := models.NewImagePartial() + imagePartial.PrimaryFileID = &newPrimaryID + + if _, err := mgr.Repository.Image.UpdatePartial(ctx, i.ID, imagePartial); err != nil { return err } } diff --git a/internal/manager/task_generate_screenshot.go b/internal/manager/task_generate_screenshot.go index 384d8740c7b..1050ebd1c05 100644 --- a/internal/manager/task_generate_screenshot.go +++ b/internal/manager/task_generate_screenshot.go @@ -72,7 +72,7 @@ func (t *GenerateCoverTask) Start(ctx context.Context) { if err := t.txnManager.WithTxn(ctx, func(ctx context.Context) error { qb := t.txnManager.Scene - updatedScene := models.NewScenePartial() + scenePartial := models.NewScenePartial() // update the scene cover table if err := qb.UpdateCover(ctx, t.Scene.ID, coverImageData); err != nil { @@ -80,7 +80,7 @@ func (t *GenerateCoverTask) Start(ctx context.Context) { } // update the scene with the update date - _, err = qb.UpdatePartial(ctx, t.Scene.ID, updatedScene) + _, err = qb.UpdatePartial(ctx, t.Scene.ID, scenePartial) if err != nil { return fmt.Errorf("error updating scene: %v", err) } diff --git a/internal/manager/task_stash_box_tag.go b/internal/manager/task_stash_box_tag.go index 866c8205cb9..6833f166343 100644 --- a/internal/manager/task_stash_box_tag.go +++ b/internal/manager/task_stash_box_tag.go @@ -138,9 +138,6 @@ func (t *StashBoxBatchTagTask) processMatchedPerformer(ctx context.Context, p *m if t.performer != nil { storedID, _ := strconv.Atoi(*p.StoredID) - existingStashIDs := getStashIDsForPerformer(ctx, storedID) - partial := p.ToPartial(t.box.Endpoint, excluded, existingStashIDs) - image, err := p.GetImage(ctx, excluded) if err != nil { logger.Errorf("Error processing scraped performer image for %s: %v", *p.Name, err) @@ -151,6 +148,13 @@ func (t *StashBoxBatchTagTask) processMatchedPerformer(ctx context.Context, p *m err = txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error { qb := instance.Repository.Performer + existingStashIDs, err := qb.GetStashIDs(ctx, storedID) + if err != nil { + return err + } + + partial := p.ToPartial(t.box.Endpoint, excluded, existingStashIDs) + if _, err := qb.UpdatePartial(ctx, t.performer.ID, partial); err != nil { return err } @@ -199,16 +203,6 @@ func (t *StashBoxBatchTagTask) processMatchedPerformer(ctx context.Context, p *m } } -func getStashIDsForPerformer(ctx context.Context, performerID int) []models.StashID { - tempPerformer := &models.Performer{ID: performerID} - - err := tempPerformer.LoadStashIDs(ctx, instance.Repository.Performer) - if err != nil { - return nil - } - return tempPerformer.StashIDs.List() -} - func (t *StashBoxBatchTagTask) stashBoxStudioTag(ctx context.Context) { studio, err := t.findStashBoxStudio(ctx) if err != nil { @@ -292,9 +286,6 @@ func (t *StashBoxBatchTagTask) processMatchedStudio(ctx context.Context, s *mode } } - existingStashIDs := getStashIDsForStudio(ctx, storedID) - partial := s.ToPartial(s.StoredID, t.box.Endpoint, excluded, existingStashIDs) - image, err := s.GetImage(ctx, excluded) if err != nil { logger.Errorf("Error processing scraped studio image for %s: %v", s.Name, err) @@ -305,6 +296,13 @@ func (t *StashBoxBatchTagTask) processMatchedStudio(ctx context.Context, s *mode err = txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error { qb := instance.Repository.Studio + existingStashIDs, err := qb.GetStashIDs(ctx, storedID) + if err != nil { + return err + } + + partial := s.ToPartial(s.StoredID, t.box.Endpoint, excluded, existingStashIDs) + if err := studio.ValidateModify(ctx, *partial, qb); err != nil { return err } @@ -400,11 +398,8 @@ func (t *StashBoxBatchTagTask) processParentStudio(ctx context.Context, parent * } return err } else { - storedID, _ := strconv.Atoi(*parent.StoredID) - // The parent studio matched an existing one and the user has chosen in the UI to link and/or update it - existingStashIDs := getStashIDsForStudio(ctx, storedID) - partial := parent.ToPartial(parent.StoredID, t.box.Endpoint, excluded, existingStashIDs) + storedID, _ := strconv.Atoi(*parent.StoredID) image, err := parent.GetImage(ctx, excluded) if err != nil { @@ -416,7 +411,14 @@ func (t *StashBoxBatchTagTask) processParentStudio(ctx context.Context, parent * err = txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error { qb := instance.Repository.Studio - if err := studio.ValidateModify(ctx, *partial, instance.Repository.Studio); err != nil { + existingStashIDs, err := qb.GetStashIDs(ctx, storedID) + if err != nil { + return err + } + + partial := parent.ToPartial(parent.StoredID, t.box.Endpoint, excluded, existingStashIDs) + + if err := studio.ValidateModify(ctx, *partial, qb); err != nil { return err } @@ -440,13 +442,3 @@ func (t *StashBoxBatchTagTask) processParentStudio(ctx context.Context, parent * return err } } - -func getStashIDsForStudio(ctx context.Context, studioID int) []models.StashID { - tempStudio := &models.Studio{ID: studioID} - - err := tempStudio.LoadStashIDs(ctx, instance.Repository.Studio) - if err != nil { - return nil - } - return tempStudio.StashIDs.List() -} diff --git a/pkg/gallery/import.go b/pkg/gallery/import.go index 57d151245b5..9c892d3b9a9 100644 --- a/pkg/gallery/import.go +++ b/pkg/gallery/import.go @@ -117,11 +117,10 @@ func (i *Importer) populateStudio(ctx context.Context) error { } func (i *Importer) createStudio(ctx context.Context, name string) (int, error) { - newStudio := &models.Studio{ - Name: name, - } + newStudio := models.NewStudio() + newStudio.Name = name - err := i.StudioWriter.Create(ctx, newStudio) + err := i.StudioWriter.Create(ctx, &newStudio) if err != nil { return 0, err } @@ -177,7 +176,8 @@ func (i *Importer) populatePerformers(ctx context.Context) error { func (i *Importer) createPerformers(ctx context.Context, names []string) ([]*models.Performer, error) { var ret []*models.Performer for _, name := range names { - newPerformer := *models.NewPerformer(name) + newPerformer := models.NewPerformer() + newPerformer.Name = name err := i.PerformerWriter.Create(ctx, &newPerformer) if err != nil { @@ -235,14 +235,15 @@ func (i *Importer) populateTags(ctx context.Context) error { func (i *Importer) createTags(ctx context.Context, names []string) ([]*models.Tag, error) { var ret []*models.Tag for _, name := range names { - newTag := models.NewTag(name) + newTag := models.NewTag() + newTag.Name = name - err := i.TagWriter.Create(ctx, newTag) + err := i.TagWriter.Create(ctx, &newTag) if err != nil { return nil, err } - ret = append(ret, newTag) + ret = append(ret, &newTag) } return ret, nil diff --git a/pkg/gallery/scan.go b/pkg/gallery/scan.go index a8f52e89bb6..f4a9adcc5c5 100644 --- a/pkg/gallery/scan.go +++ b/pkg/gallery/scan.go @@ -5,7 +5,6 @@ import ( "fmt" "path/filepath" "strings" - "time" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" @@ -76,15 +75,11 @@ func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models. } // create a new gallery - now := time.Now() - newGallery := &models.Gallery{ - CreatedAt: now, - UpdatedAt: now, - } + newGallery := models.NewGallery() logger.Infof("%s doesn't exist. Creating new gallery...", f.Base().Path) - if err := h.CreatorUpdater.Create(ctx, newGallery, []models.FileID{baseFile.ID}); err != nil { + if err := h.CreatorUpdater.Create(ctx, &newGallery, []models.FileID{baseFile.ID}); err != nil { return fmt.Errorf("creating new gallery: %w", err) } @@ -92,18 +87,21 @@ func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models. // associate all the images in the zip file with the gallery for _, i := range images { - if _, err := h.ImageFinderUpdater.UpdatePartial(ctx, i.ID, models.ImagePartial{ + imagePartial := models.ImagePartial{ GalleryIDs: &models.UpdateIDs{ IDs: []int{newGallery.ID}, Mode: models.RelationshipUpdateModeAdd, }, - UpdatedAt: models.NewOptionalTime(now), - }); err != nil { + // set UpdatedAt directly instead of using NewImagePartial, to ensure + // that the images have the same UpdatedAt time as the gallery + UpdatedAt: models.NewOptionalTime(newGallery.UpdatedAt), + } + if _, err := h.ImageFinderUpdater.UpdatePartial(ctx, i.ID, imagePartial); err != nil { return fmt.Errorf("adding image %s to gallery: %w", i.Path, err) } } - existing = []*models.Gallery{newGallery} + existing = []*models.Gallery{&newGallery} } if err := h.associateScene(ctx, existing, f); err != nil { diff --git a/pkg/gallery/update.go b/pkg/gallery/update.go index 71d92c5409b..d66da197c81 100644 --- a/pkg/gallery/update.go +++ b/pkg/gallery/update.go @@ -3,7 +3,6 @@ package gallery import ( "context" "fmt" - "time" "github.com/stashapp/stash/pkg/models" ) @@ -15,9 +14,8 @@ type ImageUpdater interface { } func (s *Service) Updated(ctx context.Context, galleryID int) error { - _, err := s.Repository.UpdatePartial(ctx, galleryID, models.GalleryPartial{ - UpdatedAt: models.NewOptionalTime(time.Now()), - }) + galleryPartial := models.NewGalleryPartial() + _, err := s.Repository.UpdatePartial(ctx, galleryID, galleryPartial) return err } @@ -55,21 +53,21 @@ func (s *Service) RemoveImages(ctx context.Context, g *models.Gallery, toRemove } func AddPerformer(ctx context.Context, qb models.GalleryUpdater, o *models.Gallery, performerID int) error { - _, err := qb.UpdatePartial(ctx, o.ID, models.GalleryPartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }) + galleryPartial := models.NewGalleryPartial() + galleryPartial.PerformerIDs = &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + } + _, err := qb.UpdatePartial(ctx, o.ID, galleryPartial) return err } func AddTag(ctx context.Context, qb models.GalleryUpdater, o *models.Gallery, tagID int) error { - _, err := qb.UpdatePartial(ctx, o.ID, models.GalleryPartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }) + galleryPartial := models.NewGalleryPartial() + galleryPartial.TagIDs = &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + } + _, err := qb.UpdatePartial(ctx, o.ID, galleryPartial) return err } diff --git a/pkg/image/import.go b/pkg/image/import.go index 4ce2287eb7b..d5ad591a997 100644 --- a/pkg/image/import.go +++ b/pkg/image/import.go @@ -148,11 +148,10 @@ func (i *Importer) populateStudio(ctx context.Context) error { } func (i *Importer) createStudio(ctx context.Context, name string) (int, error) { - newStudio := &models.Studio{ - Name: name, - } + newStudio := models.NewStudio() + newStudio.Name = name - err := i.StudioWriter.Create(ctx, newStudio) + err := i.StudioWriter.Create(ctx, &newStudio) if err != nil { return 0, err } @@ -261,7 +260,8 @@ func (i *Importer) populatePerformers(ctx context.Context) error { func (i *Importer) createPerformers(ctx context.Context, names []string) ([]*models.Performer, error) { var ret []*models.Performer for _, name := range names { - newPerformer := *models.NewPerformer(name) + newPerformer := models.NewPerformer() + newPerformer.Name = name err := i.PerformerWriter.Create(ctx, &newPerformer) if err != nil { @@ -331,10 +331,7 @@ func (i *Importer) Create(ctx context.Context) (*int, error) { fileIDs = append(fileIDs, f.Base().ID) } - err := i.ReaderWriter.Create(ctx, &models.ImageCreateInput{ - Image: &i.image, - FileIDs: fileIDs, - }) + err := i.ReaderWriter.Create(ctx, &i.image, fileIDs) if err != nil { return nil, fmt.Errorf("error creating image: %v", err) } @@ -394,14 +391,15 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names [] func createTags(ctx context.Context, tagWriter models.TagCreator, names []string) ([]*models.Tag, error) { var ret []*models.Tag for _, name := range names { - newTag := models.NewTag(name) + newTag := models.NewTag() + newTag.Name = name - err := tagWriter.Create(ctx, newTag) + err := tagWriter.Create(ctx, &newTag) if err != nil { return nil, err } - ret = append(ret, newTag) + ret = append(ret, &newTag) } return ret, nil diff --git a/pkg/image/scan.go b/pkg/image/scan.go index d584d0f55fa..9f4aa0d57e9 100644 --- a/pkg/image/scan.go +++ b/pkg/image/scan.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "path/filepath" - "time" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" @@ -27,7 +26,7 @@ type ScanCreatorUpdater interface { GetFiles(ctx context.Context, relatedID int) ([]models.File, error) GetGalleryIDs(ctx context.Context, relatedID int) ([]int, error) - Create(ctx context.Context, newImage *models.ImageCreateInput) error + Create(ctx context.Context, newImage *models.Image, fileIDs []models.FileID) error UpdatePartial(ctx context.Context, id int, updatedImage models.ImagePartial) (*models.Image, error) AddFileID(ctx context.Context, id int, fileID models.FileID) error } @@ -109,16 +108,12 @@ func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models. } } else { // create a new image - now := time.Now() - newImage := &models.Image{ - CreatedAt: now, - UpdatedAt: now, - GalleryIDs: models.NewRelatedIDs([]int{}), - } + newImage := models.NewImage() + newImage.GalleryIDs = models.NewRelatedIDs([]int{}) logger.Infof("%s doesn't exist. Creating new image...", f.Base().Path) - g, err := h.getGalleryToAssociate(ctx, newImage, f) + g, err := h.getGalleryToAssociate(ctx, &newImage, f) if err != nil { return err } @@ -128,25 +123,23 @@ func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models. logger.Infof("Adding %s to gallery %s", f.Base().Path, g.Path) } - if err := h.CreatorUpdater.Create(ctx, &models.ImageCreateInput{ - Image: newImage, - FileIDs: []models.FileID{imageFile.ID}, - }); err != nil { + if err := h.CreatorUpdater.Create(ctx, &newImage, []models.FileID{imageFile.ID}); err != nil { return fmt.Errorf("creating new image: %w", err) } // update the gallery updated at timestamp if applicable if g != nil { - if _, err := h.GalleryFinder.UpdatePartial(ctx, g.ID, models.GalleryPartial{ - UpdatedAt: models.NewOptionalTime(time.Now()), - }); err != nil { + galleryPartial := models.GalleryPartial{ + UpdatedAt: models.NewOptionalTime(newImage.UpdatedAt), + } + if _, err := h.GalleryFinder.UpdatePartial(ctx, g.ID, galleryPartial); err != nil { return fmt.Errorf("updating gallery updated at timestamp: %w", err) } } h.PluginCache.RegisterPostHooks(ctx, newImage.ID, plugin.ImageCreatePost, nil, nil) - existing = []*models.Image{newImage} + existing = []*models.Image{&newImage} } // remove the old thumbnail if the checksum changed - we'll regenerate it @@ -215,17 +208,20 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models. if changed { // always update updated_at time - if _, err := h.CreatorUpdater.UpdatePartial(ctx, i.ID, models.ImagePartial{ - GalleryIDs: galleryIDs, - UpdatedAt: models.NewOptionalTime(time.Now()), - }); err != nil { + imagePartial := models.NewImagePartial() + imagePartial.GalleryIDs = galleryIDs + + if _, err := h.CreatorUpdater.UpdatePartial(ctx, i.ID, imagePartial); err != nil { return fmt.Errorf("updating image: %w", err) } if g != nil { - if _, err := h.GalleryFinder.UpdatePartial(ctx, g.ID, models.GalleryPartial{ - UpdatedAt: models.NewOptionalTime(time.Now()), - }); err != nil { + galleryPartial := models.GalleryPartial{ + // set UpdatedAt directly instead of using NewGalleryPartial, to ensure + // that the linked gallery has the same UpdatedAt time as this image + UpdatedAt: imagePartial.UpdatedAt, + } + if _, err := h.GalleryFinder.UpdatePartial(ctx, g.ID, galleryPartial); err != nil { return fmt.Errorf("updating gallery updated at timestamp: %w", err) } } @@ -252,16 +248,12 @@ func (h *ScanHandler) getOrCreateFolderBasedGallery(ctx context.Context, f model } // create a new folder-based gallery - now := time.Now() - newGallery := &models.Gallery{ - FolderID: &folderID, - CreatedAt: now, - UpdatedAt: now, - } + newGallery := models.NewGallery() + newGallery.FolderID = &folderID logger.Infof("Creating folder-based gallery for %s", filepath.Dir(f.Base().Path)) - if err := h.GalleryFinder.Create(ctx, newGallery, nil); err != nil { + if err := h.GalleryFinder.Create(ctx, &newGallery, nil); err != nil { return nil, fmt.Errorf("creating folder based gallery: %w", err) } @@ -269,11 +261,11 @@ func (h *ScanHandler) getOrCreateFolderBasedGallery(ctx context.Context, f model // it's possible that there are other images in the folder that // need to be added to the new gallery. Find and add them now. - if err := h.associateFolderImages(ctx, newGallery); err != nil { + if err := h.associateFolderImages(ctx, &newGallery); err != nil { return nil, fmt.Errorf("associating existing folder images: %w", err) } - return newGallery, nil + return &newGallery, nil } func (h *ScanHandler) associateFolderImages(ctx context.Context, g *models.Gallery) error { @@ -285,13 +277,13 @@ func (h *ScanHandler) associateFolderImages(ctx context.Context, g *models.Galle for _, ii := range i { logger.Infof("Adding %s to gallery %s", ii.Path, g.Path) - if _, err := h.CreatorUpdater.UpdatePartial(ctx, ii.ID, models.ImagePartial{ - GalleryIDs: &models.UpdateIDs{ - IDs: []int{g.ID}, - Mode: models.RelationshipUpdateModeAdd, - }, - UpdatedAt: models.NewOptionalTime(time.Now()), - }); err != nil { + imagePartial := models.NewImagePartial() + imagePartial.GalleryIDs = &models.UpdateIDs{ + IDs: []int{g.ID}, + Mode: models.RelationshipUpdateModeAdd, + } + + if _, err := h.CreatorUpdater.UpdatePartial(ctx, ii.ID, imagePartial); err != nil { return fmt.Errorf("updating image: %w", err) } } @@ -311,21 +303,17 @@ func (h *ScanHandler) getOrCreateZipBasedGallery(ctx context.Context, zipFile mo } // create a new zip-based gallery - now := time.Now() - newGallery := &models.Gallery{ - CreatedAt: now, - UpdatedAt: now, - } + newGallery := models.NewGallery() logger.Infof("%s doesn't exist. Creating new gallery...", zipFile.Base().Path) - if err := h.GalleryFinder.Create(ctx, newGallery, []models.FileID{zipFile.Base().ID}); err != nil { + if err := h.GalleryFinder.Create(ctx, &newGallery, []models.FileID{zipFile.Base().ID}); err != nil { return nil, fmt.Errorf("creating zip-based gallery: %w", err) } h.PluginCache.RegisterPostHooks(ctx, newGallery.ID, plugin.GalleryCreatePost, nil, nil) - return newGallery, nil + return &newGallery, nil } func (h *ScanHandler) getOrCreateGallery(ctx context.Context, f models.File) (*models.Gallery, error) { diff --git a/pkg/image/update.go b/pkg/image/update.go index e3a63b53d03..844e2088f71 100644 --- a/pkg/image/update.go +++ b/pkg/image/update.go @@ -7,22 +7,21 @@ import ( ) func AddPerformer(ctx context.Context, qb models.ImageUpdater, i *models.Image, performerID int) error { - _, err := qb.UpdatePartial(ctx, i.ID, models.ImagePartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }) - + imagePartial := models.NewImagePartial() + imagePartial.PerformerIDs = &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + } + _, err := qb.UpdatePartial(ctx, i.ID, imagePartial) return err } func AddTag(ctx context.Context, qb models.ImageUpdater, i *models.Image, tagID int) error { - _, err := qb.UpdatePartial(ctx, i.ID, models.ImagePartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }) + imagePartial := models.NewImagePartial() + imagePartial.TagIDs = &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + } + _, err := qb.UpdatePartial(ctx, i.ID, imagePartial) return err } diff --git a/pkg/models/mocks/ImageReaderWriter.go b/pkg/models/mocks/ImageReaderWriter.go index 4924fd51d11..a3ec6987792 100644 --- a/pkg/models/mocks/ImageReaderWriter.go +++ b/pkg/models/mocks/ImageReaderWriter.go @@ -114,13 +114,13 @@ func (_m *ImageReaderWriter) CountByGalleryID(ctx context.Context, galleryID int return r0, r1 } -// Create provides a mock function with given fields: ctx, newImage -func (_m *ImageReaderWriter) Create(ctx context.Context, newImage *models.ImageCreateInput) error { - ret := _m.Called(ctx, newImage) +// Create provides a mock function with given fields: ctx, newImage, fileIDs +func (_m *ImageReaderWriter) Create(ctx context.Context, newImage *models.Image, fileIDs []models.FileID) error { + ret := _m.Called(ctx, newImage, fileIDs) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *models.ImageCreateInput) error); ok { - r0 = rf(ctx, newImage) + if rf, ok := ret.Get(0).(func(context.Context, *models.Image, []models.FileID) error); ok { + r0 = rf(ctx, newImage, fileIDs) } else { r0 = ret.Error(0) } diff --git a/pkg/models/model_gallery.go b/pkg/models/model_gallery.go index 8f563f06f7b..c7c74a017f3 100644 --- a/pkg/models/model_gallery.go +++ b/pkg/models/model_gallery.go @@ -36,6 +36,45 @@ type Gallery struct { PerformerIDs RelatedIDs `json:"performer_ids"` } +func NewGallery() Gallery { + currentTime := time.Now() + return Gallery{ + CreatedAt: currentTime, + UpdatedAt: currentTime, + } +} + +// GalleryPartial represents part of a Gallery object. It is used to update +// the database entry. Only non-nil fields will be updated. +type GalleryPartial struct { + // Path OptionalString + // Checksum OptionalString + // Zip OptionalBool + Title OptionalString + URL OptionalString + Date OptionalDate + Details OptionalString + // Rating expressed in 1-100 scale + Rating OptionalInt + Organized OptionalBool + StudioID OptionalInt + // FileModTime OptionalTime + CreatedAt OptionalTime + UpdatedAt OptionalTime + + SceneIDs *UpdateIDs + TagIDs *UpdateIDs + PerformerIDs *UpdateIDs + PrimaryFileID *FileID +} + +func NewGalleryPartial() GalleryPartial { + currentTime := time.Now() + return GalleryPartial{ + UpdatedAt: NewOptionalTime(currentTime), + } +} + // IsUserCreated returns true if the gallery was created by the user. // This is determined by whether the gallery has a primary file or folder. func (g *Gallery) IsUserCreated() bool { @@ -97,37 +136,6 @@ func (g Gallery) PrimaryChecksum() string { return "" } -// GalleryPartial represents part of a Gallery object. It is used to update -// the database entry. Only non-nil fields will be updated. -type GalleryPartial struct { - // Path OptionalString - // Checksum OptionalString - // Zip OptionalBool - Title OptionalString - URL OptionalString - Date OptionalDate - Details OptionalString - // Rating expressed in 1-100 scale - Rating OptionalInt - Organized OptionalBool - StudioID OptionalInt - // FileModTime OptionalTime - CreatedAt OptionalTime - UpdatedAt OptionalTime - - SceneIDs *UpdateIDs - TagIDs *UpdateIDs - PerformerIDs *UpdateIDs - PrimaryFileID *FileID -} - -func NewGalleryPartial() GalleryPartial { - updatedTime := time.Now() - return GalleryPartial{ - UpdatedAt: NewOptionalTime(updatedTime), - } -} - // GetTitle returns the title of the scene. If the Title field is empty, // then the base filename is returned. func (g Gallery) GetTitle() string { @@ -153,13 +161,3 @@ func (g Gallery) DisplayName() string { } const DefaultGthumbWidth int = 640 - -type Galleries []*Gallery - -func (g *Galleries) Append(o interface{}) { - *g = append(*g, o.(*Gallery)) -} - -func (g *Galleries) New() interface{} { - return &Gallery{} -} diff --git a/pkg/models/model_gallery_chapter.go b/pkg/models/model_gallery_chapter.go index 5c9fc05b2be..6e527106bdd 100644 --- a/pkg/models/model_gallery_chapter.go +++ b/pkg/models/model_gallery_chapter.go @@ -13,6 +13,14 @@ type GalleryChapter struct { UpdatedAt time.Time `json:"updated_at"` } +func NewGalleryChapter() GalleryChapter { + currentTime := time.Now() + return GalleryChapter{ + CreatedAt: currentTime, + UpdatedAt: currentTime, + } +} + // GalleryChapterPartial represents part of a GalleryChapter object. // It is used to update the database entry. type GalleryChapterPartial struct { @@ -24,8 +32,8 @@ type GalleryChapterPartial struct { } func NewGalleryChapterPartial() GalleryChapterPartial { - updatedTime := time.Now() + currentTime := time.Now() return GalleryChapterPartial{ - UpdatedAt: NewOptionalTime(updatedTime), + UpdatedAt: NewOptionalTime(currentTime), } } diff --git a/pkg/models/model_image.go b/pkg/models/model_image.go index 9e0a0389a77..797f638ad35 100644 --- a/pkg/models/model_image.go +++ b/pkg/models/model_image.go @@ -36,6 +36,39 @@ type Image struct { PerformerIDs RelatedIDs `json:"performer_ids"` } +func NewImage() Image { + currentTime := time.Now() + return Image{ + CreatedAt: currentTime, + UpdatedAt: currentTime, + } +} + +type ImagePartial struct { + Title OptionalString + // Rating expressed in 1-100 scale + Rating OptionalInt + URL OptionalString + Date OptionalDate + Organized OptionalBool + OCounter OptionalInt + StudioID OptionalInt + CreatedAt OptionalTime + UpdatedAt OptionalTime + + GalleryIDs *UpdateIDs + TagIDs *UpdateIDs + PerformerIDs *UpdateIDs + PrimaryFileID *FileID +} + +func NewImagePartial() ImagePartial { + currentTime := time.Now() + return ImagePartial{ + UpdatedAt: NewOptionalTime(currentTime), + } +} + func (i *Image) LoadFiles(ctx context.Context, l FileLoader) error { return i.Files.load(func() ([]File, error) { return l.GetFiles(ctx, i.ID) @@ -102,43 +135,3 @@ func (i Image) DisplayName() string { return strconv.Itoa(i.ID) } - -type ImageCreateInput struct { - *Image - FileIDs []FileID -} - -type ImagePartial struct { - Title OptionalString - // Rating expressed in 1-100 scale - Rating OptionalInt - URL OptionalString - Date OptionalDate - Organized OptionalBool - OCounter OptionalInt - StudioID OptionalInt - CreatedAt OptionalTime - UpdatedAt OptionalTime - - GalleryIDs *UpdateIDs - TagIDs *UpdateIDs - PerformerIDs *UpdateIDs - PrimaryFileID *FileID -} - -func NewImagePartial() ImagePartial { - updatedTime := time.Now() - return ImagePartial{ - UpdatedAt: NewOptionalTime(updatedTime), - } -} - -type Images []*Image - -func (i *Images) Append(o interface{}) { - *i = append(*i, o.(*Image)) -} - -func (i *Images) New() interface{} { - return &Image{} -} diff --git a/pkg/models/model_joins.go b/pkg/models/model_joins.go index 5fe8b7fa5d9..da70293c3d3 100644 --- a/pkg/models/model_joins.go +++ b/pkg/models/model_joins.go @@ -11,8 +11,8 @@ type MoviesScenes struct { SceneIndex *int `json:"scene_index"` } -func (s MoviesScenes) SceneMovieInput() *SceneMovieInput { - return &SceneMovieInput{ +func (s MoviesScenes) SceneMovieInput() SceneMovieInput { + return SceneMovieInput{ MovieID: strconv.Itoa(s.MovieID), SceneIndex: s.SceneIndex, } @@ -28,12 +28,12 @@ type UpdateMovieIDs struct { Mode RelationshipUpdateMode `json:"mode"` } -func (u *UpdateMovieIDs) SceneMovieInputs() []*SceneMovieInput { +func (u *UpdateMovieIDs) SceneMovieInputs() []SceneMovieInput { if u == nil { return nil } - ret := make([]*SceneMovieInput, len(u.Movies)) + ret := make([]SceneMovieInput, len(u.Movies)) for _, id := range u.Movies { ret = append(ret, id.SceneMovieInput()) } @@ -51,21 +51,7 @@ func (u *UpdateMovieIDs) AddUnique(v MoviesScenes) { u.Movies = append(u.Movies, v) } -func UpdateMovieIDsFromInput(i []*SceneMovieInput) (*UpdateMovieIDs, error) { - ret := &UpdateMovieIDs{ - Mode: RelationshipUpdateModeSet, - } - - var err error - ret.Movies, err = MoviesScenesFromInput(i) - if err != nil { - return nil, err - } - - return ret, nil -} - -func MoviesScenesFromInput(input []*SceneMovieInput) ([]MoviesScenes, error) { +func MoviesScenesFromInput(input []SceneMovieInput) ([]MoviesScenes, error) { ret := make([]MoviesScenes, len(input)) for i, v := range input { diff --git a/pkg/models/model_movie.go b/pkg/models/model_movie.go index cf7f997d887..152f0d3bbb5 100644 --- a/pkg/models/model_movie.go +++ b/pkg/models/model_movie.go @@ -20,6 +20,14 @@ type Movie struct { UpdatedAt time.Time `json:"updated_at"` } +func NewMovie() Movie { + currentTime := time.Now() + return Movie{ + CreatedAt: currentTime, + UpdatedAt: currentTime, + } +} + type MoviePartial struct { Name OptionalString Aliases OptionalString @@ -35,30 +43,11 @@ type MoviePartial struct { UpdatedAt OptionalTime } -var DefaultMovieImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wgVBQsJl1CMZAAAASJJREFUeNrt3N0JwyAYhlEj3cj9R3Cm5rbkqtAP+qrnGaCYHPwJpLlaa++mmLpbAERAgAgIEAEBIiBABERAgAgIEAEBIiBABERAgAgIEAHZuVflj40x4i94zhk9vqsVvEq6AsQqMP1EjORx20OACAgQRRx7T+zzcFBxcjNDfoB4ntQqTm5Awo7MlqywZxcgYQ+RlqywJ3ozJAQCSBiEJSsQA0gYBpDAgAARECACAkRAgAgIEAERECACAmSjUv6eAOSB8m8YIGGzBUjYbAESBgMkbBkDEjZbgITBAClcxiqQvEoatreYIWEBASIgJ4Gkf11ntXH3nS9uxfGWfJ5J9hAgAgJEQAQEiIAAERAgAgJEQAQEiIAAERAgAgJEQAQEiL7qBuc6RKLHxr0CAAAAAElFTkSuQmCC" - -func NewMovie(name string) *Movie { - currentTime := time.Now() - return &Movie{ - Name: name, - CreatedAt: currentTime, - UpdatedAt: currentTime, - } -} - func NewMoviePartial() MoviePartial { - updatedTime := time.Now() + currentTime := time.Now() return MoviePartial{ - UpdatedAt: NewOptionalTime(updatedTime), + UpdatedAt: NewOptionalTime(currentTime), } } -type Movies []*Movie - -func (m *Movies) Append(o interface{}) { - *m = append(*m, o.(*Movie)) -} - -func (m *Movies) New() interface{} { - return &Movie{} -} +var DefaultMovieImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wgVBQsJl1CMZAAAASJJREFUeNrt3N0JwyAYhlEj3cj9R3Cm5rbkqtAP+qrnGaCYHPwJpLlaa++mmLpbAERAgAgIEAEBIiBABERAgAgIEAEBIiBABERAgAgIEAHZuVflj40x4i94zhk9vqsVvEq6AsQqMP1EjORx20OACAgQRRx7T+zzcFBxcjNDfoB4ntQqTm5Awo7MlqywZxcgYQ+RlqywJ3ozJAQCSBiEJSsQA0gYBpDAgAARECACAkRAgAgIEAERECACAmSjUv6eAOSB8m8YIGGzBUjYbAESBgMkbBkDEjZbgITBAClcxiqQvEoatreYIWEBASIgJ4Gkf11ntXH3nS9uxfGWfJ5J9hAgAgJEQAQEiIAAERAgAgJEQAQEiIAAERAgAgJEQAQEiL7qBuc6RKLHxr0CAAAAAElFTkSuQmCC" diff --git a/pkg/models/model_performer.go b/pkg/models/model_performer.go index a620f306516..09f92e13c6d 100644 --- a/pkg/models/model_performer.go +++ b/pkg/models/model_performer.go @@ -41,38 +41,12 @@ type Performer struct { StashIDs RelatedStashIDs `json:"stash_ids"` } -func (s *Performer) LoadAliases(ctx context.Context, l AliasLoader) error { - return s.Aliases.load(func() ([]string, error) { - return l.GetAliases(ctx, s.ID) - }) -} - -func (s *Performer) LoadTagIDs(ctx context.Context, l TagIDLoader) error { - return s.TagIDs.load(func() ([]int, error) { - return l.GetTagIDs(ctx, s.ID) - }) -} - -func (s *Performer) LoadStashIDs(ctx context.Context, l StashIDLoader) error { - return s.StashIDs.load(func() ([]StashID, error) { - return l.GetStashIDs(ctx, s.ID) - }) -} - -func (s *Performer) LoadRelationships(ctx context.Context, l PerformerReader) error { - if err := s.LoadAliases(ctx, l); err != nil { - return err - } - - if err := s.LoadTagIDs(ctx, l); err != nil { - return err - } - - if err := s.LoadStashIDs(ctx, l); err != nil { - return err +func NewPerformer() Performer { + currentTime := time.Now() + return Performer{ + CreatedAt: currentTime, + UpdatedAt: currentTime, } - - return nil } // PerformerPartial represents part of a Performer object. It is used to update @@ -112,28 +86,43 @@ type PerformerPartial struct { StashIDs *UpdateStashIDs } -func NewPerformer(name string) *Performer { +func NewPerformerPartial() PerformerPartial { currentTime := time.Now() - return &Performer{ - Name: name, - CreatedAt: currentTime, - UpdatedAt: currentTime, + return PerformerPartial{ + UpdatedAt: NewOptionalTime(currentTime), } } -func NewPerformerPartial() PerformerPartial { - updatedTime := time.Now() - return PerformerPartial{ - UpdatedAt: NewOptionalTime(updatedTime), - } +func (s *Performer) LoadAliases(ctx context.Context, l AliasLoader) error { + return s.Aliases.load(func() ([]string, error) { + return l.GetAliases(ctx, s.ID) + }) } -type Performers []*Performer +func (s *Performer) LoadTagIDs(ctx context.Context, l TagIDLoader) error { + return s.TagIDs.load(func() ([]int, error) { + return l.GetTagIDs(ctx, s.ID) + }) +} -func (p *Performers) Append(o interface{}) { - *p = append(*p, o.(*Performer)) +func (s *Performer) LoadStashIDs(ctx context.Context, l StashIDLoader) error { + return s.StashIDs.load(func() ([]StashID, error) { + return l.GetStashIDs(ctx, s.ID) + }) } -func (p *Performers) New() interface{} { - return &Performer{} +func (s *Performer) LoadRelationships(ctx context.Context, l PerformerReader) error { + if err := s.LoadAliases(ctx, l); err != nil { + return err + } + + if err := s.LoadTagIDs(ctx, l); err != nil { + return err + } + + if err := s.LoadStashIDs(ctx, l); err != nil { + return err + } + + return nil } diff --git a/pkg/models/model_saved_filter.go b/pkg/models/model_saved_filter.go index 51c50be51d1..d680e7c95ef 100644 --- a/pkg/models/model_saved_filter.go +++ b/pkg/models/model_saved_filter.go @@ -67,13 +67,3 @@ type SavedFilter struct { ObjectFilter map[string]interface{} `json:"object_filter"` UIOptions map[string]interface{} `json:"ui_options"` } - -type SavedFilters []*SavedFilter - -func (m *SavedFilters) Append(o interface{}) { - *m = append(*m, o.(*SavedFilter)) -} - -func (m *SavedFilters) New() interface{} { - return &SavedFilter{} -} diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index eadbaab3a34..4cd434eed80 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -48,6 +48,50 @@ type Scene struct { StashIDs RelatedStashIDs `json:"stash_ids"` } +func NewScene() Scene { + currentTime := time.Now() + return Scene{ + CreatedAt: currentTime, + UpdatedAt: currentTime, + } +} + +// ScenePartial represents part of a Scene object. It is used to update +// the database entry. +type ScenePartial struct { + Title OptionalString + Code OptionalString + Details OptionalString + Director OptionalString + Date OptionalDate + // Rating expressed in 1-100 scale + Rating OptionalInt + Organized OptionalBool + OCounter OptionalInt + StudioID OptionalInt + CreatedAt OptionalTime + UpdatedAt OptionalTime + ResumeTime OptionalFloat64 + PlayDuration OptionalFloat64 + PlayCount OptionalInt + LastPlayedAt OptionalTime + + URLs *UpdateStrings + GalleryIDs *UpdateIDs + TagIDs *UpdateIDs + PerformerIDs *UpdateIDs + MovieIDs *UpdateMovieIDs + StashIDs *UpdateStashIDs + PrimaryFileID *FileID +} + +func NewScenePartial() ScenePartial { + currentTime := time.Now() + return ScenePartial{ + UpdatedAt: NewOptionalTime(currentTime), + } +} + func (s *Scene) LoadURLs(ctx context.Context, l URLLoader) error { return s.URLs.load(func() ([]string, error) { return l.GetURLs(ctx, s.ID) @@ -145,77 +189,6 @@ func (s *Scene) LoadRelationships(ctx context.Context, l SceneReader) error { return nil } -// ScenePartial represents part of a Scene object. It is used to update -// the database entry. -type ScenePartial struct { - Title OptionalString - Code OptionalString - Details OptionalString - Director OptionalString - Date OptionalDate - // Rating expressed in 1-100 scale - Rating OptionalInt - Organized OptionalBool - OCounter OptionalInt - StudioID OptionalInt - CreatedAt OptionalTime - UpdatedAt OptionalTime - ResumeTime OptionalFloat64 - PlayDuration OptionalFloat64 - PlayCount OptionalInt - LastPlayedAt OptionalTime - - URLs *UpdateStrings - GalleryIDs *UpdateIDs - TagIDs *UpdateIDs - PerformerIDs *UpdateIDs - MovieIDs *UpdateMovieIDs - StashIDs *UpdateStashIDs - PrimaryFileID *FileID -} - -func NewScenePartial() ScenePartial { - updatedTime := time.Now() - return ScenePartial{ - UpdatedAt: NewOptionalTime(updatedTime), - } -} - -type SceneMovieInput struct { - MovieID string `json:"movie_id"` - SceneIndex *int `json:"scene_index"` -} - -type SceneUpdateInput struct { - ClientMutationID *string `json:"clientMutationId"` - ID string `json:"id"` - Title *string `json:"title"` - Code *string `json:"code"` - Details *string `json:"details"` - Director *string `json:"director"` - URL *string `json:"url"` - Date *string `json:"date"` - // Rating expressed in 1-5 scale - Rating *int `json:"rating"` - // Rating expressed in 1-100 scale - Rating100 *int `json:"rating100"` - OCounter *int `json:"o_counter"` - Organized *bool `json:"organized"` - Urls []string `json:"urls"` - StudioID *string `json:"studio_id"` - GalleryIds []string `json:"gallery_ids"` - PerformerIds []string `json:"performer_ids"` - Movies []*SceneMovieInput `json:"movies"` - TagIds []string `json:"tag_ids"` - // This should be a URL or a base64 encoded data URL - CoverImage *string `json:"cover_image"` - StashIds []StashID `json:"stash_ids"` - ResumeTime *float64 `json:"resume_time"` - PlayDuration *float64 `json:"play_duration"` - PlayCount *int `json:"play_count"` - PrimaryFileID *string `json:"primary_file_id"` -} - // UpdateInput constructs a SceneUpdateInput using the populated fields in the ScenePartial object. func (s ScenePartial) UpdateInput(id int) SceneUpdateInput { var dateStr *string @@ -302,16 +275,6 @@ type SceneFileType struct { Bitrate *int `graphql:"bitrate" json:"bitrate"` } -type Scenes []*Scene - -func (s *Scenes) Append(o interface{}) { - *s = append(*s, o.(*Scene)) -} - -func (s *Scenes) New() interface{} { - return &Scene{} -} - type VideoCaption struct { LanguageCode string `json:"language_code"` Filename string `json:"filename"` diff --git a/pkg/models/model_scene_marker.go b/pkg/models/model_scene_marker.go index 1e9ac611589..df77afecd77 100644 --- a/pkg/models/model_scene_marker.go +++ b/pkg/models/model_scene_marker.go @@ -14,6 +14,14 @@ type SceneMarker struct { UpdatedAt time.Time `json:"updated_at"` } +func NewSceneMarker() SceneMarker { + currentTime := time.Now() + return SceneMarker{ + CreatedAt: currentTime, + UpdatedAt: currentTime, + } +} + // SceneMarkerPartial represents part of a SceneMarker object. // It is used to update the database entry. type SceneMarkerPartial struct { @@ -26,8 +34,8 @@ type SceneMarkerPartial struct { } func NewSceneMarkerPartial() SceneMarkerPartial { - updatedTime := time.Now() + currentTime := time.Now() return SceneMarkerPartial{ - UpdatedAt: NewOptionalTime(updatedTime), + UpdatedAt: NewOptionalTime(currentTime), } } diff --git a/pkg/models/model_scraped_item.go b/pkg/models/model_scraped_item.go index 97d403b10ef..cb383c082e7 100644 --- a/pkg/models/model_scraped_item.go +++ b/pkg/models/model_scraped_item.go @@ -3,7 +3,6 @@ package models import ( "context" "strconv" - "time" "github.com/stashapp/stash/pkg/sliceutil/stringslice" "github.com/stashapp/stash/pkg/utils" @@ -23,17 +22,12 @@ type ScrapedStudio struct { func (ScrapedStudio) IsScrapedContent() {} func (s *ScrapedStudio) ToStudio(endpoint string, excluded map[string]bool) *Studio { - now := time.Now() - // Populate a new studio from the input - newStudio := Studio{ - Name: s.Name, - CreatedAt: now, - UpdatedAt: now, - } + ret := NewStudio() + ret.Name = s.Name if s.RemoteSiteID != nil && endpoint != "" { - newStudio.StashIDs = NewRelatedStashIDs([]StashID{ + ret.StashIDs = NewRelatedStashIDs([]StashID{ { Endpoint: endpoint, StashID: *s.RemoteSiteID, @@ -42,15 +36,15 @@ func (s *ScrapedStudio) ToStudio(endpoint string, excluded map[string]bool) *Stu } if s.URL != nil && !excluded["url"] { - newStudio.URL = *s.URL + ret.URL = *s.URL } if s.Parent != nil && s.Parent.StoredID != nil && !excluded["parent"] && !excluded["parent_studio"] { parentId, _ := strconv.Atoi(*s.Parent.StoredID) - newStudio.ParentID = &parentId + ret.ParentID = &parentId } - return &newStudio + return &ret } func (s *ScrapedStudio) GetImage(ctx context.Context, excluded map[string]bool) ([]byte, error) { @@ -69,17 +63,15 @@ func (s *ScrapedStudio) GetImage(ctx context.Context, excluded map[string]bool) } func (s *ScrapedStudio) ToPartial(id *string, endpoint string, excluded map[string]bool, existingStashIDs []StashID) *StudioPartial { - partial := StudioPartial{ - UpdatedAt: NewOptionalTime(time.Now()), - } - partial.ID, _ = strconv.Atoi(*id) + ret := NewStudioPartial() + ret.ID, _ = strconv.Atoi(*id) if s.Name != "" && !excluded["name"] { - partial.Name = NewOptionalString(s.Name) + ret.Name = NewOptionalString(s.Name) } if s.URL != nil && !excluded["url"] { - partial.URL = NewOptionalString(*s.URL) + ret.URL = NewOptionalString(*s.URL) } if s.Parent != nil && !excluded["parent"] { @@ -87,25 +79,25 @@ func (s *ScrapedStudio) ToPartial(id *string, endpoint string, excluded map[stri parentID, _ := strconv.Atoi(*s.Parent.StoredID) if parentID > 0 { // This is to be set directly as we know it has a value and the translator won't have the field - partial.ParentID = NewOptionalInt(parentID) + ret.ParentID = NewOptionalInt(parentID) } } } else { - partial.ParentID = NewOptionalIntPtr(nil) + ret.ParentID = NewOptionalIntPtr(nil) } if s.RemoteSiteID != nil && endpoint != "" { - partial.StashIDs = &UpdateStashIDs{ + ret.StashIDs = &UpdateStashIDs{ StashIDs: existingStashIDs, Mode: RelationshipUpdateModeSet, } - partial.StashIDs.Set(StashID{ + ret.StashIDs.Set(StashID{ Endpoint: endpoint, StashID: *s.RemoteSiteID, }) } - return &partial + return &ret } // A performer from a scraping operation... @@ -145,7 +137,8 @@ type ScrapedPerformer struct { func (ScrapedPerformer) IsScrapedContent() {} func (p *ScrapedPerformer) ToPerformer(endpoint string, excluded map[string]bool) *Performer { - ret := NewPerformer(*p.Name) + ret := NewPerformer() + ret.Name = *p.Name if p.Aliases != nil && !excluded["aliases"] { ret.Aliases = NewRelatedStrings(stringslice.FromString(*p.Aliases, ",")) @@ -244,7 +237,7 @@ func (p *ScrapedPerformer) ToPerformer(endpoint string, excluded map[string]bool }) } - return ret + return &ret } func (p *ScrapedPerformer) GetImage(ctx context.Context, excluded map[string]bool) ([]byte, error) { @@ -263,10 +256,10 @@ func (p *ScrapedPerformer) GetImage(ctx context.Context, excluded map[string]boo } func (p *ScrapedPerformer) ToPartial(endpoint string, excluded map[string]bool, existingStashIDs []StashID) PerformerPartial { - partial := NewPerformerPartial() + ret := NewPerformerPartial() if p.Aliases != nil && !excluded["aliases"] { - partial.Aliases = &UpdateStrings{ + ret.Aliases = &UpdateStrings{ Values: stringslice.FromString(*p.Aliases, ","), Mode: RelationshipUpdateModeSet, } @@ -274,88 +267,88 @@ func (p *ScrapedPerformer) ToPartial(endpoint string, excluded map[string]bool, if p.Birthdate != nil && !excluded["birthdate"] { date, err := ParseDate(*p.Birthdate) if err == nil { - partial.Birthdate = NewOptionalDate(date) + ret.Birthdate = NewOptionalDate(date) } } if p.DeathDate != nil && !excluded["death_date"] { date, err := ParseDate(*p.DeathDate) if err == nil { - partial.DeathDate = NewOptionalDate(date) + ret.DeathDate = NewOptionalDate(date) } } if p.CareerLength != nil && !excluded["career_length"] { - partial.CareerLength = NewOptionalString(*p.CareerLength) + ret.CareerLength = NewOptionalString(*p.CareerLength) } if p.Country != nil && !excluded["country"] { - partial.Country = NewOptionalString(*p.Country) + ret.Country = NewOptionalString(*p.Country) } if p.Ethnicity != nil && !excluded["ethnicity"] { - partial.Ethnicity = NewOptionalString(*p.Ethnicity) + ret.Ethnicity = NewOptionalString(*p.Ethnicity) } if p.EyeColor != nil && !excluded["eye_color"] { - partial.EyeColor = NewOptionalString(*p.EyeColor) + ret.EyeColor = NewOptionalString(*p.EyeColor) } if p.HairColor != nil && !excluded["hair_color"] { - partial.HairColor = NewOptionalString(*p.HairColor) + ret.HairColor = NewOptionalString(*p.HairColor) } if p.FakeTits != nil && !excluded["fake_tits"] { - partial.FakeTits = NewOptionalString(*p.FakeTits) + ret.FakeTits = NewOptionalString(*p.FakeTits) } if p.Gender != nil && !excluded["gender"] { - partial.Gender = NewOptionalString(*p.Gender) + ret.Gender = NewOptionalString(*p.Gender) } if p.Height != nil && !excluded["height"] { h, err := strconv.Atoi(*p.Height) if err == nil { - partial.Height = NewOptionalInt(h) + ret.Height = NewOptionalInt(h) } } if p.Weight != nil && !excluded["weight"] { w, err := strconv.Atoi(*p.Weight) if err == nil { - partial.Weight = NewOptionalInt(w) + ret.Weight = NewOptionalInt(w) } } if p.Instagram != nil && !excluded["instagram"] { - partial.Instagram = NewOptionalString(*p.Instagram) + ret.Instagram = NewOptionalString(*p.Instagram) } if p.Measurements != nil && !excluded["measurements"] { - partial.Measurements = NewOptionalString(*p.Measurements) + ret.Measurements = NewOptionalString(*p.Measurements) } if p.Name != nil && !excluded["name"] { - partial.Name = NewOptionalString(*p.Name) + ret.Name = NewOptionalString(*p.Name) } if p.Disambiguation != nil && !excluded["disambiguation"] { - partial.Disambiguation = NewOptionalString(*p.Disambiguation) + ret.Disambiguation = NewOptionalString(*p.Disambiguation) } if p.Details != nil && !excluded["details"] { - partial.Details = NewOptionalString(*p.Details) + ret.Details = NewOptionalString(*p.Details) } if p.Piercings != nil && !excluded["piercings"] { - partial.Piercings = NewOptionalString(*p.Piercings) + ret.Piercings = NewOptionalString(*p.Piercings) } if p.Tattoos != nil && !excluded["tattoos"] { - partial.Tattoos = NewOptionalString(*p.Tattoos) + ret.Tattoos = NewOptionalString(*p.Tattoos) } if p.Twitter != nil && !excluded["twitter"] { - partial.Twitter = NewOptionalString(*p.Twitter) + ret.Twitter = NewOptionalString(*p.Twitter) } if p.URL != nil && !excluded["url"] { - partial.URL = NewOptionalString(*p.URL) + ret.URL = NewOptionalString(*p.URL) } if p.RemoteSiteID != nil && endpoint != "" { - partial.StashIDs = &UpdateStashIDs{ + ret.StashIDs = &UpdateStashIDs{ StashIDs: existingStashIDs, Mode: RelationshipUpdateModeSet, } - partial.StashIDs.Set(StashID{ + ret.StashIDs.Set(StashID{ Endpoint: endpoint, StashID: *p.RemoteSiteID, }) } - return partial + return ret } type ScrapedTag struct { diff --git a/pkg/models/model_studio.go b/pkg/models/model_studio.go index 9f1deca4974..109535be1b5 100644 --- a/pkg/models/model_studio.go +++ b/pkg/models/model_studio.go @@ -21,28 +21,12 @@ type Studio struct { StashIDs RelatedStashIDs `json:"stash_ids"` } -func (s *Studio) LoadAliases(ctx context.Context, l AliasLoader) error { - return s.Aliases.load(func() ([]string, error) { - return l.GetAliases(ctx, s.ID) - }) -} - -func (s *Studio) LoadStashIDs(ctx context.Context, l StashIDLoader) error { - return s.StashIDs.load(func() ([]StashID, error) { - return l.GetStashIDs(ctx, s.ID) - }) -} - -func (s *Studio) LoadRelationships(ctx context.Context, l PerformerReader) error { - if err := s.LoadAliases(ctx, l); err != nil { - return err +func NewStudio() Studio { + currentTime := time.Now() + return Studio{ + CreatedAt: currentTime, + UpdatedAt: currentTime, } - - if err := s.LoadStashIDs(ctx, l); err != nil { - return err - } - - return nil } // StudioPartial represents part of a Studio object. It is used to update the database entry. @@ -62,12 +46,33 @@ type StudioPartial struct { StashIDs *UpdateStashIDs } -type Studios []*Studio +func NewStudioPartial() StudioPartial { + currentTime := time.Now() + return StudioPartial{ + UpdatedAt: NewOptionalTime(currentTime), + } +} -func (s *Studios) Append(o interface{}) { - *s = append(*s, o.(*Studio)) +func (s *Studio) LoadAliases(ctx context.Context, l AliasLoader) error { + return s.Aliases.load(func() ([]string, error) { + return l.GetAliases(ctx, s.ID) + }) +} + +func (s *Studio) LoadStashIDs(ctx context.Context, l StashIDLoader) error { + return s.StashIDs.load(func() ([]StashID, error) { + return l.GetStashIDs(ctx, s.ID) + }) } -func (s *Studios) New() interface{} { - return &Studio{} +func (s *Studio) LoadRelationships(ctx context.Context, l PerformerReader) error { + if err := s.LoadAliases(ctx, l); err != nil { + return err + } + + if err := s.LoadStashIDs(ctx, l); err != nil { + return err + } + + return nil } diff --git a/pkg/models/model_tag.go b/pkg/models/model_tag.go index e07eee77287..f8c49c5321f 100644 --- a/pkg/models/model_tag.go +++ b/pkg/models/model_tag.go @@ -13,6 +13,14 @@ type Tag struct { UpdatedAt time.Time `json:"updated_at"` } +func NewTag() Tag { + currentTime := time.Now() + return Tag{ + CreatedAt: currentTime, + UpdatedAt: currentTime, + } +} + type TagPartial struct { Name OptionalString Description OptionalString @@ -21,43 +29,14 @@ type TagPartial struct { UpdatedAt OptionalTime } -type TagPath struct { - Tag - Path string `json:"path"` -} - -func NewTag(name string) *Tag { - currentTime := time.Now() - return &Tag{ - Name: name, - CreatedAt: currentTime, - UpdatedAt: currentTime, - } -} - func NewTagPartial() TagPartial { - updatedTime := time.Now() + currentTime := time.Now() return TagPartial{ - UpdatedAt: NewOptionalTime(updatedTime), + UpdatedAt: NewOptionalTime(currentTime), } } -type Tags []*Tag - -func (t *Tags) Append(o interface{}) { - *t = append(*t, o.(*Tag)) -} - -func (t *Tags) New() interface{} { - return &Tag{} -} - -type TagPaths []*TagPath - -func (t *TagPaths) Append(o interface{}) { - *t = append(*t, o.(*TagPath)) -} - -func (t *TagPaths) New() interface{} { - return &TagPath{} +type TagPath struct { + Tag + Path string `json:"path"` } diff --git a/pkg/models/performer.go b/pkg/models/performer.go index 752f1ce08e2..3097c0ebf3b 100644 --- a/pkg/models/performer.go +++ b/pkg/models/performer.go @@ -192,3 +192,76 @@ type PerformerFilterType struct { // Filter by updated at UpdatedAt *TimestampCriterionInput `json:"updated_at"` } + +type PerformerCreateInput struct { + Name string `json:"name"` + Disambiguation *string `json:"disambiguation"` + URL *string `json:"url"` + Gender *GenderEnum `json:"gender"` + Birthdate *string `json:"birthdate"` + Ethnicity *string `json:"ethnicity"` + Country *string `json:"country"` + EyeColor *string `json:"eye_color"` + Height *string `json:"height"` + HeightCm *int `json:"height_cm"` + Measurements *string `json:"measurements"` + FakeTits *string `json:"fake_tits"` + PenisLength *float64 `json:"penis_length"` + Circumcised *CircumisedEnum `json:"circumcised"` + CareerLength *string `json:"career_length"` + Tattoos *string `json:"tattoos"` + Piercings *string `json:"piercings"` + Aliases *string `json:"aliases"` + AliasList []string `json:"alias_list"` + Twitter *string `json:"twitter"` + Instagram *string `json:"instagram"` + Favorite *bool `json:"favorite"` + TagIds []string `json:"tag_ids"` + // This should be a URL or a base64 encoded data URL + Image *string `json:"image"` + StashIds []StashID `json:"stash_ids"` + Rating *int `json:"rating"` + Rating100 *int `json:"rating100"` + Details *string `json:"details"` + DeathDate *string `json:"death_date"` + HairColor *string `json:"hair_color"` + Weight *int `json:"weight"` + IgnoreAutoTag *bool `json:"ignore_auto_tag"` +} + +type PerformerUpdateInput struct { + ID string `json:"id"` + Name *string `json:"name"` + Disambiguation *string `json:"disambiguation"` + URL *string `json:"url"` + Gender *GenderEnum `json:"gender"` + Birthdate *string `json:"birthdate"` + Ethnicity *string `json:"ethnicity"` + Country *string `json:"country"` + EyeColor *string `json:"eye_color"` + Height *string `json:"height"` + HeightCm *int `json:"height_cm"` + Measurements *string `json:"measurements"` + FakeTits *string `json:"fake_tits"` + PenisLength *float64 `json:"penis_length"` + Circumcised *CircumisedEnum `json:"circumcised"` + CareerLength *string `json:"career_length"` + Tattoos *string `json:"tattoos"` + Piercings *string `json:"piercings"` + Aliases *string `json:"aliases"` + AliasList []string `json:"alias_list"` + Twitter *string `json:"twitter"` + Instagram *string `json:"instagram"` + Favorite *bool `json:"favorite"` + TagIds []string `json:"tag_ids"` + // This should be a URL or a base64 encoded data URL + Image *string `json:"image"` + StashIds []StashID `json:"stash_ids"` + Rating *int `json:"rating"` + Rating100 *int `json:"rating100"` + Details *string `json:"details"` + DeathDate *string `json:"death_date"` + HairColor *string `json:"hair_color"` + Weight *int `json:"weight"` + IgnoreAutoTag *bool `json:"ignore_auto_tag"` +} diff --git a/pkg/models/repository_image.go b/pkg/models/repository_image.go index 5b191b2ab8c..b6eb895a06b 100644 --- a/pkg/models/repository_image.go +++ b/pkg/models/repository_image.go @@ -36,7 +36,7 @@ type ImageCounter interface { // ImageCreator provides methods to create images. type ImageCreator interface { - Create(ctx context.Context, newImage *ImageCreateInput) error + Create(ctx context.Context, newImage *Image, fileIDs []FileID) error } // ImageUpdater provides methods to update images. diff --git a/pkg/models/scene.go b/pkg/models/scene.go index e66576f3599..09ac117ad9d 100644 --- a/pkg/models/scene.go +++ b/pkg/models/scene.go @@ -113,6 +113,64 @@ type SceneQueryResult struct { resolveErr error } +type SceneMovieInput struct { + MovieID string `json:"movie_id"` + SceneIndex *int `json:"scene_index"` +} + +type SceneCreateInput struct { + Title *string `json:"title"` + Code *string `json:"code"` + Details *string `json:"details"` + Director *string `json:"director"` + URL *string `json:"url"` + Urls []string `json:"urls"` + Date *string `json:"date"` + Rating *int `json:"rating"` + Rating100 *int `json:"rating100"` + Organized *bool `json:"organized"` + StudioID *string `json:"studio_id"` + GalleryIds []string `json:"gallery_ids"` + PerformerIds []string `json:"performer_ids"` + Movies []SceneMovieInput `json:"movies"` + TagIds []string `json:"tag_ids"` + // This should be a URL or a base64 encoded data URL + CoverImage *string `json:"cover_image"` + StashIds []StashID `json:"stash_ids"` + // The first id will be assigned as primary. + // Files will be reassigned from existing scenes if applicable. + // Files must not already be primary for another scene. + FileIds []string `json:"file_ids"` +} + +type SceneUpdateInput struct { + ClientMutationID *string `json:"clientMutationId"` + ID string `json:"id"` + Title *string `json:"title"` + Code *string `json:"code"` + Details *string `json:"details"` + Director *string `json:"director"` + URL *string `json:"url"` + Urls []string `json:"urls"` + Date *string `json:"date"` + Rating *int `json:"rating"` + Rating100 *int `json:"rating100"` + OCounter *int `json:"o_counter"` + Organized *bool `json:"organized"` + StudioID *string `json:"studio_id"` + GalleryIds []string `json:"gallery_ids"` + PerformerIds []string `json:"performer_ids"` + Movies []SceneMovieInput `json:"movies"` + TagIds []string `json:"tag_ids"` + // This should be a URL or a base64 encoded data URL + CoverImage *string `json:"cover_image"` + StashIds []StashID `json:"stash_ids"` + ResumeTime *float64 `json:"resume_time"` + PlayDuration *float64 `json:"play_duration"` + PlayCount *int `json:"play_count"` + PrimaryFileID *string `json:"primary_file_id"` +} + type SceneDestroyInput struct { ID string `json:"id"` DeleteFile *bool `json:"delete_file"` diff --git a/pkg/models/studio.go b/pkg/models/studio.go index 0973df4e316..2d743db4bb6 100644 --- a/pkg/models/studio.go +++ b/pkg/models/studio.go @@ -35,3 +35,32 @@ type StudioFilterType struct { // Filter by updated at UpdatedAt *TimestampCriterionInput `json:"updated_at"` } + +type StudioCreateInput struct { + Name string `json:"name"` + URL *string `json:"url"` + ParentID *string `json:"parent_id"` + // This should be a URL or a base64 encoded data URL + Image *string `json:"image"` + StashIds []StashID `json:"stash_ids"` + Rating *int `json:"rating"` + Rating100 *int `json:"rating100"` + Details *string `json:"details"` + Aliases []string `json:"aliases"` + IgnoreAutoTag *bool `json:"ignore_auto_tag"` +} + +type StudioUpdateInput struct { + ID string `json:"id"` + Name *string `json:"name"` + URL *string `json:"url"` + ParentID *string `json:"parent_id"` + // This should be a URL or a base64 encoded data URL + Image *string `json:"image"` + StashIds []StashID `json:"stash_ids"` + Rating *int `json:"rating"` + Rating100 *int `json:"rating100"` + Details *string `json:"details"` + Aliases []string `json:"aliases"` + IgnoreAutoTag *bool `json:"ignore_auto_tag"` +} diff --git a/pkg/movie/import.go b/pkg/movie/import.go index e231031e865..8004798ae53 100644 --- a/pkg/movie/import.go +++ b/pkg/movie/import.go @@ -109,11 +109,10 @@ func (i *Importer) populateStudio(ctx context.Context) error { } func (i *Importer) createStudio(ctx context.Context, name string) (int, error) { - newStudio := &models.Studio{ - Name: name, - } + newStudio := models.NewStudio() + newStudio.Name = name - err := i.StudioWriter.Create(ctx, newStudio) + err := i.StudioWriter.Create(ctx, &newStudio) if err != nil { return 0, err } diff --git a/pkg/performer/import.go b/pkg/performer/import.go index 1c3c075a447..9f57d97fe9a 100644 --- a/pkg/performer/import.go +++ b/pkg/performer/import.go @@ -101,14 +101,15 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names [] func createTags(ctx context.Context, tagWriter models.TagFinderCreator, names []string) ([]*models.Tag, error) { var ret []*models.Tag for _, name := range names { - newTag := models.NewTag(name) + newTag := models.NewTag() + newTag.Name = name - err := tagWriter.Create(ctx, newTag) + err := tagWriter.Create(ctx, &newTag) if err != nil { return nil, err } - ret = append(ret, newTag) + ret = append(ret, &newTag) } return ret, nil diff --git a/pkg/scene/import.go b/pkg/scene/import.go index e2cfe8abaff..8c67cecdf39 100644 --- a/pkg/scene/import.go +++ b/pkg/scene/import.go @@ -169,11 +169,10 @@ func (i *Importer) populateStudio(ctx context.Context) error { } func (i *Importer) createStudio(ctx context.Context, name string) (int, error) { - newStudio := &models.Studio{ - Name: name, - } + newStudio := models.NewStudio() + newStudio.Name = name - err := i.StudioWriter.Create(ctx, newStudio) + err := i.StudioWriter.Create(ctx, &newStudio) if err != nil { return 0, err } @@ -279,7 +278,8 @@ func (i *Importer) populatePerformers(ctx context.Context) error { func (i *Importer) createPerformers(ctx context.Context, names []string) ([]*models.Performer, error) { var ret []*models.Performer for _, name := range names { - newPerformer := *models.NewPerformer(name) + newPerformer := models.NewPerformer() + newPerformer.Name = name err := i.PerformerWriter.Create(ctx, &newPerformer) if err != nil { @@ -338,9 +338,10 @@ func (i *Importer) populateMovies(ctx context.Context) error { } func (i *Importer) createMovie(ctx context.Context, name string) (int, error) { - newMovie := models.NewMovie(name) + newMovie := models.NewMovie() + newMovie.Name = name - err := i.MovieWriter.Create(ctx, newMovie) + err := i.MovieWriter.Create(ctx, &newMovie) if err != nil { return 0, err } @@ -468,14 +469,15 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names [] func createTags(ctx context.Context, tagWriter models.TagCreator, names []string) ([]*models.Tag, error) { var ret []*models.Tag for _, name := range names { - newTag := models.NewTag(name) + newTag := models.NewTag() + newTag.Name = name - err := tagWriter.Create(ctx, newTag) + err := tagWriter.Create(ctx, &newTag) if err != nil { return nil, err } - ret = append(ret, newTag) + ret = append(ret, &newTag) } return ret, nil diff --git a/pkg/scene/scan.go b/pkg/scene/scan.go index f16d0d5c61b..821485eb9a6 100644 --- a/pkg/scene/scan.go +++ b/pkg/scene/scan.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "time" "github.com/stashapp/stash/pkg/file/video" "github.com/stashapp/stash/pkg/logger" @@ -100,21 +99,17 @@ func (h *ScanHandler) Handle(ctx context.Context, f models.File, oldFile models. } } else { // create a new scene - now := time.Now() - newScene := &models.Scene{ - CreatedAt: now, - UpdatedAt: now, - } + newScene := models.NewScene() logger.Infof("%s doesn't exist. Creating new scene...", f.Base().Path) - if err := h.CreatorUpdater.Create(ctx, newScene, []models.FileID{videoFile.ID}); err != nil { + if err := h.CreatorUpdater.Create(ctx, &newScene, []models.FileID{videoFile.ID}); err != nil { return fmt.Errorf("creating new scene: %w", err) } h.PluginCache.RegisterPostHooks(ctx, newScene.ID, plugin.SceneCreatePost, nil, nil) - existing = []*models.Scene{newScene} + existing = []*models.Scene{&newScene} } if oldFile != nil { @@ -162,7 +157,8 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models. } // update updated_at time - if _, err := h.CreatorUpdater.UpdatePartial(ctx, s.ID, models.NewScenePartial()); err != nil { + scenePartial := models.NewScenePartial() + if _, err := h.CreatorUpdater.UpdatePartial(ctx, s.ID, scenePartial); err != nil { return fmt.Errorf("updating scene: %w", err) } } diff --git a/pkg/scene/update.go b/pkg/scene/update.go index f0a1a030f83..629fdedad47 100644 --- a/pkg/scene/update.go +++ b/pkg/scene/update.go @@ -74,32 +74,32 @@ func (u UpdateSet) UpdateInput() models.SceneUpdateInput { } func AddPerformer(ctx context.Context, qb models.SceneUpdater, o *models.Scene, performerID int) error { - _, err := qb.UpdatePartial(ctx, o.ID, models.ScenePartial{ - PerformerIDs: &models.UpdateIDs{ - IDs: []int{performerID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }) + scenePartial := models.NewScenePartial() + scenePartial.PerformerIDs = &models.UpdateIDs{ + IDs: []int{performerID}, + Mode: models.RelationshipUpdateModeAdd, + } + _, err := qb.UpdatePartial(ctx, o.ID, scenePartial) return err } func AddTag(ctx context.Context, qb models.SceneUpdater, o *models.Scene, tagID int) error { - _, err := qb.UpdatePartial(ctx, o.ID, models.ScenePartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{tagID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }) + scenePartial := models.NewScenePartial() + scenePartial.TagIDs = &models.UpdateIDs{ + IDs: []int{tagID}, + Mode: models.RelationshipUpdateModeAdd, + } + _, err := qb.UpdatePartial(ctx, o.ID, scenePartial) return err } func AddGallery(ctx context.Context, qb models.SceneUpdater, o *models.Scene, galleryID int) error { - _, err := qb.UpdatePartial(ctx, o.ID, models.ScenePartial{ - TagIDs: &models.UpdateIDs{ - IDs: []int{galleryID}, - Mode: models.RelationshipUpdateModeAdd, - }, - }) + scenePartial := models.NewScenePartial() + scenePartial.TagIDs = &models.UpdateIDs{ + IDs: []int{galleryID}, + Mode: models.RelationshipUpdateModeAdd, + } + _, err := qb.UpdatePartial(ctx, o.ID, scenePartial) return err } diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index 35982642695..0258ce537eb 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -160,18 +160,18 @@ func (qb *ImageStore) selectDataset() *goqu.SelectDataset { ) } -func (qb *ImageStore) Create(ctx context.Context, newObject *models.ImageCreateInput) error { +func (qb *ImageStore) Create(ctx context.Context, newObject *models.Image, fileIDs []models.FileID) error { var r imageRow - r.fromImage(*newObject.Image) + r.fromImage(*newObject) id, err := qb.tableMgr.insertID(ctx, r) if err != nil { return err } - if len(newObject.FileIDs) > 0 { + if len(fileIDs) > 0 { const firstPrimary = true - if err := imagesFilesTableMgr.insertJoins(ctx, id, firstPrimary, newObject.FileIDs); err != nil { + if err := imagesFilesTableMgr.insertJoins(ctx, id, firstPrimary, fileIDs); err != nil { return err } } @@ -198,7 +198,7 @@ func (qb *ImageStore) Create(ctx context.Context, newObject *models.ImageCreateI return fmt.Errorf("finding after create: %w", err) } - *newObject.Image = *updated + *newObject = *updated return nil } diff --git a/pkg/sqlite/image_test.go b/pkg/sqlite/image_test.go index 900c0b79456..621b72c64cc 100644 --- a/pkg/sqlite/image_test.go +++ b/pkg/sqlite/image_test.go @@ -152,10 +152,7 @@ func Test_imageQueryBuilder_Create(t *testing.T) { } } s := tt.newObject - if err := qb.Create(ctx, &models.ImageCreateInput{ - Image: &s, - FileIDs: fileIDs, - }); (err != nil) != tt.wantErr { + if err := qb.Create(ctx, &s, fileIDs); (err != nil) != tt.wantErr { t.Errorf("imageQueryBuilder.Create() error = %v, wantErr = %v", err, tt.wantErr) } diff --git a/pkg/sqlite/setup_test.go b/pkg/sqlite/setup_test.go index 737a28e7230..04236b3ac1c 100644 --- a/pkg/sqlite/setup_test.go +++ b/pkg/sqlite/setup_test.go @@ -1177,10 +1177,7 @@ func createImages(ctx context.Context, n int) error { image := makeImage(i) - err := qb.Create(ctx, &models.ImageCreateInput{ - Image: image, - FileIDs: []models.FileID{f.ID}, - }) + err := qb.Create(ctx, image, []models.FileID{f.ID}) if err != nil { return fmt.Errorf("Error creating image %v+: %s", image, err.Error()) diff --git a/pkg/sqlite/tag.go b/pkg/sqlite/tag.go index ce09da4464f..33273525402 100644 --- a/pkg/sqlite/tag.go +++ b/pkg/sqlite/tag.go @@ -890,9 +890,9 @@ func (qb *TagStore) queryTags(ctx context.Context, query string, args []interfac return ret, nil } -func (qb *TagStore) queryTagPaths(ctx context.Context, query string, args []interface{}) (models.TagPaths, error) { +func (qb *TagStore) queryTagPaths(ctx context.Context, query string, args []interface{}) ([]*models.TagPath, error) { const single = false - var ret models.TagPaths + var ret []*models.TagPath if err := qb.queryFunc(ctx, query, args, single, func(r *sqlx.Rows) error { var f tagPathRow if err := r.StructScan(&f); err != nil { diff --git a/pkg/studio/import.go b/pkg/studio/import.go index df712daab79..1af5ec5c3e0 100644 --- a/pkg/studio/import.go +++ b/pkg/studio/import.go @@ -77,11 +77,10 @@ func (i *Importer) populateParentStudio(ctx context.Context) error { } func (i *Importer) createParentStudio(ctx context.Context, name string) (int, error) { - newStudio := &models.Studio{ - Name: name, - } + newStudio := models.NewStudio() + newStudio.Name = name - err := i.ReaderWriter.Create(ctx, newStudio) + err := i.ReaderWriter.Create(ctx, &newStudio) if err != nil { return 0, err } diff --git a/pkg/tag/import.go b/pkg/tag/import.go index 368815bbe44..6905d15ad73 100644 --- a/pkg/tag/import.go +++ b/pkg/tag/import.go @@ -151,9 +151,10 @@ func (i *Importer) getParents(ctx context.Context) ([]int, error) { } func (i *Importer) createParent(ctx context.Context, name string) (int, error) { - newTag := models.NewTag(name) + newTag := models.NewTag() + newTag.Name = name - err := i.ReaderWriter.Create(ctx, newTag) + err := i.ReaderWriter.Create(ctx, &newTag) if err != nil { return 0, err }