mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 00:13:46 +01:00
414 lines
12 KiB
Go
414 lines
12 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/stashapp/stash/internal/static"
|
|
"github.com/stashapp/stash/pkg/group"
|
|
"github.com/stashapp/stash/pkg/models"
|
|
"github.com/stashapp/stash/pkg/plugin/hook"
|
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
|
"github.com/stashapp/stash/pkg/utils"
|
|
)
|
|
|
|
func groupFromGroupCreateInput(ctx context.Context, input GroupCreateInput) (*models.Group, error) {
|
|
translator := changesetTranslator{
|
|
inputMap: getUpdateInputMap(ctx),
|
|
}
|
|
|
|
// Populate a new group from the input
|
|
newGroup := models.NewGroup()
|
|
|
|
newGroup.Name = strings.TrimSpace(input.Name)
|
|
newGroup.Aliases = translator.string(input.Aliases)
|
|
newGroup.Duration = input.Duration
|
|
newGroup.Rating = input.Rating100
|
|
newGroup.Director = translator.string(input.Director)
|
|
newGroup.Synopsis = translator.string(input.Synopsis)
|
|
|
|
var err error
|
|
|
|
newGroup.Date, err = translator.datePtr(input.Date)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting date: %w", err)
|
|
}
|
|
newGroup.StudioID, err = translator.intPtrFromString(input.StudioID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting studio id: %w", err)
|
|
}
|
|
|
|
newGroup.TagIDs, err = translator.relatedIds(input.TagIds)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
|
}
|
|
|
|
newGroup.ContainingGroups, err = translator.groupIDDescriptions(input.ContainingGroups)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting containing group ids: %w", err)
|
|
}
|
|
|
|
newGroup.SubGroups, err = translator.groupIDDescriptions(input.SubGroups)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting containing group ids: %w", err)
|
|
}
|
|
|
|
if input.Urls != nil {
|
|
newGroup.URLs = models.NewRelatedStrings(stringslice.TrimSpace(input.Urls))
|
|
}
|
|
|
|
return &newGroup, nil
|
|
}
|
|
|
|
func (r *mutationResolver) GroupCreate(ctx context.Context, input GroupCreateInput) (*models.Group, error) {
|
|
newGroup, err := groupFromGroupCreateInput(ctx, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Process the base 64 encoded image string
|
|
var frontimageData []byte
|
|
if input.FrontImage != nil {
|
|
frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("processing front image: %w", err)
|
|
}
|
|
}
|
|
|
|
// Process the base 64 encoded image string
|
|
var backimageData []byte
|
|
if input.BackImage != nil {
|
|
backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("processing back image: %w", err)
|
|
}
|
|
}
|
|
|
|
// HACK: if back image is being set, set the front image to the default.
|
|
// This is because we can't have a null front image with a non-null back image.
|
|
if len(frontimageData) == 0 && len(backimageData) != 0 {
|
|
frontimageData = static.ReadAll(static.DefaultGroupImage)
|
|
}
|
|
|
|
// Start the transaction and save the group
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
if err = r.groupService.Create(ctx, newGroup, frontimageData, backimageData); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// for backwards compatibility - run both movie and group hooks
|
|
r.hookExecutor.ExecutePostHooks(ctx, newGroup.ID, hook.GroupCreatePost, input, nil)
|
|
r.hookExecutor.ExecutePostHooks(ctx, newGroup.ID, hook.MovieCreatePost, input, nil)
|
|
return r.getGroup(ctx, newGroup.ID)
|
|
}
|
|
|
|
func groupPartialFromGroupUpdateInput(translator changesetTranslator, input GroupUpdateInput) (ret models.GroupPartial, err error) {
|
|
// Populate group from the input
|
|
updatedGroup := models.NewGroupPartial()
|
|
|
|
updatedGroup.Name = translator.optionalString(input.Name, "name")
|
|
updatedGroup.Aliases = translator.optionalString(input.Aliases, "aliases")
|
|
updatedGroup.Duration = translator.optionalInt(input.Duration, "duration")
|
|
updatedGroup.Rating = translator.optionalInt(input.Rating100, "rating100")
|
|
updatedGroup.Director = translator.optionalString(input.Director, "director")
|
|
updatedGroup.Synopsis = translator.optionalString(input.Synopsis, "synopsis")
|
|
|
|
updatedGroup.Date, err = translator.optionalDate(input.Date, "date")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting date: %w", err)
|
|
return
|
|
}
|
|
updatedGroup.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting studio id: %w", err)
|
|
return
|
|
}
|
|
|
|
updatedGroup.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting tag ids: %w", err)
|
|
return
|
|
}
|
|
|
|
updatedGroup.ContainingGroups, err = translator.updateGroupIDDescriptions(input.ContainingGroups, "containing_groups")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting containing group ids: %w", err)
|
|
return
|
|
}
|
|
|
|
updatedGroup.SubGroups, err = translator.updateGroupIDDescriptions(input.SubGroups, "sub_groups")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting containing group ids: %w", err)
|
|
return
|
|
}
|
|
|
|
updatedGroup.URLs = translator.updateStrings(input.Urls, "urls")
|
|
|
|
return updatedGroup, nil
|
|
}
|
|
|
|
func (r *mutationResolver) GroupUpdate(ctx context.Context, input GroupUpdateInput) (*models.Group, error) {
|
|
groupID, err := strconv.Atoi(input.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
translator := changesetTranslator{
|
|
inputMap: getUpdateInputMap(ctx),
|
|
}
|
|
|
|
updatedGroup, err := groupPartialFromGroupUpdateInput(translator, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var frontimageData []byte
|
|
frontImageIncluded := translator.hasField("front_image")
|
|
if input.FrontImage != nil {
|
|
frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("processing front image: %w", err)
|
|
}
|
|
}
|
|
|
|
var backimageData []byte
|
|
backImageIncluded := translator.hasField("back_image")
|
|
if input.BackImage != nil {
|
|
backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("processing back image: %w", err)
|
|
}
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
frontImage := group.ImageInput{
|
|
Image: frontimageData,
|
|
Set: frontImageIncluded,
|
|
}
|
|
|
|
backImage := group.ImageInput{
|
|
Image: backimageData,
|
|
Set: backImageIncluded,
|
|
}
|
|
|
|
_, err = r.groupService.UpdatePartial(ctx, groupID, updatedGroup, frontImage, backImage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// for backwards compatibility - run both movie and group hooks
|
|
r.hookExecutor.ExecutePostHooks(ctx, groupID, hook.GroupUpdatePost, input, translator.getFields())
|
|
r.hookExecutor.ExecutePostHooks(ctx, groupID, hook.MovieUpdatePost, input, translator.getFields())
|
|
return r.getGroup(ctx, groupID)
|
|
}
|
|
|
|
func groupPartialFromBulkGroupUpdateInput(translator changesetTranslator, input BulkGroupUpdateInput) (ret models.GroupPartial, err error) {
|
|
updatedGroup := models.NewGroupPartial()
|
|
|
|
updatedGroup.Rating = translator.optionalInt(input.Rating100, "rating100")
|
|
updatedGroup.Director = translator.optionalString(input.Director, "director")
|
|
|
|
updatedGroup.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting studio id: %w", err)
|
|
return
|
|
}
|
|
|
|
updatedGroup.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting tag ids: %w", err)
|
|
return
|
|
}
|
|
|
|
updatedGroup.ContainingGroups, err = translator.updateGroupIDDescriptionsBulk(input.ContainingGroups, "containing_groups")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting containing group ids: %w", err)
|
|
return
|
|
}
|
|
|
|
updatedGroup.SubGroups, err = translator.updateGroupIDDescriptionsBulk(input.SubGroups, "sub_groups")
|
|
if err != nil {
|
|
err = fmt.Errorf("converting containing group ids: %w", err)
|
|
return
|
|
}
|
|
|
|
updatedGroup.URLs = translator.optionalURLsBulk(input.Urls, nil)
|
|
|
|
return updatedGroup, nil
|
|
}
|
|
|
|
func (r *mutationResolver) BulkGroupUpdate(ctx context.Context, input BulkGroupUpdateInput) ([]*models.Group, error) {
|
|
groupIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting ids: %w", err)
|
|
}
|
|
|
|
translator := changesetTranslator{
|
|
inputMap: getUpdateInputMap(ctx),
|
|
}
|
|
|
|
// Populate group from the input
|
|
updatedGroup, err := groupPartialFromBulkGroupUpdateInput(translator, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ret := []*models.Group{}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
for _, groupID := range groupIDs {
|
|
group, err := r.groupService.UpdatePartial(ctx, groupID, updatedGroup, group.ImageInput{}, group.ImageInput{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ret = append(ret, group)
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var newRet []*models.Group
|
|
for _, group := range ret {
|
|
// for backwards compatibility - run both movie and group hooks
|
|
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.GroupUpdatePost, input, translator.getFields())
|
|
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.MovieUpdatePost, input, translator.getFields())
|
|
|
|
group, err = r.getGroup(ctx, group.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newRet = append(newRet, group)
|
|
}
|
|
|
|
return newRet, nil
|
|
}
|
|
|
|
func (r *mutationResolver) GroupDestroy(ctx context.Context, input GroupDestroyInput) (bool, error) {
|
|
id, err := strconv.Atoi(input.ID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting id: %w", err)
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
return r.repository.Group.Destroy(ctx, id)
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// for backwards compatibility - run both movie and group hooks
|
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, input, nil)
|
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, input, nil)
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *mutationResolver) GroupsDestroy(ctx context.Context, groupIDs []string) (bool, error) {
|
|
ids, err := stringslice.StringSliceToIntSlice(groupIDs)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting ids: %w", err)
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
qb := r.repository.Group
|
|
for _, id := range ids {
|
|
if err := qb.Destroy(ctx, id); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, id := range ids {
|
|
// for backwards compatibility - run both movie and group hooks
|
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, groupIDs, nil)
|
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, groupIDs, nil)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *mutationResolver) AddGroupSubGroups(ctx context.Context, input GroupSubGroupAddInput) (bool, error) {
|
|
groupID, err := strconv.Atoi(input.ContainingGroupID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting group id: %w", err)
|
|
}
|
|
|
|
subGroups, err := groupsDescriptionsFromGroupInput(input.SubGroups)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting sub group ids: %w", err)
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
return r.groupService.AddSubGroups(ctx, groupID, subGroups, input.InsertIndex)
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *mutationResolver) RemoveGroupSubGroups(ctx context.Context, input GroupSubGroupRemoveInput) (bool, error) {
|
|
groupID, err := strconv.Atoi(input.ContainingGroupID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting group id: %w", err)
|
|
}
|
|
|
|
subGroupIDs, err := stringslice.StringSliceToIntSlice(input.SubGroupIds)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting sub group ids: %w", err)
|
|
}
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
return r.groupService.RemoveSubGroups(ctx, groupID, subGroupIDs)
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (r *mutationResolver) ReorderSubGroups(ctx context.Context, input ReorderSubGroupsInput) (bool, error) {
|
|
groupID, err := strconv.Atoi(input.GroupID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting group id: %w", err)
|
|
}
|
|
|
|
subGroupIDs, err := stringslice.StringSliceToIntSlice(input.SubGroupIds)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting sub group ids: %w", err)
|
|
}
|
|
|
|
insertPointID, err := strconv.Atoi(input.InsertAtID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("converting insert at id: %w", err)
|
|
}
|
|
|
|
insertAfter := utils.IsTrue(input.InsertAfter)
|
|
|
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
|
return r.groupService.ReorderSubGroups(ctx, groupID, subGroupIDs, insertPointID, insertAfter)
|
|
}); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|