Add bulk update markers interface (#6210)

This commit is contained in:
WithoutPants 2025-11-06 16:01:24 +11:00 committed by GitHub
parent 2a2a730296
commit 8c4b607454
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 151 additions and 3 deletions

View file

@ -328,6 +328,7 @@ type Mutation {
sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
bulkSceneMarkerUpdate(input: BulkSceneMarkerUpdateInput!): [SceneMarker!]
sceneMarkerDestroy(id: ID!): Boolean! sceneMarkerDestroy(id: ID!): Boolean!
sceneMarkersDestroy(ids: [ID!]!): Boolean! sceneMarkersDestroy(ids: [ID!]!): Boolean!

View file

@ -42,6 +42,13 @@ input SceneMarkerUpdateInput {
tag_ids: [ID!] tag_ids: [ID!]
} }
input BulkSceneMarkerUpdateInput {
ids: [ID!]
title: String
primary_tag_id: ID
tag_ids: BulkUpdateIds
}
type FindSceneMarkersResultType { type FindSceneMarkersResultType {
count: Int! count: Int!
scene_markers: [SceneMarker!]! scene_markers: [SceneMarker!]!

View file

@ -820,6 +820,123 @@ func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMar
return r.getSceneMarker(ctx, markerID) return r.getSceneMarker(ctx, markerID)
} }
func (r *mutationResolver) BulkSceneMarkerUpdate(ctx context.Context, input BulkSceneMarkerUpdateInput) ([]*models.SceneMarker, error) {
ids, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil {
return nil, fmt.Errorf("converting ids: %w", err)
}
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
// Populate performer from the input
partial := models.NewSceneMarkerPartial()
partial.Title = translator.optionalString(input.Title, "title")
partial.PrimaryTagID, err = translator.optionalIntFromString(input.PrimaryTagID, "primary_tag_id")
if err != nil {
return nil, fmt.Errorf("converting primary tag id: %w", err)
}
partial.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids")
if err != nil {
return nil, fmt.Errorf("converting tag ids: %w", err)
}
ret := []*models.SceneMarker{}
// Start the transaction and save the performers
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.SceneMarker
for _, id := range ids {
l := partial
if err := adjustMarkerPartialForTagExclusion(ctx, r.repository.SceneMarker, id, &l); err != nil {
return err
}
updated, err := qb.UpdatePartial(ctx, id, l)
if err != nil {
return err
}
ret = append(ret, updated)
}
return nil
}); err != nil {
return nil, err
}
// execute post hooks outside of txn
var newRet []*models.SceneMarker
for _, m := range ret {
r.hookExecutor.ExecutePostHooks(ctx, m.ID, hook.SceneMarkerUpdatePost, input, translator.getFields())
m, err = r.getSceneMarker(ctx, m.ID)
if err != nil {
return nil, err
}
newRet = append(newRet, m)
}
return newRet, nil
}
// adjustMarkerPartialForTagExclusion adjusts the SceneMarkerPartial to exclude the primary tag from tag updates.
func adjustMarkerPartialForTagExclusion(ctx context.Context, r models.SceneMarkerReader, id int, partial *models.SceneMarkerPartial) error {
if partial.TagIDs == nil && !partial.PrimaryTagID.Set {
return nil
}
// exclude primary tag from tag updates
var primaryTagID int
if partial.PrimaryTagID.Set {
primaryTagID = partial.PrimaryTagID.Value
} else {
existing, err := r.Find(ctx, id)
if err != nil {
return fmt.Errorf("finding existing primary tag id: %w", err)
}
primaryTagID = existing.PrimaryTagID
}
existingTagIDs, err := r.GetTagIDs(ctx, id)
if err != nil {
return fmt.Errorf("getting existing tag ids: %w", err)
}
tagIDAttr := partial.TagIDs
if tagIDAttr == nil {
tagIDAttr = &models.UpdateIDs{
IDs: existingTagIDs,
Mode: models.RelationshipUpdateModeSet,
}
}
newTagIDs := tagIDAttr.Apply(existingTagIDs)
// Remove primary tag from newTagIDs if present
newTagIDs = sliceutil.Exclude(newTagIDs, []int{primaryTagID})
if len(existingTagIDs) != len(newTagIDs) {
partial.TagIDs = &models.UpdateIDs{
IDs: newTagIDs,
Mode: models.RelationshipUpdateModeSet,
}
} else {
// no change to tags required
partial.TagIDs = nil
}
return nil
}
func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (bool, error) { func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (bool, error) {
return r.SceneMarkersDestroy(ctx, []string{id}) return r.SceneMarkersDestroy(ctx, []string{id})
} }

View file

@ -30,6 +30,7 @@ type SceneMarkerPartial struct {
Seconds OptionalFloat64 Seconds OptionalFloat64
EndSeconds OptionalFloat64 EndSeconds OptionalFloat64
PrimaryTagID OptionalInt PrimaryTagID OptionalInt
TagIDs *UpdateIDs
SceneID OptionalInt SceneID OptionalInt
CreatedAt OptionalTime CreatedAt OptionalTime
UpdatedAt OptionalTime UpdatedAt OptionalTime

View file

@ -15,7 +15,11 @@ import (
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
) )
const sceneMarkerTable = "scene_markers" const (
sceneMarkerTable = "scene_markers"
sceneMarkersTagsTable = "scene_markers_tags"
sceneMarkerIDColumn = "scene_marker_id"
)
const countSceneMarkersForTagQuery = ` const countSceneMarkersForTagQuery = `
SELECT scene_markers.id FROM scene_markers SELECT scene_markers.id FROM scene_markers
@ -101,8 +105,8 @@ var (
}, },
tags: joinRepository{ tags: joinRepository{
repository: repository{ repository: repository{
tableName: "scene_markers_tags", tableName: sceneMarkersTagsTable,
idColumn: "scene_marker_id", idColumn: sceneMarkerIDColumn,
}, },
fkColumn: tagIDColumn, fkColumn: tagIDColumn,
}, },
@ -157,6 +161,12 @@ func (qb *SceneMarkerStore) UpdatePartial(ctx context.Context, id int, partial m
} }
} }
if partial.TagIDs != nil {
if err := sceneMarkersTagsTableMgr.modifyJoins(ctx, id, partial.TagIDs.IDs, partial.TagIDs.Mode); err != nil {
return nil, fmt.Errorf("modifying scene marker tags: %w", err)
}
}
return qb.find(ctx, id) return qb.find(ctx, id)
} }

View file

@ -28,6 +28,8 @@ var (
scenesGroupsJoinTable = goqu.T(groupsScenesTable) scenesGroupsJoinTable = goqu.T(groupsScenesTable)
scenesURLsJoinTable = goqu.T(scenesURLsTable) scenesURLsJoinTable = goqu.T(scenesURLsTable)
sceneMarkersTagsJoinTable = goqu.T(sceneMarkersTagsTable)
performersAliasesJoinTable = goqu.T(performersAliasesTable) performersAliasesJoinTable = goqu.T(performersAliasesTable)
performersURLsJoinTable = goqu.T(performerURLsTable) performersURLsJoinTable = goqu.T(performerURLsTable)
performersTagsJoinTable = goqu.T(performersTagsTable) performersTagsJoinTable = goqu.T(performersTagsTable)
@ -160,6 +162,16 @@ var (
idColumn: goqu.T(sceneMarkerTable).Col(idColumn), idColumn: goqu.T(sceneMarkerTable).Col(idColumn),
} }
sceneMarkersTagsTableMgr = &joinTable{
table: table{
table: sceneMarkersTagsJoinTable,
idColumn: sceneMarkersTagsJoinTable.Col(sceneMarkerIDColumn),
},
fkColumn: sceneMarkersTagsJoinTable.Col(tagIDColumn),
foreignTable: tagTableMgr,
orderBy: tagTableSort,
}
scenesFilesTableMgr = &relatedFilesTable{ scenesFilesTableMgr = &relatedFilesTable{
table: table{ table: table{
table: scenesFilesJoinTable, table: scenesFilesJoinTable,