Use changesets correctly when updating objects (#976)

This commit is contained in:
WithoutPants
2020-12-04 12:42:56 +11:00
committed by GitHub
parent 6eea33aec9
commit 86747acc78
34 changed files with 624 additions and 669 deletions

View File

@@ -25,55 +25,17 @@ mutation GalleryCreate(
}
mutation GalleryUpdate(
$id: ID!,
$title: String,
$details: String,
$url: String,
$date: String,
$rating: Int,
$scene_id: ID,
$studio_id: ID,
$performer_ids: [ID!] = [],
$tag_ids: [ID!] = []) {
$input: GalleryUpdateInput!) {
galleryUpdate(input: {
id: $id,
title: $title,
details: $details,
url: $url,
date: $date,
rating: $rating,
scene_id: $scene_id,
studio_id: $studio_id,
tag_ids: $tag_ids,
performer_ids: $performer_ids
}) {
galleryUpdate(input: $input) {
...GalleryData
}
}
mutation BulkGalleryUpdate(
$ids: [ID!] = [],
$url: String,
$date: String,
$details: String,
$rating: Int,
$scene_id: ID,
$studio_id: ID,
$tag_ids: BulkUpdateIds,
$performer_ids: BulkUpdateIds) {
$input: BulkGalleryUpdateInput!) {
bulkGalleryUpdate(input: {
ids: $ids,
details: $details,
url: $url,
date: $date,
rating: $rating,
scene_id: $scene_id,
studio_id: $studio_id,
tag_ids: $tag_ids,
performer_ids: $performer_ids
}) {
bulkGalleryUpdate(input: $input) {
...GalleryData
}
}

View File

@@ -1,43 +1,15 @@
mutation ImageUpdate(
$id: ID!,
$title: String,
$rating: Int,
$studio_id: ID,
$gallery_ids: [ID!] = [],
$performer_ids: [ID!] = [],
$tag_ids: [ID!] = []) {
$input: ImageUpdateInput!) {
imageUpdate(input: {
id: $id,
title: $title,
rating: $rating,
studio_id: $studio_id,
gallery_ids: $gallery_ids,
performer_ids: $performer_ids,
tag_ids: $tag_ids
}) {
imageUpdate(input: $input) {
...SlimImageData
}
}
mutation BulkImageUpdate(
$ids: [ID!] = [],
$title: String,
$rating: Int,
$studio_id: ID,
$gallery_ids: BulkUpdateIds,
$performer_ids: BulkUpdateIds,
$tag_ids: BulkUpdateIds) {
$input: BulkImageUpdateInput!) {
bulkImageUpdate(input: {
ids: $ids,
title: $title,
rating: $rating,
studio_id: $studio_id,
gallery_ids: $gallery_ids,
performer_ids: $performer_ids,
tag_ids: $tag_ids
}) {
bulkImageUpdate(input: $input) {
...SlimImageData
}
}

View File

@@ -16,21 +16,8 @@ mutation MovieCreate(
}
}
mutation MovieUpdate(
$id: ID!
$name: String,
$aliases: String,
$duration: Int,
$date: String,
$rating: Int,
$studio_id: ID,
$director: String,
$synopsis: String,
$url: String,
$front_image: String,
$back_image: String) {
movieUpdate(input: { id: $id, name: $name, aliases: $aliases, duration: $duration, date: $date, rating: $rating, studio_id: $studio_id, director: $director, synopsis: $synopsis, url: $url, front_image: $front_image, back_image: $back_image }) {
mutation MovieUpdate($input: MovieUpdateInput!) {
movieUpdate(input: $input) {
...MovieData
}
}

View File

@@ -45,49 +45,9 @@ mutation PerformerCreate(
}
mutation PerformerUpdate(
$id: ID!,
$name: String,
$url: String,
$gender: GenderEnum,
$birthdate: String,
$ethnicity: String,
$country: String,
$eye_color: String,
$height: String,
$measurements: String,
$fake_tits: String,
$career_length: String,
$tattoos: String,
$piercings: String,
$aliases: String,
$twitter: String,
$instagram: String,
$favorite: Boolean,
$stash_ids: [StashIDInput!],
$image: String) {
$input: PerformerUpdateInput!) {
performerUpdate(input: {
id: $id,
name: $name,
url: $url,
gender: $gender,
birthdate: $birthdate,
ethnicity: $ethnicity,
country: $country,
eye_color: $eye_color,
height: $height,
measurements: $measurements,
fake_tits: $fake_tits,
career_length: $career_length,
tattoos: $tattoos,
piercings: $piercings,
aliases: $aliases,
twitter: $twitter,
instagram: $instagram,
favorite: $favorite,
stash_ids: $stash_ids,
image: $image
}) {
performerUpdate(input: $input) {
...PerformerData
}
}

View File

@@ -1,62 +1,16 @@
mutation SceneUpdate(
$id: ID!,
$title: String,
$details: String,
$url: String,
$date: String,
$rating: Int,
$studio_id: ID,
$gallery_id: ID,
$performer_ids: [ID!] = [],
$movies: [SceneMovieInput!] = [],
$tag_ids: [ID!] = [],
$stash_ids: [StashIDInput!],
$cover_image: String) {
$input: SceneUpdateInput!) {
sceneUpdate(input: {
id: $id,
title: $title,
details: $details,
url: $url,
date: $date,
rating: $rating,
studio_id: $studio_id,
gallery_id: $gallery_id,
performer_ids: $performer_ids,
movies: $movies,
tag_ids: $tag_ids,
stash_ids: $stash_ids,
cover_image: $cover_image
}) {
...SceneData
sceneUpdate(input: $input) {
...SceneData
}
}
mutation BulkSceneUpdate(
$ids: [ID!] = [],
$title: String,
$details: String,
$url: String,
$date: String,
$rating: Int,
$studio_id: ID,
$gallery_id: ID,
$performer_ids: BulkUpdateIds,
$tag_ids: BulkUpdateIds) {
$input: BulkSceneUpdateInput!) {
bulkSceneUpdate(input: {
ids: $ids,
title: $title,
details: $details,
url: $url,
date: $date,
rating: $rating,
studio_id: $studio_id,
gallery_id: $gallery_id,
performer_ids: $performer_ids,
tag_ids: $tag_ids
}) {
...SceneData
bulkSceneUpdate(input: $input) {
...SceneData
}
}

View File

@@ -11,14 +11,9 @@ mutation StudioCreate(
}
mutation StudioUpdate(
$id: ID!
$name: String,
$url: String,
$image: String,
$stash_ids: [StashIDInput!],
$parent_id: ID) {
$input: StudioUpdateInput!) {
studioUpdate(input: { id: $id, name: $name, url: $url, image: $image, stash_ids: $stash_ids, parent_id: $parent_id }) {
studioUpdate(input: $input) {
...StudioData
}
}

View File

@@ -8,8 +8,8 @@ mutation TagDestroy($id: ID!) {
tagDestroy(input: { id: $id })
}
mutation TagUpdate($id: ID!, $name: String!, $image: String) {
tagUpdate(input: { id: $id, name: $name, image: $image }) {
mutation TagUpdate($input: TagUpdateInput!) {
tagUpdate(input: $input) {
...TagData
}
}

View File

@@ -0,0 +1,134 @@
package api
import (
"context"
"database/sql"
"strconv"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/models"
)
const updateInputField = "input"
func getArgumentMap(ctx context.Context) map[string]interface{} {
rctx := graphql.GetResolverContext(ctx)
reqCtx := graphql.GetRequestContext(ctx)
return rctx.Field.ArgumentMap(reqCtx.Variables)
}
func getUpdateInputMap(ctx context.Context) map[string]interface{} {
args := getArgumentMap(ctx)
input, _ := args[updateInputField]
var ret map[string]interface{}
if input != nil {
ret, _ = input.(map[string]interface{})
}
if ret == nil {
ret = make(map[string]interface{})
}
return ret
}
func getUpdateInputMaps(ctx context.Context) []map[string]interface{} {
args := getArgumentMap(ctx)
input, _ := args[updateInputField]
var ret []map[string]interface{}
if input != nil {
ret, _ = input.([]map[string]interface{})
}
return ret
}
type changesetTranslator struct {
inputMap map[string]interface{}
}
func (t changesetTranslator) hasField(field string) bool {
if t.inputMap == nil {
return false
}
_, found := t.inputMap[field]
return found
}
func (t changesetTranslator) nullString(value *string, field string) *sql.NullString {
if !t.hasField(field) {
return nil
}
ret := &sql.NullString{}
if value != nil {
ret.String = *value
ret.Valid = true
}
return ret
}
func (t changesetTranslator) sqliteDate(value *string, field string) *models.SQLiteDate {
if !t.hasField(field) {
return nil
}
ret := &models.SQLiteDate{}
if value != nil {
ret.String = *value
ret.Valid = true
}
return ret
}
func (t changesetTranslator) nullInt64(value *int, field string) *sql.NullInt64 {
if !t.hasField(field) {
return nil
}
ret := &sql.NullInt64{}
if value != nil {
ret.Int64 = int64(*value)
ret.Valid = true
}
return ret
}
func (t changesetTranslator) nullInt64FromString(value *string, field string) *sql.NullInt64 {
if !t.hasField(field) {
return nil
}
ret := &sql.NullInt64{}
if value != nil {
ret.Int64, _ = strconv.ParseInt(*value, 10, 64)
ret.Valid = true
}
return ret
}
func (t changesetTranslator) nullBool(value *bool, field string) *sql.NullBool {
if !t.hasField(field) {
return nil
}
ret := &sql.NullBool{}
if value != nil {
ret.Bool = *value
ret.Valid = true
}
return ret
}

View File

@@ -119,7 +119,10 @@ func (r *mutationResolver) GalleryUpdate(ctx context.Context, input models.Galle
// Start the transaction and save the gallery
tx := database.DB.MustBeginTx(ctx, nil)
ret, err := r.galleryUpdate(input, tx)
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
ret, err := r.galleryUpdate(input, translator, tx)
if err != nil {
_ = tx.Rollback()
@@ -137,11 +140,16 @@ func (r *mutationResolver) GalleryUpdate(ctx context.Context, input models.Galle
func (r *mutationResolver) GalleriesUpdate(ctx context.Context, input []*models.GalleryUpdateInput) ([]*models.Gallery, error) {
// Start the transaction and save the gallery
tx := database.DB.MustBeginTx(ctx, nil)
inputMaps := getUpdateInputMaps(ctx)
var ret []*models.Gallery
for _, gallery := range input {
thisGallery, err := r.galleryUpdate(*gallery, tx)
for i, gallery := range input {
translator := changesetTranslator{
inputMap: inputMaps[i],
}
thisGallery, err := r.galleryUpdate(*gallery, translator, tx)
ret = append(ret, thisGallery)
if err != nil {
@@ -158,7 +166,7 @@ func (r *mutationResolver) GalleriesUpdate(ctx context.Context, input []*models.
return ret, nil
}
func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, tx *sqlx.Tx) (*models.Gallery, error) {
func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, translator changesetTranslator, tx *sqlx.Tx) (*models.Gallery, error) {
qb := models.NewGalleryQueryBuilder()
// Populate gallery from the input
galleryID, _ := strconv.Atoi(input.ID)
@@ -176,6 +184,7 @@ func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, tx *sq
ID: galleryID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
if input.Title != nil {
// ensure title is not empty
if *input.Title == "" {
@@ -190,30 +199,12 @@ func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, tx *sq
updatedGallery.Title = &sql.NullString{String: *input.Title, Valid: true}
}
if input.Details != nil {
updatedGallery.Details = &sql.NullString{String: *input.Details, Valid: true}
}
if input.URL != nil {
updatedGallery.URL = &sql.NullString{String: *input.URL, Valid: true}
}
if input.Date != nil {
updatedGallery.Date = &models.SQLiteDate{String: *input.Date, Valid: true}
}
if input.Rating != nil {
updatedGallery.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
} else {
// rating must be nullable
updatedGallery.Rating = &sql.NullInt64{Valid: false}
}
if input.StudioID != nil {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedGallery.StudioID = &sql.NullInt64{Int64: studioID, Valid: true}
} else {
// studio must be nullable
updatedGallery.StudioID = &sql.NullInt64{Valid: false}
}
updatedGallery.Details = translator.nullString(input.Details, "details")
updatedGallery.URL = translator.nullString(input.URL, "url")
updatedGallery.Date = translator.sqliteDate(input.Date, "date")
updatedGallery.Rating = translator.nullInt64(input.Rating, "rating")
updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
// gallery scene is set from the scene only
@@ -224,31 +215,35 @@ func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, tx *sq
}
// Save the performers
var performerJoins []models.PerformersGalleries
for _, pid := range input.PerformerIds {
performerID, _ := strconv.Atoi(pid)
performerJoin := models.PerformersGalleries{
PerformerID: performerID,
GalleryID: galleryID,
if translator.hasField("performer_ids") {
var performerJoins []models.PerformersGalleries
for _, pid := range input.PerformerIds {
performerID, _ := strconv.Atoi(pid)
performerJoin := models.PerformersGalleries{
PerformerID: performerID,
GalleryID: galleryID,
}
performerJoins = append(performerJoins, performerJoin)
}
if err := jqb.UpdatePerformersGalleries(galleryID, performerJoins, tx); err != nil {
return nil, err
}
performerJoins = append(performerJoins, performerJoin)
}
if err := jqb.UpdatePerformersGalleries(galleryID, performerJoins, tx); err != nil {
return nil, err
}
// Save the tags
var tagJoins []models.GalleriesTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
tagJoin := models.GalleriesTags{
GalleryID: galleryID,
TagID: tagID,
if translator.hasField("tag_ids") {
var tagJoins []models.GalleriesTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
tagJoin := models.GalleriesTags{
GalleryID: galleryID,
TagID: tagID,
}
tagJoins = append(tagJoins, tagJoin)
}
if err := jqb.UpdateGalleriesTags(galleryID, tagJoins, tx); err != nil {
return nil, err
}
tagJoins = append(tagJoins, tagJoin)
}
if err := jqb.UpdateGalleriesTags(galleryID, tagJoins, tx); err != nil {
return nil, err
}
return gallery, nil
@@ -263,44 +258,20 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.B
qb := models.NewGalleryQueryBuilder()
jqb := models.NewJoinsQueryBuilder()
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
updatedGallery := models.GalleryPartial{
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
if input.Details != nil {
updatedGallery.Details = &sql.NullString{String: *input.Details, Valid: true}
}
if input.URL != nil {
updatedGallery.URL = &sql.NullString{String: *input.URL, Valid: true}
}
if input.Date != nil {
updatedGallery.Date = &models.SQLiteDate{String: *input.Date, Valid: true}
}
if input.Rating != nil {
// a rating of 0 means unset the rating
if *input.Rating == 0 {
updatedGallery.Rating = &sql.NullInt64{Int64: 0, Valid: false}
} else {
updatedGallery.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
}
}
if input.StudioID != nil {
// empty string means unset the studio
if *input.StudioID == "" {
updatedGallery.StudioID = &sql.NullInt64{Int64: 0, Valid: false}
} else {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedGallery.StudioID = &sql.NullInt64{Int64: studioID, Valid: true}
}
}
if input.SceneID != nil {
// empty string means unset the studio
if *input.SceneID == "" {
updatedGallery.SceneID = &sql.NullInt64{Int64: 0, Valid: false}
} else {
sceneID, _ := strconv.ParseInt(*input.SceneID, 10, 64)
updatedGallery.SceneID = &sql.NullInt64{Int64: sceneID, Valid: true}
}
}
updatedGallery.Details = translator.nullString(input.Details, "details")
updatedGallery.URL = translator.nullString(input.URL, "url")
updatedGallery.Date = translator.sqliteDate(input.Date, "date")
updatedGallery.Rating = translator.nullInt64(input.Rating, "rating")
updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
updatedGallery.SceneID = translator.nullInt64FromString(input.SceneID, "scene_id")
ret := []*models.Gallery{}
@@ -317,7 +288,7 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.B
ret = append(ret, gallery)
// Save the performers
if wasFieldIncluded(ctx, "performer_ids") {
if translator.hasField("performer_ids") {
performerIDs, err := adjustGalleryPerformerIDs(tx, galleryID, *input.PerformerIds)
if err != nil {
_ = tx.Rollback()
@@ -339,7 +310,7 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.B
}
// Save the tags
if wasFieldIncluded(ctx, "tag_ids") {
if translator.hasField("tag_ids") {
tagIDs, err := adjustGalleryTagIDs(tx, galleryID, *input.TagIds)
if err != nil {
_ = tx.Rollback()

View File

@@ -2,7 +2,6 @@ package api
import (
"context"
"database/sql"
"strconv"
"time"
@@ -17,7 +16,11 @@ func (r *mutationResolver) ImageUpdate(ctx context.Context, input models.ImageUp
// Start the transaction and save the image
tx := database.DB.MustBeginTx(ctx, nil)
ret, err := r.imageUpdate(input, tx)
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
ret, err := r.imageUpdate(input, translator, tx)
if err != nil {
_ = tx.Rollback()
@@ -35,11 +38,16 @@ func (r *mutationResolver) ImageUpdate(ctx context.Context, input models.ImageUp
func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*models.ImageUpdateInput) ([]*models.Image, error) {
// Start the transaction and save the image
tx := database.DB.MustBeginTx(ctx, nil)
inputMaps := getUpdateInputMaps(ctx)
var ret []*models.Image
for _, image := range input {
thisImage, err := r.imageUpdate(*image, tx)
for i, image := range input {
translator := changesetTranslator{
inputMap: inputMaps[i],
}
thisImage, err := r.imageUpdate(*image, translator, tx)
ret = append(ret, thisImage)
if err != nil {
@@ -56,7 +64,7 @@ func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*models.Ima
return ret, nil
}
func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, tx *sqlx.Tx) (*models.Image, error) {
func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, translator changesetTranslator, tx *sqlx.Tx) (*models.Image, error) {
// Populate image from the input
imageID, _ := strconv.Atoi(input.ID)
@@ -65,24 +73,10 @@ func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, tx *sqlx.T
ID: imageID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
if input.Title != nil {
updatedImage.Title = &sql.NullString{String: *input.Title, Valid: true}
}
if input.Rating != nil {
updatedImage.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
} else {
// rating must be nullable
updatedImage.Rating = &sql.NullInt64{Valid: false}
}
if input.StudioID != nil {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedImage.StudioID = &sql.NullInt64{Int64: studioID, Valid: true}
} else {
// studio must be nullable
updatedImage.StudioID = &sql.NullInt64{Valid: false}
}
updatedImage.Title = translator.nullString(input.Title, "title")
updatedImage.Rating = translator.nullInt64(input.Rating, "rating")
updatedImage.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
qb := models.NewImageQueryBuilder()
jqb := models.NewJoinsQueryBuilder()
@@ -94,31 +88,35 @@ func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, tx *sqlx.T
// don't set the galleries directly. Use add/remove gallery images interface instead
// Save the performers
var performerJoins []models.PerformersImages
for _, pid := range input.PerformerIds {
performerID, _ := strconv.Atoi(pid)
performerJoin := models.PerformersImages{
PerformerID: performerID,
ImageID: imageID,
if translator.hasField("performer_ids") {
var performerJoins []models.PerformersImages
for _, pid := range input.PerformerIds {
performerID, _ := strconv.Atoi(pid)
performerJoin := models.PerformersImages{
PerformerID: performerID,
ImageID: imageID,
}
performerJoins = append(performerJoins, performerJoin)
}
if err := jqb.UpdatePerformersImages(imageID, performerJoins, tx); err != nil {
return nil, err
}
performerJoins = append(performerJoins, performerJoin)
}
if err := jqb.UpdatePerformersImages(imageID, performerJoins, tx); err != nil {
return nil, err
}
// Save the tags
var tagJoins []models.ImagesTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
tagJoin := models.ImagesTags{
ImageID: imageID,
TagID: tagID,
if translator.hasField("tag_ids") {
var tagJoins []models.ImagesTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
tagJoin := models.ImagesTags{
ImageID: imageID,
TagID: tagID,
}
tagJoins = append(tagJoins, tagJoin)
}
if err := jqb.UpdateImagesTags(imageID, tagJoins, tx); err != nil {
return nil, err
}
tagJoins = append(tagJoins, tagJoin)
}
if err := jqb.UpdateImagesTags(imageID, tagJoins, tx); err != nil {
return nil, err
}
return image, nil
@@ -136,27 +134,15 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.Bul
updatedImage := models.ImagePartial{
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
if input.Title != nil {
updatedImage.Title = &sql.NullString{String: *input.Title, Valid: true}
}
if input.Rating != nil {
// a rating of 0 means unset the rating
if *input.Rating == 0 {
updatedImage.Rating = &sql.NullInt64{Int64: 0, Valid: false}
} else {
updatedImage.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
}
}
if input.StudioID != nil {
// empty string means unset the studio
if *input.StudioID == "" {
updatedImage.StudioID = &sql.NullInt64{Int64: 0, Valid: false}
} else {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedImage.StudioID = &sql.NullInt64{Int64: studioID, Valid: true}
}
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
updatedImage.Title = translator.nullString(input.Title, "title")
updatedImage.Rating = translator.nullInt64(input.Rating, "rating")
updatedImage.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
ret := []*models.Image{}
for _, imageIDStr := range input.Ids {
@@ -172,7 +158,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.Bul
ret = append(ret, image)
// Save the galleries
if wasFieldIncluded(ctx, "gallery_ids") {
if translator.hasField("gallery_ids") {
galleryIDs, err := adjustImageGalleryIDs(tx, imageID, *input.GalleryIds)
if err != nil {
_ = tx.Rollback()
@@ -193,7 +179,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.Bul
}
// Save the performers
if wasFieldIncluded(ctx, "performer_ids") {
if translator.hasField("performer_ids") {
performerIDs, err := adjustImagePerformerIDs(tx, imageID, *input.PerformerIds)
if err != nil {
_ = tx.Rollback()
@@ -215,7 +201,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.Bul
}
// Save the tags
if wasFieldIncluded(ctx, "tag_ids") {
if translator.hasField("tag_ids") {
tagIDs, err := adjustImageTagIDs(tx, imageID, *input.TagIds)
if err != nil {
_ = tx.Rollback()

View File

@@ -117,16 +117,21 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
ID: movieID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
}
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
var frontimageData []byte
var err error
frontImageIncluded := wasFieldIncluded(ctx, "front_image")
frontImageIncluded := translator.hasField("front_image")
if input.FrontImage != nil {
_, frontimageData, err = utils.ProcessBase64Image(*input.FrontImage)
if err != nil {
return nil, err
}
}
backImageIncluded := wasFieldIncluded(ctx, "back_image")
backImageIncluded := translator.hasField("back_image")
var backimageData []byte
if input.BackImage != nil {
_, backimageData, err = utils.ProcessBase64Image(*input.BackImage)
@@ -142,45 +147,14 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
updatedMovie.Checksum = &checksum
}
if input.Aliases != nil {
updatedMovie.Aliases = &sql.NullString{String: *input.Aliases, Valid: true}
}
if input.Duration != nil {
duration := int64(*input.Duration)
updatedMovie.Duration = &sql.NullInt64{Int64: duration, Valid: true}
}
if input.Date != nil {
updatedMovie.Date = &models.SQLiteDate{String: *input.Date, Valid: true}
}
if input.Rating != nil {
rating := int64(*input.Rating)
updatedMovie.Rating = &sql.NullInt64{Int64: rating, Valid: true}
} else {
// rating must be nullable
updatedMovie.Rating = &sql.NullInt64{Valid: false}
}
if input.StudioID != nil {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedMovie.StudioID = &sql.NullInt64{Int64: studioID, Valid: true}
} else {
// studio must be nullable
updatedMovie.StudioID = &sql.NullInt64{Valid: false}
}
if input.Director != nil {
updatedMovie.Director = &sql.NullString{String: *input.Director, Valid: true}
}
if input.Synopsis != nil {
updatedMovie.Synopsis = &sql.NullString{String: *input.Synopsis, Valid: true}
}
if input.URL != nil {
updatedMovie.URL = &sql.NullString{String: *input.URL, Valid: true}
}
updatedMovie.Aliases = translator.nullString(input.Aliases, "aliases")
updatedMovie.Duration = translator.nullInt64(input.Duration, "duration")
updatedMovie.Date = translator.sqliteDate(input.Date, "date")
updatedMovie.Rating = translator.nullInt64(input.Rating, "rating")
updatedMovie.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
updatedMovie.Director = translator.nullString(input.Director, "director")
updatedMovie.Synopsis = translator.nullString(input.Synopsis, "synopsis")
updatedMovie.URL = translator.nullString(input.URL, "url")
// Start the transaction and save the movie
tx := database.DB.MustBeginTx(ctx, nil)

View File

@@ -130,77 +130,58 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.PerformerUpdateInput) (*models.Performer, error) {
// Populate performer from the input
performerID, _ := strconv.Atoi(input.ID)
updatedPerformer := models.Performer{
updatedPerformer := models.PerformerPartial{
ID: performerID,
UpdatedAt: models.SQLiteTimestamp{Timestamp: time.Now()},
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
}
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
var imageData []byte
var err error
imageIncluded := wasFieldIncluded(ctx, "image")
imageIncluded := translator.hasField("image")
if input.Image != nil {
_, imageData, err = utils.ProcessBase64Image(*input.Image)
if err != nil {
return nil, err
}
}
if input.Name != nil {
// generate checksum from performer name rather than image
checksum := utils.MD5FromString(*input.Name)
updatedPerformer.Name = sql.NullString{String: *input.Name, Valid: true}
updatedPerformer.Checksum = checksum
updatedPerformer.Name = &sql.NullString{String: *input.Name, Valid: true}
updatedPerformer.Checksum = &checksum
}
if input.URL != nil {
updatedPerformer.URL = sql.NullString{String: *input.URL, Valid: true}
}
if input.Gender != nil {
updatedPerformer.Gender = sql.NullString{String: input.Gender.String(), Valid: true}
}
if input.Birthdate != nil {
updatedPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
}
if input.Ethnicity != nil {
updatedPerformer.Ethnicity = sql.NullString{String: *input.Ethnicity, Valid: true}
}
if input.Country != nil {
updatedPerformer.Country = sql.NullString{String: *input.Country, Valid: true}
}
if input.EyeColor != nil {
updatedPerformer.EyeColor = sql.NullString{String: *input.EyeColor, Valid: true}
}
if input.Height != nil {
updatedPerformer.Height = sql.NullString{String: *input.Height, Valid: true}
}
if input.Measurements != nil {
updatedPerformer.Measurements = sql.NullString{String: *input.Measurements, Valid: true}
}
if input.FakeTits != nil {
updatedPerformer.FakeTits = sql.NullString{String: *input.FakeTits, Valid: true}
}
if input.CareerLength != nil {
updatedPerformer.CareerLength = sql.NullString{String: *input.CareerLength, Valid: true}
}
if input.Tattoos != nil {
updatedPerformer.Tattoos = sql.NullString{String: *input.Tattoos, Valid: true}
}
if input.Piercings != nil {
updatedPerformer.Piercings = sql.NullString{String: *input.Piercings, Valid: true}
}
if input.Aliases != nil {
updatedPerformer.Aliases = sql.NullString{String: *input.Aliases, Valid: true}
}
if input.Twitter != nil {
updatedPerformer.Twitter = sql.NullString{String: *input.Twitter, Valid: true}
}
if input.Instagram != nil {
updatedPerformer.Instagram = sql.NullString{String: *input.Instagram, Valid: true}
}
if input.Favorite != nil {
updatedPerformer.Favorite = sql.NullBool{Bool: *input.Favorite, Valid: true}
} else {
updatedPerformer.Favorite = sql.NullBool{Bool: false, Valid: true}
updatedPerformer.URL = translator.nullString(input.URL, "url")
if translator.hasField("gender") {
if input.Gender != nil {
updatedPerformer.Gender = &sql.NullString{String: input.Gender.String(), Valid: true}
} else {
updatedPerformer.Gender = &sql.NullString{String: "", Valid: false}
}
}
updatedPerformer.Birthdate = translator.sqliteDate(input.Birthdate, "birthdate")
updatedPerformer.Country = translator.nullString(input.Country, "country")
updatedPerformer.EyeColor = translator.nullString(input.EyeColor, "eye_color")
updatedPerformer.Measurements = translator.nullString(input.Measurements, "measurements")
updatedPerformer.Height = translator.nullString(input.Height, "height")
updatedPerformer.Ethnicity = translator.nullString(input.Ethnicity, "ethnicity")
updatedPerformer.FakeTits = translator.nullString(input.FakeTits, "fake_tits")
updatedPerformer.CareerLength = translator.nullString(input.CareerLength, "career_length")
updatedPerformer.Tattoos = translator.nullString(input.Tattoos, "tattoos")
updatedPerformer.Piercings = translator.nullString(input.Piercings, "piercings")
updatedPerformer.Aliases = translator.nullString(input.Aliases, "aliases")
updatedPerformer.Twitter = translator.nullString(input.Twitter, "twitter")
updatedPerformer.Instagram = translator.nullString(input.Instagram, "instagram")
updatedPerformer.Favorite = translator.nullBool(input.Favorite, "favorite")
// Start the transaction and save the performer
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewPerformerQueryBuilder()
@@ -227,7 +208,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
}
// Save the stash_ids
if input.StashIds != nil {
if translator.hasField("stash_ids") {
var stashIDJoins []models.StashID
for _, stashID := range input.StashIds {
newJoin := models.StashID{

View File

@@ -19,7 +19,10 @@ func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUp
// Start the transaction and save the scene
tx := database.DB.MustBeginTx(ctx, nil)
ret, err := r.sceneUpdate(input, tx)
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
ret, err := r.sceneUpdate(input, translator, tx)
if err != nil {
_ = tx.Rollback()
@@ -40,8 +43,14 @@ func (r *mutationResolver) ScenesUpdate(ctx context.Context, input []*models.Sce
var ret []*models.Scene
for _, scene := range input {
thisScene, err := r.sceneUpdate(*scene, tx)
inputMaps := getUpdateInputMaps(ctx)
for i, scene := range input {
translator := changesetTranslator{
inputMap: inputMaps[i],
}
thisScene, err := r.sceneUpdate(*scene, translator, tx)
ret = append(ret, thisScene)
if err != nil {
@@ -58,7 +67,7 @@ func (r *mutationResolver) ScenesUpdate(ctx context.Context, input []*models.Sce
return ret, nil
}
func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.Tx) (*models.Scene, error) {
func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator changesetTranslator, tx *sqlx.Tx) (*models.Scene, error) {
// Populate scene from the input
sceneID, _ := strconv.Atoi(input.ID)
@@ -69,18 +78,13 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T
ID: sceneID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
if input.Title != nil {
updatedScene.Title = &sql.NullString{String: *input.Title, Valid: true}
}
if input.Details != nil {
updatedScene.Details = &sql.NullString{String: *input.Details, Valid: true}
}
if input.URL != nil {
updatedScene.URL = &sql.NullString{String: *input.URL, Valid: true}
}
if input.Date != nil {
updatedScene.Date = &models.SQLiteDate{String: *input.Date, Valid: true}
}
updatedScene.Title = translator.nullString(input.Title, "title")
updatedScene.Details = translator.nullString(input.Details, "details")
updatedScene.URL = translator.nullString(input.URL, "url")
updatedScene.Date = translator.sqliteDate(input.Date, "date")
updatedScene.Rating = translator.nullInt64(input.Rating, "rating")
updatedScene.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
if input.CoverImage != nil && *input.CoverImage != "" {
var err error
@@ -92,21 +96,6 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T
// update the cover after updating the scene
}
if input.Rating != nil {
updatedScene.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
} else {
// rating must be nullable
updatedScene.Rating = &sql.NullInt64{Valid: false}
}
if input.StudioID != nil {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedScene.StudioID = &sql.NullInt64{Int64: studioID, Valid: true}
} else {
// studio must be nullable
updatedScene.StudioID = &sql.NullInt64{Valid: false}
}
qb := models.NewSceneQueryBuilder()
jqb := models.NewJoinsQueryBuilder()
scene, err := qb.Update(updatedScene, tx)
@@ -122,78 +111,86 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T
}
// Clear the existing gallery value
gqb := models.NewGalleryQueryBuilder()
err = gqb.ClearGalleryId(sceneID, tx)
if err != nil {
return nil, err
}
if input.GalleryID != nil {
// Save the gallery
galleryID, _ := strconv.Atoi(*input.GalleryID)
updatedGallery := models.Gallery{
ID: galleryID,
SceneID: sql.NullInt64{Int64: int64(sceneID), Valid: true},
UpdatedAt: models.SQLiteTimestamp{Timestamp: updatedTime},
}
if translator.hasField("gallery_id") {
gqb := models.NewGalleryQueryBuilder()
_, err := gqb.Update(updatedGallery, tx)
err = gqb.ClearGalleryId(sceneID, tx)
if err != nil {
return nil, err
}
if input.GalleryID != nil {
// Save the gallery
galleryID, _ := strconv.Atoi(*input.GalleryID)
updatedGallery := models.Gallery{
ID: galleryID,
SceneID: sql.NullInt64{Int64: int64(sceneID), Valid: true},
UpdatedAt: models.SQLiteTimestamp{Timestamp: updatedTime},
}
gqb := models.NewGalleryQueryBuilder()
_, err := gqb.Update(updatedGallery, tx)
if err != nil {
return nil, err
}
}
}
// Save the performers
var performerJoins []models.PerformersScenes
for _, pid := range input.PerformerIds {
performerID, _ := strconv.Atoi(pid)
performerJoin := models.PerformersScenes{
PerformerID: performerID,
SceneID: sceneID,
if translator.hasField("performer_ids") {
var performerJoins []models.PerformersScenes
for _, pid := range input.PerformerIds {
performerID, _ := strconv.Atoi(pid)
performerJoin := models.PerformersScenes{
PerformerID: performerID,
SceneID: sceneID,
}
performerJoins = append(performerJoins, performerJoin)
}
if err := jqb.UpdatePerformersScenes(sceneID, performerJoins, tx); err != nil {
return nil, err
}
performerJoins = append(performerJoins, performerJoin)
}
if err := jqb.UpdatePerformersScenes(sceneID, performerJoins, tx); err != nil {
return nil, err
}
// Save the movies
var movieJoins []models.MoviesScenes
if translator.hasField("movies") {
var movieJoins []models.MoviesScenes
for _, movie := range input.Movies {
for _, movie := range input.Movies {
movieID, _ := strconv.Atoi(movie.MovieID)
movieID, _ := strconv.Atoi(movie.MovieID)
movieJoin := models.MoviesScenes{
MovieID: movieID,
SceneID: sceneID,
}
if movie.SceneIndex != nil {
movieJoin.SceneIndex = sql.NullInt64{
Int64: int64(*movie.SceneIndex),
Valid: true,
movieJoin := models.MoviesScenes{
MovieID: movieID,
SceneID: sceneID,
}
}
movieJoins = append(movieJoins, movieJoin)
}
if err := jqb.UpdateMoviesScenes(sceneID, movieJoins, tx); err != nil {
return nil, err
if movie.SceneIndex != nil {
movieJoin.SceneIndex = sql.NullInt64{
Int64: int64(*movie.SceneIndex),
Valid: true,
}
}
movieJoins = append(movieJoins, movieJoin)
}
if err := jqb.UpdateMoviesScenes(sceneID, movieJoins, tx); err != nil {
return nil, err
}
}
// Save the tags
var tagJoins []models.ScenesTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
tagJoin := models.ScenesTags{
SceneID: sceneID,
TagID: tagID,
if translator.hasField("tag_ids") {
var tagJoins []models.ScenesTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
tagJoin := models.ScenesTags{
SceneID: sceneID,
TagID: tagID,
}
tagJoins = append(tagJoins, tagJoin)
}
if err := jqb.UpdateScenesTags(sceneID, tagJoins, tx); err != nil {
return nil, err
}
tagJoins = append(tagJoins, tagJoin)
}
if err := jqb.UpdateScenesTags(sceneID, tagJoins, tx); err != nil {
return nil, err
}
// only update the cover image if provided and everything else was successful
@@ -205,7 +202,7 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T
}
// Save the stash_ids
if input.StashIds != nil {
if translator.hasField("stash_ids") {
var stashIDJoins []models.StashID
for _, stashID := range input.StashIds {
newJoin := models.StashID{
@@ -226,6 +223,10 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
// Populate scene from the input
updatedTime := time.Now()
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
// Start the transaction and save the scene marker
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewSceneQueryBuilder()
@@ -234,35 +235,13 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
updatedScene := models.ScenePartial{
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
if input.Title != nil {
updatedScene.Title = &sql.NullString{String: *input.Title, Valid: true}
}
if input.Details != nil {
updatedScene.Details = &sql.NullString{String: *input.Details, Valid: true}
}
if input.URL != nil {
updatedScene.URL = &sql.NullString{String: *input.URL, Valid: true}
}
if input.Date != nil {
updatedScene.Date = &models.SQLiteDate{String: *input.Date, Valid: true}
}
if input.Rating != nil {
// a rating of 0 means unset the rating
if *input.Rating == 0 {
updatedScene.Rating = &sql.NullInt64{Int64: 0, Valid: false}
} else {
updatedScene.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
}
}
if input.StudioID != nil {
// empty string means unset the studio
if *input.StudioID == "" {
updatedScene.StudioID = &sql.NullInt64{Int64: 0, Valid: false}
} else {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedScene.StudioID = &sql.NullInt64{Int64: studioID, Valid: true}
}
}
updatedScene.Title = translator.nullString(input.Title, "title")
updatedScene.Details = translator.nullString(input.Details, "details")
updatedScene.URL = translator.nullString(input.URL, "url")
updatedScene.Date = translator.sqliteDate(input.Date, "date")
updatedScene.Rating = translator.nullInt64(input.Rating, "rating")
updatedScene.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
ret := []*models.Scene{}
@@ -278,9 +257,12 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
ret = append(ret, scene)
if input.GalleryID != nil {
if translator.hasField("gallery_id") {
// Save the gallery
galleryID, _ := strconv.Atoi(*input.GalleryID)
var galleryID int
if input.GalleryID != nil {
galleryID, _ = strconv.Atoi(*input.GalleryID)
}
updatedGallery := models.Gallery{
ID: galleryID,
SceneID: sql.NullInt64{Int64: int64(sceneID), Valid: true},
@@ -295,7 +277,7 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
}
// Save the performers
if wasFieldIncluded(ctx, "performer_ids") {
if translator.hasField("performer_ids") {
performerIDs, err := adjustScenePerformerIDs(tx, sceneID, *input.PerformerIds)
if err != nil {
_ = tx.Rollback()
@@ -317,7 +299,7 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
}
// Save the tags
if wasFieldIncluded(ctx, "tag_ids") {
if translator.hasField("tag_ids") {
tagIDs, err := adjustSceneTagIDs(tx, sceneID, *input.TagIds)
if err != nil {
_ = tx.Rollback()

View File

@@ -89,13 +89,17 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
// Populate studio from the input
studioID, _ := strconv.Atoi(input.ID)
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
updatedStudio := models.StudioPartial{
ID: studioID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
}
var imageData []byte
imageIncluded := wasFieldIncluded(ctx, "image")
imageIncluded := translator.hasField("image")
if input.Image != nil {
var err error
_, imageData, err = utils.ProcessBase64Image(*input.Image)
@@ -109,17 +113,9 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
updatedStudio.Name = &sql.NullString{String: *input.Name, Valid: true}
updatedStudio.Checksum = &checksum
}
if input.URL != nil {
updatedStudio.URL = &sql.NullString{String: *input.URL, Valid: true}
}
if input.ParentID != nil {
parentID, _ := strconv.ParseInt(*input.ParentID, 10, 64)
updatedStudio.ParentID = &sql.NullInt64{Int64: parentID, Valid: true}
} else {
// parent studio must be nullable
updatedStudio.ParentID = &sql.NullInt64{Valid: false}
}
updatedStudio.URL = translator.nullString(input.URL, "url")
updatedStudio.ParentID = translator.nullInt64FromString(input.ParentID, "parent_id")
// Start the transaction and save the studio
tx := database.DB.MustBeginTx(ctx, nil)
@@ -152,7 +148,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
}
// Save the stash_ids
if input.StashIds != nil {
if translator.hasField("stash_ids") {
var stashIDJoins []models.StashID
for _, stashID := range input.StashIds {
newJoin := models.StashID{

View File

@@ -76,7 +76,11 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
var imageData []byte
var err error
imageIncluded := wasFieldIncluded(ctx, "image")
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
imageIncluded := translator.hasField("image")
if input.Image != nil {
_, imageData, err = utils.ProcessBase64Image(*input.Image)

View File

@@ -220,7 +220,30 @@ func (_m *PerformerReaderWriter) GetPerformerImage(performerID int) ([]byte, err
}
// Update provides a mock function with given fields: updatedPerformer
func (_m *PerformerReaderWriter) Update(updatedPerformer models.Performer) (*models.Performer, error) {
func (_m *PerformerReaderWriter) Update(updatedPerformer models.PerformerPartial) (*models.Performer, error) {
ret := _m.Called(updatedPerformer)
var r0 *models.Performer
if rf, ok := ret.Get(0).(func(models.PerformerPartial) *models.Performer); ok {
r0 = rf(updatedPerformer)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Performer)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(models.PerformerPartial) error); ok {
r1 = rf(updatedPerformer)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateFull provides a mock function with given fields: updatedPerformer
func (_m *PerformerReaderWriter) UpdateFull(updatedPerformer models.Performer) (*models.Performer, error) {
ret := _m.Called(updatedPerformer)
var r0 *models.Performer

View File

@@ -31,6 +31,30 @@ type Performer struct {
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
}
type PerformerPartial struct {
ID int `db:"id" json:"id"`
Checksum *string `db:"checksum" json:"checksum"`
Name *sql.NullString `db:"name" json:"name"`
Gender *sql.NullString `db:"gender" json:"gender"`
URL *sql.NullString `db:"url" json:"url"`
Twitter *sql.NullString `db:"twitter" json:"twitter"`
Instagram *sql.NullString `db:"instagram" json:"instagram"`
Birthdate *SQLiteDate `db:"birthdate" json:"birthdate"`
Ethnicity *sql.NullString `db:"ethnicity" json:"ethnicity"`
Country *sql.NullString `db:"country" json:"country"`
EyeColor *sql.NullString `db:"eye_color" json:"eye_color"`
Height *sql.NullString `db:"height" json:"height"`
Measurements *sql.NullString `db:"measurements" json:"measurements"`
FakeTits *sql.NullString `db:"fake_tits" json:"fake_tits"`
CareerLength *sql.NullString `db:"career_length" json:"career_length"`
Tattoos *sql.NullString `db:"tattoos" json:"tattoos"`
Piercings *sql.NullString `db:"piercings" json:"piercings"`
Aliases *sql.NullString `db:"aliases" json:"aliases"`
Favorite *sql.NullBool `db:"favorite" json:"favorite"`
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
}
func NewPerformer(name string) *Performer {
currentTime := time.Now()
return &Performer{

View File

@@ -21,7 +21,8 @@ type PerformerReader interface {
type PerformerWriter interface {
Create(newPerformer Performer) (*Performer, error)
Update(updatedPerformer Performer) (*Performer, error)
Update(updatedPerformer PerformerPartial) (*Performer, error)
UpdateFull(updatedPerformer Performer) (*Performer, error)
// Destroy(id string) error
UpdatePerformerImage(performerID int, image []byte) error
// DestroyPerformerImage(performerID int) error
@@ -80,10 +81,14 @@ func (t *performerReaderWriter) Create(newPerformer Performer) (*Performer, erro
return t.qb.Create(newPerformer, t.tx)
}
func (t *performerReaderWriter) Update(updatedPerformer Performer) (*Performer, error) {
func (t *performerReaderWriter) Update(updatedPerformer PerformerPartial) (*Performer, error) {
return t.qb.Update(updatedPerformer, t.tx)
}
func (t *performerReaderWriter) UpdateFull(updatedPerformer Performer) (*Performer, error) {
return t.qb.UpdateFull(updatedPerformer, t.tx)
}
func (t *performerReaderWriter) UpdatePerformerImage(performerID int, image []byte) error {
return t.qb.UpdatePerformerImage(performerID, image, t.tx)
}

View File

@@ -42,7 +42,24 @@ func (qb *PerformerQueryBuilder) Create(newPerformer Performer, tx *sqlx.Tx) (*P
return &newPerformer, nil
}
func (qb *PerformerQueryBuilder) Update(updatedPerformer Performer, tx *sqlx.Tx) (*Performer, error) {
func (qb *PerformerQueryBuilder) Update(updatedPerformer PerformerPartial, tx *sqlx.Tx) (*Performer, error) {
ensureTx(tx)
_, err := tx.NamedExec(
`UPDATE performers SET `+SQLGenKeysPartial(updatedPerformer)+` WHERE performers.id = :id`,
updatedPerformer,
)
if err != nil {
return nil, err
}
var ret Performer
if err := tx.Get(&ret, `SELECT * FROM performers WHERE id = ? LIMIT 1`, updatedPerformer.ID); err != nil {
return nil, err
}
return &ret, nil
}
func (qb *PerformerQueryBuilder) UpdateFull(updatedPerformer Performer, tx *sqlx.Tx) (*Performer, error) {
ensureTx(tx)
_, err := tx.NamedExec(
`UPDATE performers SET `+SQLGenKeys(updatedPerformer)+` WHERE performers.id = :id`,

View File

@@ -73,7 +73,7 @@ func (i *Importer) Create() (*int, error) {
func (i *Importer) Update(id int) error {
performer := i.performer
performer.ID = id
_, err := i.ReaderWriter.Update(performer)
_, err := i.ReaderWriter.UpdateFull(performer)
if err != nil {
return fmt.Errorf("error updating existing performer: %s", err.Error())
}

View File

@@ -166,7 +166,7 @@ func TestUpdate(t *testing.T) {
// id needs to be set for the mock input
performer.ID = performerID
readerWriter.On("Update", performer).Return(nil, nil).Once()
readerWriter.On("UpdateFull", performer).Return(nil, nil).Once()
err := i.Update(performerID)
assert.Nil(t, err)
@@ -175,7 +175,7 @@ func TestUpdate(t *testing.T) {
// need to set id separately
performerErr.ID = errImageID
readerWriter.On("Update", performerErr).Return(nil, errUpdate).Once()
readerWriter.On("UpdateFull", performerErr).Return(nil, errUpdate).Once()
err = i.Update(errImageID)
assert.NotNil(t, err)

View File

@@ -29,7 +29,7 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
);
const [tagIds, setTagIds] = useState<string[]>();
const [updateGalleries] = useBulkGalleryUpdate(getGalleryInput());
const [updateGalleries] = useBulkGalleryUpdate();
// Network state
const [isUpdating, setIsUpdating] = useState(false);
@@ -61,8 +61,8 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
if (rating === undefined) {
// and all galleries have the same rating, then we are unsetting the rating.
if (aggregateRating) {
// an undefined rating is ignored in the server, so set it to 0 instead
galleryInput.rating = 0;
// null to unset rating
galleryInput.rating = null;
}
// otherwise not setting the rating
} else {
@@ -75,8 +75,8 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
// and all galleries have the same studioId,
// then unset the studioId, otherwise ignoring studioId
if (aggregateStudioId) {
// an undefined studio_id is ignored in the server, so set it to empty string instead
galleryInput.studio_id = "";
// null to unset studio_id
galleryInput.studio_id = null;
}
} else {
// if studioId is set, then we are setting it
@@ -125,7 +125,11 @@ export const EditGalleriesDialog: React.FC<IListOperationProps> = (
async function onSave() {
setIsUpdating(true);
try {
await updateGalleries();
await updateGalleries({
variables: {
input: getGalleryInput(),
},
});
Toast.success({ content: "Updated galleries" });
props.onClose(true);
} catch (e) {

View File

@@ -63,9 +63,7 @@ export const GalleryEditPanel: React.FC<
const [createGallery] = useGalleryCreate(
getGalleryInput() as GQL.GalleryCreateInput
);
const [updateGallery] = useGalleryUpdate(
getGalleryInput() as GQL.GalleryUpdateInput
);
const [updateGallery] = useGalleryUpdate();
useEffect(() => {
if (props.isVisible) {
@@ -135,8 +133,8 @@ export const GalleryEditPanel: React.FC<
details,
url,
date,
rating,
studio_id: studioId,
rating: rating ?? null,
studio_id: studioId ?? null,
performer_ids: performerIds,
tag_ids: tagIds,
};
@@ -152,7 +150,11 @@ export const GalleryEditPanel: React.FC<
Toast.success({ content: "Created gallery" });
}
} else {
const result = await updateGallery();
const result = await updateGallery({
variables: {
input: getGalleryInput() as GQL.GalleryUpdateInput,
},
});
if (result.data?.galleryUpdate) {
Toast.success({ content: "Updated gallery" });
}

View File

@@ -29,7 +29,7 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
);
const [tagIds, setTagIds] = useState<string[]>();
const [updateImages] = useBulkImageUpdate(getImageInput());
const [updateImages] = useBulkImageUpdate();
// Network state
const [isUpdating, setIsUpdating] = useState(false);
@@ -61,8 +61,8 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
if (rating === undefined) {
// and all images have the same rating, then we are unsetting the rating.
if (aggregateRating) {
// an undefined rating is ignored in the server, so set it to 0 instead
imageInput.rating = 0;
// null rating to unset it
imageInput.rating = null;
}
// otherwise not setting the rating
} else {
@@ -75,8 +75,8 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
// and all images have the same studioId,
// then unset the studioId, otherwise ignoring studioId
if (aggregateStudioId) {
// an undefined studio_id is ignored in the server, so set it to empty string instead
imageInput.studio_id = "";
// null studio_id to unset it
imageInput.studio_id = null;
}
} else {
// if studioId is set, then we are setting it
@@ -125,7 +125,11 @@ export const EditImagesDialog: React.FC<IListOperationProps> = (
async function onSave() {
setIsUpdating(true);
try {
await updateImages();
await updateImages({
variables: {
input: getImageInput(),
},
});
Toast.success({ content: "Updated images" });
props.onClose(true);
} catch (e) {

View File

@@ -30,7 +30,7 @@ export const ImageEditPanel: React.FC<IProps> = (props: IProps) => {
// Network state
const [isLoading, setIsLoading] = useState(true);
const [updateImage] = useImageUpdate(getImageInput());
const [updateImage] = useImageUpdate();
useEffect(() => {
if (props.isVisible) {
@@ -95,8 +95,8 @@ export const ImageEditPanel: React.FC<IProps> = (props: IProps) => {
return {
id: props.image.id,
title,
rating,
studio_id: studioId,
rating: rating ?? null,
studio_id: studioId ?? null,
performer_ids: performerIds,
tag_ids: tagIds,
};
@@ -105,7 +105,11 @@ export const ImageEditPanel: React.FC<IProps> = (props: IProps) => {
async function onSave() {
setIsLoading(true);
try {
const result = await updateImage();
const result = await updateImage({
variables: {
input: getImageInput(),
},
});
if (result.data?.imageUpdate) {
Toast.success({ content: "Updated image" });
}

View File

@@ -79,7 +79,7 @@ export const Movie: React.FC = () => {
// Network state
const { data, error, loading } = useFindMovie(id);
const [isLoading, setIsLoading] = useState(false);
const [updateMovie] = useMovieUpdate(getMovieInput() as GQL.MovieUpdateInput);
const [updateMovie] = useMovieUpdate();
const [createMovie] = useMovieCreate(getMovieInput() as GQL.MovieCreateInput);
const [deleteMovie] = useMovieDestroy(
getMovieInput() as GQL.MovieDestroyInput
@@ -201,8 +201,8 @@ export const Movie: React.FC = () => {
aliases,
duration,
date,
rating,
studio_id: studioId,
rating: rating ?? null,
studio_id: studioId ?? null,
director,
synopsis,
url,
@@ -219,7 +219,11 @@ export const Movie: React.FC = () => {
async function onSave() {
try {
if (!isNew) {
const result = await updateMovie();
const result = await updateMovie({
variables: {
input: getMovieInput() as GQL.MovieUpdateInput,
},
});
if (result.data?.movieUpdate) {
updateMovieData(result.data.movieUpdate);
setIsEditing(false);

View File

@@ -111,12 +111,14 @@ export const Performer: React.FC = () => {
if (!isNew) {
await updatePerformer({
variables: {
...performerInput,
stash_ids: (performerInput?.stash_ids ?? []).map((s) => ({
endpoint: s.endpoint,
stash_id: s.stash_id,
})),
} as GQL.PerformerUpdateInput,
input: {
...performerInput,
stash_ids: performerInput?.stash_ids?.map((s) => ({
endpoint: s.endpoint,
stash_id: s.stash_id,
})),
} as GQL.PerformerUpdateInput,
},
});
if (performerInput.image) {
// Refetch image to bust browser cache
@@ -215,7 +217,10 @@ export const Performer: React.FC = () => {
}
function setFavorite(v: boolean) {
onSave({ ...performer, favorite: v });
onSave({
id: performer.id,
favorite: v,
});
}
const renderIcons = () => (

View File

@@ -61,8 +61,8 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
if (rating === undefined) {
// and all scenes have the same rating, then we are unsetting the rating.
if (aggregateRating) {
// an undefined rating is ignored in the server, so set it to 0 instead
sceneInput.rating = 0;
// null rating unsets it
sceneInput.rating = null;
}
// otherwise not setting the rating
} else {
@@ -75,8 +75,8 @@ export const EditScenesDialog: React.FC<IListOperationProps> = (
// and all scenes have the same studioId,
// then unset the studioId, otherwise ignoring studioId
if (aggregateStudioId) {
// an undefined studio_id is ignored in the server, so set it to empty string instead
sceneInput.studio_id = "";
// null studio_id unsets it
sceneInput.studio_id = null;
}
} else {
// if studioId is set, then we are setting it

View File

@@ -194,9 +194,9 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
details,
url,
date,
rating,
gallery_id: galleryId,
studio_id: studioId,
rating: rating ?? null,
gallery_id: galleryId ?? null,
studio_id: studioId ?? null,
performer_ids: performerIds,
movies: makeMovieInputs(),
tag_ids: tagIds,

View File

@@ -51,9 +51,7 @@ export const Studio: React.FC = () => {
const [imagePreview, setImagePreview] = useState<string | null>();
const { data, error, loading } = useFindStudio(id);
const [updateStudio] = useStudioUpdate(
getStudioInput() as GQL.StudioUpdateInput
);
const [updateStudio] = useStudioUpdate();
const [createStudio] = useStudioCreate(
getStudioInput() as GQL.StudioCreateInput
);
@@ -118,8 +116,8 @@ export const Studio: React.FC = () => {
const input: Partial<GQL.StudioCreateInput | GQL.StudioUpdateInput> = {
name,
url,
parent_id: parentStudioId,
image,
parent_id: parentStudioId ?? null,
image: image ?? null,
};
if (!isNew) {
@@ -131,7 +129,11 @@ export const Studio: React.FC = () => {
async function onSave() {
try {
if (!isNew) {
const result = await updateStudio();
const result = await updateStudio({
variables: {
input: getStudioInput() as GQL.StudioUpdateInput,
},
});
if (result.data?.studioUpdate) {
updateStudioData(result.data.studioUpdate);
setIsEditing(false);

View File

@@ -279,27 +279,26 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
const sceneUpdateResult = await updateScene({
variables: {
id: stashScene.id ?? "",
title: scene.title,
details: scene.details,
date: scene.date,
performer_ids: performerIDs.filter((id) => id !== "Skip") as string[],
studio_id: studioID,
cover_image: imgData,
url: scene.url,
tag_ids: updatedTags,
rating: stashScene.rating,
movies: stashScene.movies.map((m) => ({
movie_id: m.movie.id,
scene_index: m.scene_index,
})),
stash_ids: [
...(stashScene?.stash_ids ?? []),
{
endpoint,
stash_id: scene.stash_id,
},
],
input: {
id: stashScene.id ?? "",
title: scene.title,
details: scene.details,
date: scene.date,
performer_ids: performerIDs.filter(
(id) => id !== "Skip"
) as string[],
studio_id: studioID,
cover_image: imgData,
url: scene.url,
tag_ids: updatedTags,
stash_ids: [
...(stashScene?.stash_ids ?? []),
{
endpoint,
stash_id: scene.stash_id,
},
],
},
},
});

View File

@@ -12,11 +12,13 @@ export const useUpdatePerformerStashID = () => {
) =>
updatePerformer({
variables: {
id: performerID,
stash_ids: stashIDs.map((s) => ({
stash_id: s.stash_id,
endpoint: s.endpoint,
})),
input: {
id: performerID,
stash_ids: stashIDs.map((s) => ({
stash_id: s.stash_id,
endpoint: s.endpoint,
})),
},
},
update: (store, updatedPerformer) => {
if (!updatedPerformer.data?.performerUpdate) return;
@@ -117,12 +119,13 @@ export const useUpdateStudioStashID = () => {
) =>
updateStudio({
variables: {
id: studio.id,
parent_id: studio.parent_studio?.id,
stash_ids: stashIDs.map((s) => ({
stash_id: s.stash_id,
endpoint: s.endpoint,
})),
input: {
id: studio.id,
stash_ids: stashIDs.map((s) => ({
stash_id: s.stash_id,
endpoint: s.endpoint,
})),
},
},
update: (store, result) => {
if (!result.data?.studioUpdate) return;

View File

@@ -47,7 +47,7 @@ export const Tag: React.FC = () => {
const [imagePreview, setImagePreview] = useState<string>();
const { data, error, loading } = useFindTag(id);
const [updateTag] = useTagUpdate(getTagInput() as GQL.TagUpdateInput);
const [updateTag] = useTagUpdate();
const [createTag] = useTagCreate(getTagInput() as GQL.TagUpdateInput);
const [deleteTag] = useTagDestroy(getTagInput() as GQL.TagUpdateInput);
@@ -127,7 +127,11 @@ export const Tag: React.FC = () => {
async function onSave() {
try {
if (!isNew) {
const result = await updateTag();
const result = await updateTag({
variables: {
input: getTagInput() as GQL.TagUpdateInput,
},
});
if (result.data?.tagUpdate) {
updateTagData(result.data.tagUpdate);
setIsEditing(false);

View File

@@ -325,13 +325,17 @@ const sceneMutationImpactedQueries = [
export const useSceneUpdate = (input: GQL.SceneUpdateInput) =>
GQL.useSceneUpdateMutation({
variables: input,
variables: {
input,
},
update: deleteCache(sceneMutationImpactedQueries),
});
export const useBulkSceneUpdate = (input: GQL.BulkSceneUpdateInput) =>
GQL.useBulkSceneUpdateMutation({
variables: input,
variables: {
input,
},
update: deleteCache(sceneMutationImpactedQueries),
});
@@ -419,15 +423,13 @@ const imageMutationImpactedQueries = [
GQL.FindGalleriesDocument,
];
export const useImageUpdate = (input: GQL.ImageUpdateInput) =>
export const useImageUpdate = () =>
GQL.useImageUpdateMutation({
variables: input,
update: deleteCache(imageMutationImpactedQueries),
});
export const useBulkImageUpdate = (input: GQL.BulkImageUpdateInput) =>
export const useBulkImageUpdate = () =>
GQL.useBulkImageUpdateMutation({
variables: input,
update: deleteCache(imageMutationImpactedQueries),
});
@@ -506,15 +508,13 @@ export const useGalleryCreate = (input: GQL.GalleryCreateInput) =>
update: deleteCache(galleryMutationImpactedQueries),
});
export const useGalleryUpdate = (input: GQL.GalleryUpdateInput) =>
export const useGalleryUpdate = () =>
GQL.useGalleryUpdateMutation({
variables: input,
update: deleteCache(galleryMutationImpactedQueries),
});
export const useBulkGalleryUpdate = (input: GQL.BulkGalleryUpdateInput) =>
export const useBulkGalleryUpdate = () =>
GQL.useBulkGalleryUpdateMutation({
variables: input,
update: deleteCache(galleryMutationImpactedQueries),
});
@@ -555,9 +555,8 @@ export const useStudioCreate = (input: GQL.StudioCreateInput) =>
]),
});
export const useStudioUpdate = (input: GQL.StudioUpdateInput) =>
export const useStudioUpdate = () =>
GQL.useStudioUpdateMutation({
variables: input,
update: deleteCache(studioMutationImpactedQueries),
});
@@ -583,9 +582,8 @@ export const useMovieCreate = (input: GQL.MovieCreateInput) =>
]),
});
export const useMovieUpdate = (input: GQL.MovieUpdateInput) =>
export const useMovieUpdate = () =>
GQL.useMovieUpdateMutation({
variables: input,
update: deleteCache(movieMutationImpactedQueries),
});
@@ -618,9 +616,8 @@ export const useTagCreate = (input: GQL.TagCreateInput) =>
GQL.AllTagsForFilterDocument,
]),
});
export const useTagUpdate = (input: GQL.TagUpdateInput) =>
export const useTagUpdate = () =>
GQL.useTagUpdateMutation({
variables: input,
update: deleteCache(tagMutationImpactedQueries),
});
export const useTagDestroy = (input: GQL.TagDestroyInput) =>