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>
This commit is contained in:
DingDongSoLong4 2023-09-11 04:24:15 +02:00 committed by GitHub
parent cf3301c8bc
commit 24e4719abc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 1514 additions and 1407 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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()

View file

@ -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,42 +39,38 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input GalleryCreat
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)
}
sceneIDs, err := stringslice.StringSliceToIntSlice(input.SceneIds)
if err != nil {
return nil, fmt.Errorf("converting scene 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 := models.NewGallery()
newGallery.Date, err = translator.datePtr(input.Date, "date")
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 date: %w", err)
}
newGallery.StudioID, err = translator.intPtrFromString(input.StudioID, "studio_id")
newGallery.StudioID, err = translator.intPtrFromString(input.StudioID)
if err != nil {
return nil, fmt.Errorf("converting studio id: %w", err)
}
newGallery.PerformerIDs, err = translator.relatedIds(input.PerformerIds)
if err != nil {
return nil, fmt.Errorf("converting performer ids: %w", err)
}
newGallery.TagIDs, err = translator.relatedIds(input.TagIds)
if err != nil {
return nil, fmt.Errorf("converting tag ids: %w", err)
}
newGallery.SceneIDs, err = translator.relatedIds(input.SceneIds)
if err != nil {
return nil, fmt.Errorf("converting scene ids: %w", err)
}
// Start the transaction and save the gallery
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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 {

View file

@ -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 := models.NewPerformer()
newPerformer.Birthdate, err = translator.datePtr(input.Birthdate, "birthdate")
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.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")
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,
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,
}
}
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.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,
}
} 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)
}
// 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)
}
}
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)
// prefer alias_list over aliases
if translator.hasField("alias_list") {
updatedPerformer.Aliases = translator.updateStringsBulk(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.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 {

View file

@ -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 {

View file

@ -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 := models.NewScene()
newScene.Date, err = translator.datePtr(input.Date, "date")
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,
urls = []string{*input.URL}
}
updatedScene.URLs = translator.updateStrings(urls, "url")
}
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)
updatedScene.PrimaryFileID = &converted
updatedScene.PrimaryFileID, err = translator.fileIDPtrFromString(input.PrimaryFileID)
if err != nil {
return nil, fmt.Errorf("converting primary file id: %w", err)
}
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.PerformerIDs, err = translator.updateIds(input.PerformerIds, "performer_ids")
if err != nil {
return nil, fmt.Errorf("converting performer ids: %w", err)
}
updatedScene.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids")
if err != nil {
return nil, fmt.Errorf("converting tag ids: %w", err)
}
updatedScene.GalleryIDs, err = translator.updateIds(input.GalleryIds, "gallery_ids")
if err != nil {
return nil, fmt.Errorf("converting gallery ids: %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)
}
}
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)
}
}
// 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)
}
}
// 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)
}
updatedScene.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids")
if err != nil {
return nil, fmt.Errorf("converting tag ids: %w", err)
}
updatedScene.GalleryIDs, err = translator.updateIdsBulk(input.GalleryIds, "gallery_ids")
if err != nil {
return nil, fmt.Errorf("converting gallery 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)
}
}
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)
}
}
// 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 {

View file

@ -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

View file

@ -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) {
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)
}
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
// 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"),
}
// Populate studio from the input
updatedStudio := models.NewStudioPartial()
var err error
newStudio.ParentID, err = translator.intPtrFromString(input.ParentID, "parent_id")
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 {

View file

@ -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
}

View file

@ -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)
func stashIDsSliceToPtrSlice(v []models.StashID) []*models.StashID {
ret := make([]*models.StashID, len(v))
for i, vv := range v {
c := vv
ret[i] = &c
}
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,
})
}
return ret, nil
return ret
}

View file

@ -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{

View file

@ -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{

View file

@ -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())

View file

@ -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{

View file

@ -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{

View file

@ -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)

View file

@ -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{

View file

@ -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()
}

View file

@ -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)

View file

@ -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()
}

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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()
}

View file

@ -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

View file

@ -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 {

View file

@ -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
}

View file

@ -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

View file

@ -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) {

View file

@ -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
}

View file

@ -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)
}

View file

@ -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{}
}

View file

@ -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),
}
}

View file

@ -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{}
}

View file

@ -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 {

View file

@ -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"

View file

@ -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
func NewPerformer() Performer {
currentTime := time.Now()
return Performer{
CreatedAt: currentTime,
UpdatedAt: currentTime,
}
if err := s.LoadTagIDs(ctx, l); err != nil {
return err
}
if err := s.LoadStashIDs(ctx, l); err != nil {
return err
}
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 {
currentTime := time.Now()
return &Performer{
Name: name,
CreatedAt: currentTime,
UpdatedAt: currentTime,
}
}
func NewPerformerPartial() PerformerPartial {
updatedTime := time.Now()
currentTime := time.Now()
return PerformerPartial{
UpdatedAt: NewOptionalTime(updatedTime),
UpdatedAt: NewOptionalTime(currentTime),
}
}
type Performers []*Performer
func (p *Performers) Append(o interface{}) {
*p = append(*p, o.(*Performer))
func (s *Performer) LoadAliases(ctx context.Context, l AliasLoader) error {
return s.Aliases.load(func() ([]string, error) {
return l.GetAliases(ctx, s.ID)
})
}
func (p *Performers) New() interface{} {
return &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 (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
}
return nil
}

View file

@ -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{}
}

View file

@ -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"`

View file

@ -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),
}
}

View file

@ -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 {

View file

@ -21,6 +21,38 @@ type Studio struct {
StashIDs RelatedStashIDs `json:"stash_ids"`
}
func NewStudio() Studio {
currentTime := time.Now()
return Studio{
CreatedAt: currentTime,
UpdatedAt: currentTime,
}
}
// StudioPartial represents part of a Studio object. It is used to update the database entry.
type StudioPartial struct {
ID int
Name OptionalString
URL OptionalString
ParentID OptionalInt
// Rating expressed in 1-100 scale
Rating OptionalInt
Details OptionalString
CreatedAt OptionalTime
UpdatedAt OptionalTime
IgnoreAutoTag OptionalBool
Aliases *UpdateStrings
StashIDs *UpdateStashIDs
}
func NewStudioPartial() StudioPartial {
currentTime := time.Now()
return StudioPartial{
UpdatedAt: NewOptionalTime(currentTime),
}
}
func (s *Studio) LoadAliases(ctx context.Context, l AliasLoader) error {
return s.Aliases.load(func() ([]string, error) {
return l.GetAliases(ctx, s.ID)
@ -44,30 +76,3 @@ func (s *Studio) LoadRelationships(ctx context.Context, l PerformerReader) error
return nil
}
// StudioPartial represents part of a Studio object. It is used to update the database entry.
type StudioPartial struct {
ID int
Name OptionalString
URL OptionalString
ParentID OptionalInt
// Rating expressed in 1-100 scale
Rating OptionalInt
Details OptionalString
CreatedAt OptionalTime
UpdatedAt OptionalTime
IgnoreAutoTag OptionalBool
Aliases *UpdateStrings
StashIDs *UpdateStashIDs
}
type Studios []*Studio
func (s *Studios) Append(o interface{}) {
*s = append(*s, o.(*Studio))
}
func (s *Studios) New() interface{} {
return &Studio{}
}

View file

@ -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
}
func NewTagPartial() TagPartial {
currentTime := time.Now()
return TagPartial{
UpdatedAt: NewOptionalTime(currentTime),
}
}
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()
return TagPartial{
UpdatedAt: NewOptionalTime(updatedTime),
}
}
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{}
}

View file

@ -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"`
}

View file

@ -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.

View file

@ -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"`

View file

@ -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"`
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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())

View file

@ -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 {

View file

@ -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
}

View file

@ -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
}