mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Studio aliases (#1660)
* Add migration to create studio aliases table * Refactor studioQueryBuilder.Query to use filterBuilder * Expand GraphQL API with aliases support for studio * Add aliases support for studios to the UI * List aliases in details panel * Allow editing aliases in edit panel * Add 'aliases' filter when searching * Find studios by alias in filter / select * Add auto-tagging based on studio aliases * Support studio aliases for filename parsing * Support importing and exporting of studio aliases * Search for studio alias as well during scraping
This commit is contained in:
@@ -42,6 +42,13 @@ func ToJSON(reader models.StudioReader, studio *models.Studio) (*jsonschema.Stud
|
||||
newStudioJSON.Rating = int(studio.Rating.Int64)
|
||||
}
|
||||
|
||||
aliases, err := reader.GetAliases(studio.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting studio aliases: %s", err.Error())
|
||||
}
|
||||
|
||||
newStudioJSON.Aliases = aliases
|
||||
|
||||
image, err := reader.GetImage(studio.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting studio image: %s", err.Error())
|
||||
|
||||
@@ -18,6 +18,7 @@ const (
|
||||
errImageID = 3
|
||||
missingParentStudioID = 4
|
||||
errStudioID = 5
|
||||
errAliasID = 6
|
||||
|
||||
parentStudioID = 10
|
||||
missingStudioID = 11
|
||||
@@ -77,7 +78,7 @@ func createEmptyStudio(id int) models.Studio {
|
||||
}
|
||||
}
|
||||
|
||||
func createFullJSONStudio(parentStudio, image string) *jsonschema.Studio {
|
||||
func createFullJSONStudio(parentStudio, image string, aliases []string) *jsonschema.Studio {
|
||||
return &jsonschema.Studio{
|
||||
Name: studioName,
|
||||
URL: url,
|
||||
@@ -91,6 +92,7 @@ func createFullJSONStudio(parentStudio, image string) *jsonschema.Studio {
|
||||
ParentStudio: parentStudio,
|
||||
Image: image,
|
||||
Rating: rating,
|
||||
Aliases: aliases,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +119,7 @@ func initTestTable() {
|
||||
scenarios = []testScenario{
|
||||
testScenario{
|
||||
createFullStudio(studioID, parentStudioID),
|
||||
createFullJSONStudio(parentStudioName, image),
|
||||
createFullJSONStudio(parentStudioName, image, []string{"alias"}),
|
||||
false,
|
||||
},
|
||||
testScenario{
|
||||
@@ -132,7 +134,7 @@ func initTestTable() {
|
||||
},
|
||||
testScenario{
|
||||
createFullStudio(missingParentStudioID, missingStudioID),
|
||||
createFullJSONStudio("", image),
|
||||
createFullJSONStudio("", image, nil),
|
||||
false,
|
||||
},
|
||||
testScenario{
|
||||
@@ -140,6 +142,11 @@ func initTestTable() {
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
testScenario{
|
||||
createFullStudio(errAliasID, parentStudioID),
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +162,7 @@ func TestToJSON(t *testing.T) {
|
||||
mockStudioReader.On("GetImage", errImageID).Return(nil, imageErr).Once()
|
||||
mockStudioReader.On("GetImage", missingParentStudioID).Return(imageBytes, nil).Maybe()
|
||||
mockStudioReader.On("GetImage", errStudioID).Return(imageBytes, nil).Maybe()
|
||||
mockStudioReader.On("GetImage", errAliasID).Return(imageBytes, nil).Maybe()
|
||||
|
||||
parentStudioErr := errors.New("error getting parent studio")
|
||||
|
||||
@@ -162,6 +170,14 @@ func TestToJSON(t *testing.T) {
|
||||
mockStudioReader.On("Find", missingStudioID).Return(nil, nil)
|
||||
mockStudioReader.On("Find", errParentStudioID).Return(nil, parentStudioErr)
|
||||
|
||||
aliasErr := errors.New("error getting aliases")
|
||||
|
||||
mockStudioReader.On("GetAliases", studioID).Return([]string{"alias"}, nil).Once()
|
||||
mockStudioReader.On("GetAliases", noImageID).Return(nil, nil).Once()
|
||||
mockStudioReader.On("GetAliases", errImageID).Return(nil, nil).Once()
|
||||
mockStudioReader.On("GetAliases", missingParentStudioID).Return(nil, nil).Once()
|
||||
mockStudioReader.On("GetAliases", errAliasID).Return(nil, aliasErr).Once()
|
||||
|
||||
for i, s := range scenarios {
|
||||
studio := s.input
|
||||
json, err := ToJSON(mockStudioReader, &studio)
|
||||
|
||||
@@ -101,6 +101,10 @@ func (i *Importer) PostImport(id int) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := i.ReaderWriter.UpdateAliases(id, i.Input.Aliases); err != nil {
|
||||
return fmt.Errorf("error setting tag aliases: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestImporterPreImport(t *testing.T) {
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
i.Input = *createFullJSONStudio(studioName, image)
|
||||
i.Input = *createFullJSONStudio(studioName, image, []string{"alias"})
|
||||
i.Input.ParentStudio = ""
|
||||
|
||||
err = i.PreImport()
|
||||
@@ -151,13 +151,22 @@ func TestImporterPostImport(t *testing.T) {
|
||||
|
||||
i := Importer{
|
||||
ReaderWriter: readerWriter,
|
||||
imageData: imageBytes,
|
||||
Input: jsonschema.Studio{
|
||||
Aliases: []string{"alias"},
|
||||
},
|
||||
imageData: imageBytes,
|
||||
}
|
||||
|
||||
updateStudioImageErr := errors.New("UpdateImage error")
|
||||
updateTagAliasErr := errors.New("UpdateAlias error")
|
||||
|
||||
readerWriter.On("UpdateImage", studioID, imageBytes).Return(nil).Once()
|
||||
readerWriter.On("UpdateImage", errImageID, imageBytes).Return(updateStudioImageErr).Once()
|
||||
readerWriter.On("UpdateImage", errAliasID, imageBytes).Return(nil).Once()
|
||||
|
||||
readerWriter.On("UpdateAliases", studioID, i.Input.Aliases).Return(nil).Once()
|
||||
readerWriter.On("UpdateAliases", errImageID, i.Input.Aliases).Return(nil).Maybe()
|
||||
readerWriter.On("UpdateAliases", errAliasID, i.Input.Aliases).Return(updateTagAliasErr).Once()
|
||||
|
||||
err := i.PostImport(studioID)
|
||||
assert.Nil(t, err)
|
||||
@@ -165,6 +174,9 @@ func TestImporterPostImport(t *testing.T) {
|
||||
err = i.PostImport(errImageID)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
err = i.PostImport(errAliasID)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
readerWriter.AssertExpectations(t)
|
||||
}
|
||||
|
||||
|
||||
51
pkg/studio/query.go
Normal file
51
pkg/studio/query.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package studio
|
||||
|
||||
import "github.com/stashapp/stash/pkg/models"
|
||||
|
||||
func ByName(qb models.StudioReader, name string) (*models.Studio, error) {
|
||||
f := &models.StudioFilterType{
|
||||
Name: &models.StringCriterionInput{
|
||||
Value: name,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
}
|
||||
|
||||
pp := 1
|
||||
ret, count, err := qb.Query(f, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
return ret[0], nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func ByAlias(qb models.StudioReader, alias string) (*models.Studio, error) {
|
||||
f := &models.StudioFilterType{
|
||||
Aliases: &models.StringCriterionInput{
|
||||
Value: alias,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
}
|
||||
|
||||
pp := 1
|
||||
ret, count, err := qb.Query(f, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
return ret[0], nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
65
pkg/studio/update.go
Normal file
65
pkg/studio/update.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package studio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type NameExistsError struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (e *NameExistsError) Error() string {
|
||||
return fmt.Sprintf("studio with name '%s' already exists", e.Name)
|
||||
}
|
||||
|
||||
type NameUsedByAliasError struct {
|
||||
Name string
|
||||
OtherStudio string
|
||||
}
|
||||
|
||||
func (e *NameUsedByAliasError) Error() string {
|
||||
return fmt.Sprintf("name '%s' is used as alias for '%s'", e.Name, e.OtherStudio)
|
||||
}
|
||||
|
||||
// EnsureStudioNameUnique returns an error if the studio name provided
|
||||
// is used as a name or alias of another existing tag.
|
||||
func EnsureStudioNameUnique(id int, name string, qb models.StudioReader) error {
|
||||
// ensure name is unique
|
||||
sameNameStudio, err := ByName(qb, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sameNameStudio != nil && id != sameNameStudio.ID {
|
||||
return &NameExistsError{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// query by alias
|
||||
sameNameStudio, err = ByAlias(qb, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sameNameStudio != nil && id != sameNameStudio.ID {
|
||||
return &NameUsedByAliasError{
|
||||
Name: name,
|
||||
OtherStudio: sameNameStudio.Name.String,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureAliasesUnique(id int, aliases []string, qb models.StudioReader) error {
|
||||
for _, a := range aliases {
|
||||
if err := EnsureStudioNameUnique(id, a, qb); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user