mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Add updated_at field to stash_id's (#5259)
* Add updated_at field to stash_id's * Only set updated at on stash ids when actually updating in identify --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -129,9 +129,6 @@ models:
|
||||
model: github.com/stashapp/stash/internal/identify.FieldStrategy
|
||||
ScraperSource:
|
||||
model: github.com/stashapp/stash/pkg/scraper.Source
|
||||
# rebind inputs to types
|
||||
StashIDInput:
|
||||
model: github.com/stashapp/stash/pkg/models.StashID
|
||||
IdentifySourceInput:
|
||||
model: github.com/stashapp/stash/internal/identify.Source
|
||||
IdentifyFieldOptionsInput:
|
||||
|
||||
@@ -13,11 +13,13 @@ input StashBoxInput {
|
||||
type StashID {
|
||||
endpoint: String!
|
||||
stash_id: String!
|
||||
updated_at: Time!
|
||||
}
|
||||
|
||||
input StashIDInput {
|
||||
endpoint: String!
|
||||
stash_id: String!
|
||||
updated_at: Time
|
||||
}
|
||||
|
||||
input StashBoxFingerprintSubmissionInput {
|
||||
|
||||
@@ -335,13 +335,13 @@ func (t changesetTranslator) updateStringsBulk(value *BulkUpdateStrings, field s
|
||||
}
|
||||
}
|
||||
|
||||
func (t changesetTranslator) updateStashIDs(value []models.StashID, field string) *models.UpdateStashIDs {
|
||||
func (t changesetTranslator) updateStashIDs(value models.StashIDInputs, field string) *models.UpdateStashIDs {
|
||||
if !t.hasField(field) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &models.UpdateStashIDs{
|
||||
StashIDs: value,
|
||||
StashIDs: value.ToStashIDs(),
|
||||
Mode: models.RelationshipUpdateModeSet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||
newPerformer.Height = input.HeightCm
|
||||
newPerformer.Weight = input.Weight
|
||||
newPerformer.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag)
|
||||
newPerformer.StashIDs = models.NewRelatedStashIDs(input.StashIds)
|
||||
newPerformer.StashIDs = models.NewRelatedStashIDs(models.StashIDInputs(input.StashIds).ToStashIDs())
|
||||
|
||||
newPerformer.URLs = models.NewRelatedStrings([]string{})
|
||||
if input.URL != nil {
|
||||
|
||||
@@ -50,7 +50,7 @@ func (r *mutationResolver) SceneCreate(ctx context.Context, input models.SceneCr
|
||||
newScene.Director = translator.string(input.Director)
|
||||
newScene.Rating = input.Rating100
|
||||
newScene.Organized = translator.bool(input.Organized)
|
||||
newScene.StashIDs = models.NewRelatedStashIDs(input.StashIds)
|
||||
newScene.StashIDs = models.NewRelatedStashIDs(models.StashIDInputs(input.StashIds).ToStashIDs())
|
||||
|
||||
newScene.Date, err = translator.datePtr(input.Date)
|
||||
if err != nil {
|
||||
|
||||
@@ -39,7 +39,7 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio
|
||||
newStudio.Details = translator.string(input.Details)
|
||||
newStudio.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag)
|
||||
newStudio.Aliases = models.NewRelatedStrings(input.Aliases)
|
||||
newStudio.StashIDs = models.NewRelatedStashIDs(input.StashIds)
|
||||
newStudio.StashIDs = models.NewRelatedStashIDs(models.StashIDInputs(input.StashIds).ToStashIDs())
|
||||
|
||||
var err error
|
||||
|
||||
|
||||
@@ -245,7 +245,18 @@ func (t *SceneIdentifier) getSceneUpdater(ctx context.Context, s *models.Scene,
|
||||
}
|
||||
}
|
||||
|
||||
stashIDs, err := rel.stashIDs(ctx)
|
||||
// SetCoverImage defaults to true if unset
|
||||
if options.SetCoverImage == nil || *options.SetCoverImage {
|
||||
ret.CoverImage, err = rel.cover(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// if anything changed, also update the updated at time on the applicable stash id
|
||||
changed := !ret.IsEmpty()
|
||||
|
||||
stashIDs, err := rel.stashIDs(ctx, changed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -256,14 +267,6 @@ func (t *SceneIdentifier) getSceneUpdater(ctx context.Context, s *models.Scene,
|
||||
}
|
||||
}
|
||||
|
||||
// SetCoverImage defaults to true if unset
|
||||
if options.SetCoverImage == nil || *options.SetCoverImage {
|
||||
ret.CoverImage, err = rel.cover(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
@@ -182,7 +183,13 @@ func (g sceneRelationships) tags(ctx context.Context) ([]int, error) {
|
||||
return tagIDs, nil
|
||||
}
|
||||
|
||||
func (g sceneRelationships) stashIDs(ctx context.Context) ([]models.StashID, error) {
|
||||
// stashIDs returns the updated stash IDs for the scene
|
||||
// returns nil if not applicable or no changes were made
|
||||
// if setUpdateTime is true, then the updated_at field will be set to the current time
|
||||
// for the applicable matching stash ID
|
||||
func (g sceneRelationships) stashIDs(ctx context.Context, setUpdateTime bool) ([]models.StashID, error) {
|
||||
updateTime := time.Now()
|
||||
|
||||
remoteSiteID := g.result.result.RemoteSiteID
|
||||
fieldStrategy := g.fieldOptions["stash_ids"]
|
||||
target := g.scene
|
||||
@@ -199,7 +206,7 @@ func (g sceneRelationships) stashIDs(ctx context.Context) ([]models.StashID, err
|
||||
strategy = fieldStrategy.Strategy
|
||||
}
|
||||
|
||||
var stashIDs []models.StashID
|
||||
var stashIDs models.StashIDs
|
||||
originalStashIDs := target.StashIDs.List()
|
||||
|
||||
if strategy == FieldStrategyMerge {
|
||||
@@ -208,15 +215,17 @@ func (g sceneRelationships) stashIDs(ctx context.Context) ([]models.StashID, err
|
||||
stashIDs = append(stashIDs, originalStashIDs...)
|
||||
}
|
||||
|
||||
// find and update the stash id if it exists
|
||||
for i, stashID := range stashIDs {
|
||||
if endpoint == stashID.Endpoint {
|
||||
// if stashID is the same, then don't set
|
||||
if stashID.StashID == *remoteSiteID {
|
||||
if !setUpdateTime && stashID.StashID == *remoteSiteID {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// replace the stash id and return
|
||||
stashID.StashID = *remoteSiteID
|
||||
stashID.UpdatedAt = updateTime
|
||||
stashIDs[i] = stashID
|
||||
return stashIDs, nil
|
||||
}
|
||||
@@ -224,11 +233,14 @@ func (g sceneRelationships) stashIDs(ctx context.Context) ([]models.StashID, err
|
||||
|
||||
// not found, create new entry
|
||||
stashIDs = append(stashIDs, models.StashID{
|
||||
StashID: *remoteSiteID,
|
||||
Endpoint: endpoint,
|
||||
StashID: *remoteSiteID,
|
||||
Endpoint: endpoint,
|
||||
UpdatedAt: updateTime,
|
||||
})
|
||||
|
||||
if sliceutil.SliceSame(originalStashIDs, stashIDs) {
|
||||
// don't return if nothing was changed
|
||||
// if we're setting update time, then we always return
|
||||
if !setUpdateTime && stashIDs.HasSameStashIDs(originalStashIDs) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
@@ -548,8 +549,9 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
ID: sceneWithStashID,
|
||||
StashIDs: models.NewRelatedStashIDs([]models.StashID{
|
||||
{
|
||||
StashID: remoteSiteID,
|
||||
Endpoint: existingEndpoint,
|
||||
StashID: remoteSiteID,
|
||||
Endpoint: existingEndpoint,
|
||||
UpdatedAt: time.Time{},
|
||||
},
|
||||
}),
|
||||
}
|
||||
@@ -561,14 +563,17 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
fieldOptions: make(map[string]*FieldOptions),
|
||||
}
|
||||
|
||||
setTime := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scene *models.Scene
|
||||
fieldOptions *FieldOptions
|
||||
endpoint string
|
||||
remoteSiteID *string
|
||||
want []models.StashID
|
||||
wantErr bool
|
||||
name string
|
||||
scene *models.Scene
|
||||
fieldOptions *FieldOptions
|
||||
endpoint string
|
||||
remoteSiteID *string
|
||||
setUpdateTime bool
|
||||
want []models.StashID
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"ignore",
|
||||
@@ -578,6 +583,7 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
},
|
||||
newEndpoint,
|
||||
&remoteSiteID,
|
||||
false,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
@@ -587,6 +593,7 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
defaultOptions,
|
||||
"",
|
||||
&remoteSiteID,
|
||||
false,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
@@ -596,6 +603,7 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
defaultOptions,
|
||||
newEndpoint,
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
@@ -605,19 +613,38 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
defaultOptions,
|
||||
existingEndpoint,
|
||||
&remoteSiteID,
|
||||
false,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"merge existing set update time",
|
||||
sceneWithStashIDs,
|
||||
defaultOptions,
|
||||
existingEndpoint,
|
||||
&remoteSiteID,
|
||||
true,
|
||||
[]models.StashID{
|
||||
{
|
||||
StashID: remoteSiteID,
|
||||
Endpoint: existingEndpoint,
|
||||
UpdatedAt: setTime,
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"merge existing new value",
|
||||
sceneWithStashIDs,
|
||||
defaultOptions,
|
||||
existingEndpoint,
|
||||
&newRemoteSiteID,
|
||||
false,
|
||||
[]models.StashID{
|
||||
{
|
||||
StashID: newRemoteSiteID,
|
||||
Endpoint: existingEndpoint,
|
||||
StashID: newRemoteSiteID,
|
||||
Endpoint: existingEndpoint,
|
||||
UpdatedAt: setTime,
|
||||
},
|
||||
},
|
||||
false,
|
||||
@@ -628,14 +655,17 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
defaultOptions,
|
||||
newEndpoint,
|
||||
&newRemoteSiteID,
|
||||
false,
|
||||
[]models.StashID{
|
||||
{
|
||||
StashID: remoteSiteID,
|
||||
Endpoint: existingEndpoint,
|
||||
StashID: remoteSiteID,
|
||||
Endpoint: existingEndpoint,
|
||||
UpdatedAt: time.Time{},
|
||||
},
|
||||
{
|
||||
StashID: newRemoteSiteID,
|
||||
Endpoint: newEndpoint,
|
||||
StashID: newRemoteSiteID,
|
||||
Endpoint: newEndpoint,
|
||||
UpdatedAt: setTime,
|
||||
},
|
||||
},
|
||||
false,
|
||||
@@ -648,10 +678,12 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
},
|
||||
newEndpoint,
|
||||
&newRemoteSiteID,
|
||||
false,
|
||||
[]models.StashID{
|
||||
{
|
||||
StashID: newRemoteSiteID,
|
||||
Endpoint: newEndpoint,
|
||||
StashID: newRemoteSiteID,
|
||||
Endpoint: newEndpoint,
|
||||
UpdatedAt: setTime,
|
||||
},
|
||||
},
|
||||
false,
|
||||
@@ -664,9 +696,28 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
},
|
||||
existingEndpoint,
|
||||
&remoteSiteID,
|
||||
false,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"overwrite same set update time",
|
||||
sceneWithStashIDs,
|
||||
&FieldOptions{
|
||||
Strategy: FieldStrategyOverwrite,
|
||||
},
|
||||
existingEndpoint,
|
||||
&remoteSiteID,
|
||||
true,
|
||||
[]models.StashID{
|
||||
{
|
||||
StashID: remoteSiteID,
|
||||
Endpoint: existingEndpoint,
|
||||
UpdatedAt: setTime,
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -681,11 +732,20 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
got, err := tr.stashIDs(testCtx)
|
||||
got, err := tr.stashIDs(testCtx, tt.setUpdateTime)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("sceneRelationships.stashIDs() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// massage updatedAt times to be consistent for comparison
|
||||
for i := range got {
|
||||
if !got[i].UpdatedAt.IsZero() {
|
||||
got[i].UpdatedAt = setTime
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("sceneRelationships.stashIDs() = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
|
||||
@@ -192,9 +192,9 @@ func (s ScenePartial) UpdateInput(id int) SceneUpdateInput {
|
||||
dateStr = &v
|
||||
}
|
||||
|
||||
var stashIDs []StashID
|
||||
var stashIDs StashIDs
|
||||
if s.StashIDs != nil {
|
||||
stashIDs = s.StashIDs.StashIDs
|
||||
stashIDs = StashIDs(s.StashIDs.StashIDs)
|
||||
}
|
||||
|
||||
ret := SceneUpdateInput{
|
||||
@@ -212,7 +212,7 @@ func (s ScenePartial) UpdateInput(id int) SceneUpdateInput {
|
||||
PerformerIds: s.PerformerIDs.IDStrings(),
|
||||
Movies: s.GroupIDs.SceneMovieInputs(),
|
||||
TagIds: s.TagIDs.IDStrings(),
|
||||
StashIds: stashIDs,
|
||||
StashIds: stashIDs.ToStashIDInputs(),
|
||||
}
|
||||
|
||||
return ret
|
||||
|
||||
@@ -3,6 +3,7 @@ package models
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
@@ -29,8 +30,9 @@ func (s *ScrapedStudio) ToStudio(endpoint string, excluded map[string]bool) *Stu
|
||||
if s.RemoteSiteID != nil && endpoint != "" {
|
||||
ret.StashIDs = NewRelatedStashIDs([]StashID{
|
||||
{
|
||||
Endpoint: endpoint,
|
||||
StashID: *s.RemoteSiteID,
|
||||
Endpoint: endpoint,
|
||||
StashID: *s.RemoteSiteID,
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -65,6 +67,7 @@ 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 {
|
||||
ret := NewStudioPartial()
|
||||
ret.ID, _ = strconv.Atoi(id)
|
||||
currentTime := time.Now()
|
||||
|
||||
if s.Name != "" && !excluded["name"] {
|
||||
ret.Name = NewOptionalString(s.Name)
|
||||
@@ -90,8 +93,9 @@ func (s *ScrapedStudio) ToPartial(id string, endpoint string, excluded map[strin
|
||||
Mode: RelationshipUpdateModeSet,
|
||||
}
|
||||
ret.StashIDs.Set(StashID{
|
||||
Endpoint: endpoint,
|
||||
StashID: *s.RemoteSiteID,
|
||||
Endpoint: endpoint,
|
||||
StashID: *s.RemoteSiteID,
|
||||
UpdatedAt: currentTime,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -137,6 +141,7 @@ func (ScrapedPerformer) IsScrapedContent() {}
|
||||
|
||||
func (p *ScrapedPerformer) ToPerformer(endpoint string, excluded map[string]bool) *Performer {
|
||||
ret := NewPerformer()
|
||||
currentTime := time.Now()
|
||||
ret.Name = *p.Name
|
||||
|
||||
if p.Aliases != nil && !excluded["aliases"] {
|
||||
@@ -244,8 +249,9 @@ func (p *ScrapedPerformer) ToPerformer(endpoint string, excluded map[string]bool
|
||||
if p.RemoteSiteID != nil && endpoint != "" {
|
||||
ret.StashIDs = NewRelatedStashIDs([]StashID{
|
||||
{
|
||||
Endpoint: endpoint,
|
||||
StashID: *p.RemoteSiteID,
|
||||
Endpoint: endpoint,
|
||||
StashID: *p.RemoteSiteID,
|
||||
UpdatedAt: currentTime,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -375,8 +381,9 @@ func (p *ScrapedPerformer) ToPartial(endpoint string, excluded map[string]bool,
|
||||
Mode: RelationshipUpdateModeSet,
|
||||
}
|
||||
ret.StashIDs.Set(StashID{
|
||||
Endpoint: endpoint,
|
||||
StashID: *p.RemoteSiteID,
|
||||
Endpoint: endpoint,
|
||||
StashID: *p.RemoteSiteID,
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,11 @@ func Test_scrapedToStudioInput(t *testing.T) {
|
||||
|
||||
got.CreatedAt = time.Time{}
|
||||
got.UpdatedAt = time.Time{}
|
||||
if got.StashIDs.Loaded() && len(got.StashIDs.List()) > 0 {
|
||||
for stid := range got.StashIDs.List() {
|
||||
got.StashIDs.List()[stid].UpdatedAt = time.Time{}
|
||||
}
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
@@ -243,6 +248,12 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
||||
|
||||
got.CreatedAt = time.Time{}
|
||||
got.UpdatedAt = time.Time{}
|
||||
|
||||
if got.StashIDs.Loaded() && len(got.StashIDs.List()) > 0 {
|
||||
for stid := range got.StashIDs.List() {
|
||||
got.StashIDs.List()[stid].UpdatedAt = time.Time{}
|
||||
}
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
@@ -263,7 +274,7 @@ func TestScrapedStudio_ToPartial(t *testing.T) {
|
||||
images = []string{image}
|
||||
|
||||
existingEndpoint = "existingEndpoint"
|
||||
existingStashID = StashID{"existingStashID", existingEndpoint}
|
||||
existingStashID = StashID{"existingStashID", existingEndpoint, time.Time{}}
|
||||
existingStashIDs = []StashID{existingStashID}
|
||||
)
|
||||
|
||||
@@ -362,6 +373,11 @@ func TestScrapedStudio_ToPartial(t *testing.T) {
|
||||
|
||||
// unset updatedAt - we don't need to compare it
|
||||
got.UpdatedAt = OptionalTime{}
|
||||
if got.StashIDs != nil && len(got.StashIDs.StashIDs) > 0 {
|
||||
for stid := range got.StashIDs.StashIDs {
|
||||
got.StashIDs.StashIDs[stid].UpdatedAt = time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
|
||||
@@ -226,14 +226,14 @@ type PerformerCreateInput struct {
|
||||
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"`
|
||||
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"`
|
||||
Image *string `json:"image"`
|
||||
StashIds []StashIDInput `json:"stash_ids"`
|
||||
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 {
|
||||
@@ -263,12 +263,12 @@ type PerformerUpdateInput struct {
|
||||
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"`
|
||||
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"`
|
||||
Image *string `json:"image"`
|
||||
StashIds []StashIDInput `json:"stash_ids"`
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -163,8 +163,8 @@ type SceneCreateInput struct {
|
||||
Groups []SceneGroupInput `json:"groups"`
|
||||
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"`
|
||||
CoverImage *string `json:"cover_image"`
|
||||
StashIds []StashIDInput `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.
|
||||
@@ -191,12 +191,12 @@ type SceneUpdateInput struct {
|
||||
Groups []SceneGroupInput `json:"groups"`
|
||||
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"`
|
||||
CoverImage *string `json:"cover_image"`
|
||||
StashIds []StashIDInput `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 {
|
||||
|
||||
@@ -1,8 +1,89 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StashID struct {
|
||||
StashID string `db:"stash_id" json:"stash_id"`
|
||||
Endpoint string `db:"endpoint" json:"endpoint"`
|
||||
StashID string `db:"stash_id" json:"stash_id"`
|
||||
Endpoint string `db:"endpoint" json:"endpoint"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (s StashID) ToStashIDInput() StashIDInput {
|
||||
t := s.UpdatedAt
|
||||
return StashIDInput{
|
||||
StashID: s.StashID,
|
||||
Endpoint: s.Endpoint,
|
||||
UpdatedAt: &t,
|
||||
}
|
||||
}
|
||||
|
||||
type StashIDs []StashID
|
||||
|
||||
func (s StashIDs) ToStashIDInputs() StashIDInputs {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := make(StashIDInputs, len(s))
|
||||
for i, v := range s {
|
||||
ret[i] = v.ToStashIDInput()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// HasSameStashIDs returns true if the two lists of StashIDs are the same, ignoring order and updated at time.
|
||||
func (s StashIDs) HasSameStashIDs(other StashIDs) bool {
|
||||
if len(s) != len(other) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range s {
|
||||
if !slices.ContainsFunc(other, func(o StashID) bool {
|
||||
return o.StashID == v.StashID && o.Endpoint == v.Endpoint
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type StashIDInput struct {
|
||||
StashID string `db:"stash_id" json:"stash_id"`
|
||||
Endpoint string `db:"endpoint" json:"endpoint"`
|
||||
UpdatedAt *time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (s StashIDInput) ToStashID() StashID {
|
||||
ret := StashID{
|
||||
StashID: s.StashID,
|
||||
Endpoint: s.Endpoint,
|
||||
}
|
||||
if s.UpdatedAt != nil {
|
||||
ret.UpdatedAt = *s.UpdatedAt
|
||||
} else {
|
||||
// default to now if not provided
|
||||
ret.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type StashIDInputs []StashIDInput
|
||||
|
||||
func (s StashIDInputs) ToStashIDs() StashIDs {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := make(StashIDs, len(s))
|
||||
for i, v := range s {
|
||||
ret[i] = v.ToStashID()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type UpdateStashIDs struct {
|
||||
|
||||
@@ -51,14 +51,14 @@ type StudioCreateInput struct {
|
||||
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"`
|
||||
Rating100 *int `json:"rating100"`
|
||||
Favorite *bool `json:"favorite"`
|
||||
Details *string `json:"details"`
|
||||
Aliases []string `json:"aliases"`
|
||||
TagIds []string `json:"tag_ids"`
|
||||
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
|
||||
Image *string `json:"image"`
|
||||
StashIds []StashIDInput `json:"stash_ids"`
|
||||
Rating100 *int `json:"rating100"`
|
||||
Favorite *bool `json:"favorite"`
|
||||
Details *string `json:"details"`
|
||||
Aliases []string `json:"aliases"`
|
||||
TagIds []string `json:"tag_ids"`
|
||||
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
|
||||
}
|
||||
|
||||
type StudioUpdateInput struct {
|
||||
@@ -67,12 +67,12 @@ type StudioUpdateInput struct {
|
||||
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"`
|
||||
Rating100 *int `json:"rating100"`
|
||||
Favorite *bool `json:"favorite"`
|
||||
Details *string `json:"details"`
|
||||
Aliases []string `json:"aliases"`
|
||||
TagIds []string `json:"tag_ids"`
|
||||
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
|
||||
Image *string `json:"image"`
|
||||
StashIds []StashIDInput `json:"stash_ids"`
|
||||
Rating100 *int `json:"rating100"`
|
||||
Favorite *bool `json:"favorite"`
|
||||
Details *string `json:"details"`
|
||||
Aliases []string `json:"aliases"`
|
||||
TagIds []string `json:"tag_ids"`
|
||||
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
@@ -236,16 +237,19 @@ func TestUpdateSet_UpdateInput(t *testing.T) {
|
||||
tagIDStrs := intslice.IntSliceToStringSlice(tagIDs)
|
||||
stashID := "stashID"
|
||||
endpoint := "endpoint"
|
||||
updatedAt := time.Now()
|
||||
stashIDs := []models.StashID{
|
||||
{
|
||||
StashID: stashID,
|
||||
Endpoint: endpoint,
|
||||
StashID: stashID,
|
||||
Endpoint: endpoint,
|
||||
UpdatedAt: updatedAt,
|
||||
},
|
||||
}
|
||||
stashIDInputs := []models.StashID{
|
||||
stashIDInputs := []models.StashIDInput{
|
||||
{
|
||||
StashID: stashID,
|
||||
Endpoint: endpoint,
|
||||
StashID: stashID,
|
||||
Endpoint: endpoint,
|
||||
UpdatedAt: &updatedAt,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const (
|
||||
cacheSizeEnv = "STASH_SQLITE_CACHE_SIZE"
|
||||
)
|
||||
|
||||
var appSchemaVersion uint = 68
|
||||
var appSchemaVersion uint = 69
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsBox embed.FS
|
||||
|
||||
3
pkg/sqlite/migrations/69_stash_id_updated_at.up.sql
Normal file
3
pkg/sqlite/migrations/69_stash_id_updated_at.up.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `performer_stash_ids` ADD COLUMN `updated_at` datetime not null default '1970-01-01T00:00:00Z';
|
||||
ALTER TABLE `scene_stash_ids` ADD COLUMN `updated_at` datetime not null default '1970-01-01T00:00:00Z';
|
||||
ALTER TABLE `studio_stash_ids` ADD COLUMN `updated_at` datetime not null default '1970-01-01T00:00:00Z';
|
||||
@@ -14,11 +14,6 @@ import (
|
||||
|
||||
const idColumn = "id"
|
||||
|
||||
type objectList interface {
|
||||
Append(o interface{})
|
||||
New() interface{}
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
tableName string
|
||||
idColumn string
|
||||
@@ -124,17 +119,6 @@ func (r *repository) queryFunc(ctx context.Context, query string, args []interfa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *repository) query(ctx context.Context, query string, args []interface{}, out objectList) error {
|
||||
return r.queryFunc(ctx, query, args, false, func(rows *sqlx.Rows) error {
|
||||
object := out.New()
|
||||
if err := rows.StructScan(object); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Append(object)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *repository) queryStruct(ctx context.Context, query string, args []interface{}, out interface{}) error {
|
||||
if err := r.queryFunc(ctx, query, args, true, func(rows *sqlx.Rows) error {
|
||||
if err := rows.StructScan(out); err != nil {
|
||||
@@ -421,7 +405,7 @@ type stashIDRepository struct {
|
||||
type stashIDs []models.StashID
|
||||
|
||||
func (s *stashIDs) Append(o interface{}) {
|
||||
*s = append(*s, *o.(*models.StashID))
|
||||
*s = append(*s, o.(models.StashID))
|
||||
}
|
||||
|
||||
func (s *stashIDs) New() interface{} {
|
||||
@@ -429,10 +413,17 @@ func (s *stashIDs) New() interface{} {
|
||||
}
|
||||
|
||||
func (r *stashIDRepository) get(ctx context.Context, id int) ([]models.StashID, error) {
|
||||
query := fmt.Sprintf("SELECT stash_id, endpoint from %s WHERE %s = ?", r.tableName, r.idColumn)
|
||||
query := fmt.Sprintf("SELECT stash_id, endpoint, updated_at from %s WHERE %s = ?", r.tableName, r.idColumn)
|
||||
var ret stashIDs
|
||||
err := r.query(ctx, query, []interface{}{id}, &ret)
|
||||
return []models.StashID(ret), err
|
||||
err := r.queryFunc(ctx, query, []interface{}{id}, false, func(rows *sqlx.Rows) error {
|
||||
var v stashIDRow
|
||||
if err := rows.StructScan(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Append(v.resolve())
|
||||
return nil
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
type filesRepository struct {
|
||||
|
||||
@@ -275,19 +275,21 @@ type stashIDTable struct {
|
||||
}
|
||||
|
||||
type stashIDRow struct {
|
||||
StashID null.String `db:"stash_id"`
|
||||
Endpoint null.String `db:"endpoint"`
|
||||
StashID null.String `db:"stash_id"`
|
||||
Endpoint null.String `db:"endpoint"`
|
||||
UpdatedAt Timestamp `db:"updated_at"`
|
||||
}
|
||||
|
||||
func (r *stashIDRow) resolve() models.StashID {
|
||||
return models.StashID{
|
||||
StashID: r.StashID.String,
|
||||
Endpoint: r.Endpoint.String,
|
||||
StashID: r.StashID.String,
|
||||
Endpoint: r.Endpoint.String,
|
||||
UpdatedAt: r.UpdatedAt.Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *stashIDTable) get(ctx context.Context, id int) ([]models.StashID, error) {
|
||||
q := dialect.Select("endpoint", "stash_id").From(t.table.table).Where(t.idColumn.Eq(id))
|
||||
q := dialect.Select("endpoint", "stash_id", "updated_at").From(t.table.table).Where(t.idColumn.Eq(id))
|
||||
|
||||
const single = false
|
||||
var ret []models.StashID
|
||||
@@ -308,8 +310,8 @@ func (t *stashIDTable) get(ctx context.Context, id int) ([]models.StashID, error
|
||||
}
|
||||
|
||||
func (t *stashIDTable) insertJoin(ctx context.Context, id int, v models.StashID) (sql.Result, error) {
|
||||
q := dialect.Insert(t.table.table).Cols(t.idColumn.GetCol(), "endpoint", "stash_id").Vals(
|
||||
goqu.Vals{id, v.Endpoint, v.StashID},
|
||||
var q = dialect.Insert(t.table.table).Cols(t.idColumn.GetCol(), "endpoint", "stash_id", "updated_at").Vals(
|
||||
goqu.Vals{id, v.Endpoint, v.StashID, v.UpdatedAt},
|
||||
)
|
||||
ret, err := exec(ctx, q)
|
||||
if err != nil {
|
||||
|
||||
@@ -27,6 +27,7 @@ fragment SlimPerformerData on Performer {
|
||||
stash_ids {
|
||||
endpoint
|
||||
stash_id
|
||||
updated_at
|
||||
}
|
||||
rating100
|
||||
death_date
|
||||
|
||||
@@ -34,6 +34,7 @@ fragment PerformerData on Performer {
|
||||
stash_ids {
|
||||
stash_id
|
||||
endpoint
|
||||
updated_at
|
||||
}
|
||||
rating100
|
||||
details
|
||||
|
||||
@@ -84,5 +84,6 @@ fragment SlimSceneData on Scene {
|
||||
stash_ids {
|
||||
endpoint
|
||||
stash_id
|
||||
updated_at
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ fragment SceneData on Scene {
|
||||
stash_ids {
|
||||
endpoint
|
||||
stash_id
|
||||
updated_at
|
||||
}
|
||||
|
||||
sceneStreams {
|
||||
|
||||
@@ -5,6 +5,7 @@ fragment SlimStudioData on Studio {
|
||||
stash_ids {
|
||||
endpoint
|
||||
stash_id
|
||||
updated_at
|
||||
}
|
||||
parent_studio {
|
||||
id
|
||||
|
||||
@@ -28,6 +28,7 @@ fragment StudioData on Studio {
|
||||
stash_ids {
|
||||
stash_id
|
||||
endpoint
|
||||
updated_at
|
||||
}
|
||||
details
|
||||
rating100
|
||||
|
||||
@@ -282,7 +282,10 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||
formik.setFieldValue("penis_length", state.penis_length);
|
||||
}
|
||||
|
||||
const remoteSiteID = state.remote_site_id;
|
||||
updateStashIDs(state.remote_site_id);
|
||||
}
|
||||
|
||||
function updateStashIDs(remoteSiteID: string | null | undefined) {
|
||||
if (remoteSiteID && (scraper as IStashBox).endpoint) {
|
||||
const newIDs =
|
||||
formik.values.stash_ids?.filter(
|
||||
@@ -291,6 +294,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||
newIDs?.push({
|
||||
endpoint: (scraper as IStashBox).endpoint,
|
||||
stash_id: remoteSiteID,
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
formik.setFieldValue("stash_ids", newIDs);
|
||||
}
|
||||
@@ -438,6 +442,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
||||
setScraper(undefined);
|
||||
} else {
|
||||
setScrapedPerformer(result);
|
||||
updateStashIDs(performerResult.remote_site_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -508,6 +508,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
return {
|
||||
endpoint,
|
||||
stash_id: updatedScene.remote_site_id,
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -521,6 +522,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
formik.values.stash_ids.concat({
|
||||
endpoint,
|
||||
stash_id: updatedScene.remote_site_id,
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ExternalLink } from "./ExternalLink";
|
||||
export type LinkType = "performers" | "scenes" | "studios";
|
||||
|
||||
export const StashIDPill: React.FC<{
|
||||
stashID: StashId;
|
||||
stashID: Pick<StashId, "endpoint" | "stash_id">;
|
||||
linkType: LinkType;
|
||||
}> = ({ stashID, linkType }) => {
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
|
||||
@@ -272,6 +272,7 @@ const PerformerModal: React.FC<IPerformerModalProps> = ({
|
||||
{
|
||||
endpoint,
|
||||
stash_id: remoteSiteID,
|
||||
updated_at: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -613,12 +613,14 @@ export const TaggerContext: React.FC = ({ children }) => {
|
||||
return {
|
||||
endpoint: e.endpoint,
|
||||
stash_id: e.stash_id,
|
||||
updated_at: e.updated_at,
|
||||
};
|
||||
});
|
||||
|
||||
stashIDs.push({
|
||||
stash_id: performer.remote_site_id,
|
||||
endpoint: currentSource?.sourceInput.stash_box_endpoint,
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await updatePerformer({
|
||||
@@ -770,12 +772,14 @@ export const TaggerContext: React.FC = ({ children }) => {
|
||||
return {
|
||||
endpoint: e.endpoint,
|
||||
stash_id: e.stash_id,
|
||||
updated_at: e.updated_at,
|
||||
};
|
||||
});
|
||||
|
||||
stashIDs.push({
|
||||
stash_id: studio.remote_site_id,
|
||||
endpoint: currentSource?.sourceInput.stash_box_endpoint,
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await updateStudio({
|
||||
|
||||
@@ -413,6 +413,7 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
|
||||
return {
|
||||
endpoint: s.endpoint,
|
||||
stash_id: s.stash_id,
|
||||
updated_at: s.updated_at,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
@@ -421,6 +422,7 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
|
||||
{
|
||||
endpoint: currentSource.sourceInput.stash_box_endpoint,
|
||||
stash_id: scene.remote_site_id,
|
||||
updated_at: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
|
||||
@@ -198,11 +198,13 @@ const StudioModal: React.FC<IStudioModalProps> = ({
|
||||
|
||||
// stashid handling code
|
||||
const remoteSiteID = studio.remote_site_id;
|
||||
const timeNow = new Date().toISOString();
|
||||
if (remoteSiteID && endpoint) {
|
||||
studioData.stash_ids = [
|
||||
{
|
||||
endpoint,
|
||||
stash_id: remoteSiteID,
|
||||
updated_at: timeNow,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -230,6 +232,7 @@ const StudioModal: React.FC<IStudioModalProps> = ({
|
||||
{
|
||||
endpoint,
|
||||
stash_id: parentRemoteSiteID,
|
||||
updated_at: timeNow,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
export const getStashIDs = (ids?: { stash_id: string; endpoint: string }[]) =>
|
||||
(ids ?? []).map(({ stash_id, endpoint }) => ({
|
||||
export const getStashIDs = (
|
||||
ids?: { stash_id: string; endpoint: string; updated_at: string }[]
|
||||
) =>
|
||||
(ids ?? []).map(({ stash_id, endpoint, updated_at }) => ({
|
||||
stash_id,
|
||||
endpoint,
|
||||
updated_at,
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user