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:
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
const studioTable = "studios"
|
||||
const studioIDColumn = "studio_id"
|
||||
const studioAliasesTable = "studio_aliases"
|
||||
const studioAliasColumn = "alias"
|
||||
|
||||
type studioQueryBuilder struct {
|
||||
repository
|
||||
@@ -126,19 +128,50 @@ func (qb *studioQueryBuilder) QueryForAutoTag(words []string) ([]*models.Studio,
|
||||
// TODO - Query needs to be changed to support queries of this type, and
|
||||
// this method should be removed
|
||||
query := selectAll(studioTable)
|
||||
query += " LEFT JOIN studio_aliases ON studio_aliases.studio_id = studios.id"
|
||||
|
||||
var whereClauses []string
|
||||
var args []interface{}
|
||||
|
||||
for _, w := range words {
|
||||
whereClauses = append(whereClauses, "name like ?")
|
||||
args = append(args, w+"%")
|
||||
ww := w + "%"
|
||||
whereClauses = append(whereClauses, "studios.name like ?")
|
||||
args = append(args, ww)
|
||||
|
||||
// include aliases
|
||||
whereClauses = append(whereClauses, "studio_aliases.alias like ?")
|
||||
args = append(args, ww)
|
||||
}
|
||||
|
||||
where := strings.Join(whereClauses, " OR ")
|
||||
return qb.queryStudios(query+" WHERE "+where, args)
|
||||
}
|
||||
|
||||
func (qb *studioQueryBuilder) makeFilter(studioFilter *models.StudioFilterType) *filterBuilder {
|
||||
query := &filterBuilder{}
|
||||
|
||||
query.handleCriterion(stringCriterionHandler(studioFilter.Name, studioTable+".name"))
|
||||
query.handleCriterion(stringCriterionHandler(studioFilter.Details, studioTable+".details"))
|
||||
query.handleCriterion(stringCriterionHandler(studioFilter.URL, studioTable+".url"))
|
||||
query.handleCriterion(intCriterionHandler(studioFilter.Rating, studioTable+".rating"))
|
||||
|
||||
query.handleCriterion(criterionHandlerFunc(func(f *filterBuilder) {
|
||||
if studioFilter.StashID != nil {
|
||||
qb.stashIDRepository().join(f, "studio_stash_ids", "studios.id")
|
||||
stringCriterionHandler(studioFilter.StashID, "scene_stash_ids.stash_id")(f)
|
||||
}
|
||||
}))
|
||||
|
||||
query.handleCriterion(studioIsMissingCriterionHandler(qb, studioFilter.IsMissing))
|
||||
query.handleCriterion(studioSceneCountCriterionHandler(qb, studioFilter.SceneCount))
|
||||
query.handleCriterion(studioImageCountCriterionHandler(qb, studioFilter.ImageCount))
|
||||
query.handleCriterion(studioGalleryCountCriterionHandler(qb, studioFilter.GalleryCount))
|
||||
query.handleCriterion(studioParentCriterionHandler(qb, studioFilter.Parents))
|
||||
query.handleCriterion(studioAliasCriterionHandler(qb, studioFilter.Aliases))
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func (qb *studioQueryBuilder) Query(studioFilter *models.StudioFilterType, findFilter *models.FindFilterType) ([]*models.Studio, int, error) {
|
||||
if studioFilter == nil {
|
||||
studioFilter = &models.StudioFilterType{}
|
||||
@@ -150,57 +183,19 @@ func (qb *studioQueryBuilder) Query(studioFilter *models.StudioFilterType, findF
|
||||
query := qb.newQuery()
|
||||
|
||||
query.body = selectDistinctIDs("studios")
|
||||
query.body += `
|
||||
left join scenes on studios.id = scenes.studio_id
|
||||
left join studio_stash_ids on studio_stash_ids.studio_id = studios.id
|
||||
`
|
||||
|
||||
if q := findFilter.Q; q != nil && *q != "" {
|
||||
searchColumns := []string{"studios.name"}
|
||||
query.join(studioAliasesTable, "", "studio_aliases.studio_id = studios.id")
|
||||
searchColumns := []string{"studios.name", "studio_aliases.alias"}
|
||||
|
||||
clause, thisArgs := getSearchBinding(searchColumns, *q, false)
|
||||
query.addWhere(clause)
|
||||
query.addArg(thisArgs...)
|
||||
}
|
||||
|
||||
if parentsFilter := studioFilter.Parents; parentsFilter != nil && len(parentsFilter.Value) > 0 {
|
||||
query.body += `
|
||||
left join studios as parent_studio on parent_studio.id = studios.parent_id
|
||||
`
|
||||
filter := qb.makeFilter(studioFilter)
|
||||
|
||||
for _, studioID := range parentsFilter.Value {
|
||||
query.addArg(studioID)
|
||||
}
|
||||
|
||||
whereClause, havingClause := getMultiCriterionClause("studios", "parent_studio", "", "", "parent_id", parentsFilter)
|
||||
|
||||
query.addWhere(whereClause)
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
if rating := studioFilter.Rating; rating != nil {
|
||||
query.handleIntCriterionInput(studioFilter.Rating, "studios.rating")
|
||||
}
|
||||
query.handleCountCriterion(studioFilter.SceneCount, studioTable, sceneTable, studioIDColumn)
|
||||
query.handleCountCriterion(studioFilter.ImageCount, studioTable, imageTable, studioIDColumn)
|
||||
query.handleCountCriterion(studioFilter.GalleryCount, studioTable, galleryTable, studioIDColumn)
|
||||
query.handleStringCriterionInput(studioFilter.Name, "studios.name")
|
||||
query.handleStringCriterionInput(studioFilter.Details, "studios.details")
|
||||
query.handleStringCriterionInput(studioFilter.URL, "studios.url")
|
||||
query.handleStringCriterionInput(studioFilter.StashID, "studio_stash_ids.stash_id")
|
||||
|
||||
if isMissingFilter := studioFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
||||
switch *isMissingFilter {
|
||||
case "image":
|
||||
query.body += `left join studios_image on studios_image.studio_id = studios.id
|
||||
`
|
||||
query.addWhere("studios_image.studio_id IS NULL")
|
||||
case "stash_id":
|
||||
query.addWhere("studio_stash_ids.studio_id IS NULL")
|
||||
default:
|
||||
query.addWhere("studios." + *isMissingFilter + " IS NULL")
|
||||
}
|
||||
}
|
||||
query.addFilter(filter)
|
||||
|
||||
query.sortAndPagination = qb.getStudioSort(findFilter) + getPagination(findFilter)
|
||||
idsResult, countResult, err := query.executeFind()
|
||||
@@ -221,6 +216,83 @@ func (qb *studioQueryBuilder) Query(studioFilter *models.StudioFilterType, findF
|
||||
return studios, countResult, nil
|
||||
}
|
||||
|
||||
func studioIsMissingCriterionHandler(qb *studioQueryBuilder, isMissing *string) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "image":
|
||||
f.addJoin("studios_image", "", "studios_image.studio_id = studios.id")
|
||||
f.addWhere("studios_image.studio_id IS NULL")
|
||||
case "stash_id":
|
||||
qb.stashIDRepository().join(f, "studio_stash_ids", "studios.id")
|
||||
f.addWhere("studio_stash_ids.studio_id IS NULL")
|
||||
default:
|
||||
f.addWhere("(studios." + *isMissing + " IS NULL OR TRIM(studios." + *isMissing + ") = '')")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func studioSceneCountCriterionHandler(qb *studioQueryBuilder, sceneCount *models.IntCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if sceneCount != nil {
|
||||
f.addJoin("scenes", "", "scenes.studio_id = studios.id")
|
||||
clause, args := getIntCriterionWhereClause("count(distinct scenes.id)", *sceneCount)
|
||||
|
||||
f.addHaving(clause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func studioImageCountCriterionHandler(qb *studioQueryBuilder, imageCount *models.IntCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if imageCount != nil {
|
||||
f.addJoin("images", "", "images.studio_id = studios.id")
|
||||
clause, args := getIntCriterionWhereClause("count(distinct images.id)", *imageCount)
|
||||
|
||||
f.addHaving(clause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func studioGalleryCountCriterionHandler(qb *studioQueryBuilder, galleryCount *models.IntCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if galleryCount != nil {
|
||||
f.addJoin("galleries", "", "galleries.studio_id = studios.id")
|
||||
clause, args := getIntCriterionWhereClause("count(distinct galleries.id)", *galleryCount)
|
||||
|
||||
f.addHaving(clause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func studioParentCriterionHandler(qb *studioQueryBuilder, parents *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
f.addJoin("studios", "parent_studio", "parent_studio.id = studios.parent_id")
|
||||
}
|
||||
h := multiCriterionHandlerBuilder{
|
||||
primaryTable: studioTable,
|
||||
foreignTable: "parent_studio",
|
||||
joinTable: "",
|
||||
primaryFK: studioIDColumn,
|
||||
foreignFK: "parent_id",
|
||||
addJoinsFunc: addJoinsFunc,
|
||||
}
|
||||
return h.handler(parents)
|
||||
}
|
||||
|
||||
func studioAliasCriterionHandler(qb *studioQueryBuilder, alias *models.StringCriterionInput) criterionHandlerFunc {
|
||||
h := stringListCriterionHandlerBuilder{
|
||||
joinTable: studioAliasesTable,
|
||||
stringColumn: studioAliasColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
qb.aliasRepository().join(f, "", "studios.id")
|
||||
},
|
||||
}
|
||||
|
||||
return h.handler(alias)
|
||||
}
|
||||
|
||||
func (qb *studioQueryBuilder) getStudioSort(findFilter *models.FindFilterType) string {
|
||||
var sort string
|
||||
var direction string
|
||||
@@ -303,3 +375,22 @@ func (qb *studioQueryBuilder) GetStashIDs(studioID int) ([]*models.StashID, erro
|
||||
func (qb *studioQueryBuilder) UpdateStashIDs(studioID int, stashIDs []models.StashID) error {
|
||||
return qb.stashIDRepository().replace(studioID, stashIDs)
|
||||
}
|
||||
|
||||
func (qb *studioQueryBuilder) aliasRepository() *stringRepository {
|
||||
return &stringRepository{
|
||||
repository: repository{
|
||||
tx: qb.tx,
|
||||
tableName: studioAliasesTable,
|
||||
idColumn: studioIDColumn,
|
||||
},
|
||||
stringColumn: studioAliasColumn,
|
||||
}
|
||||
}
|
||||
|
||||
func (qb *studioQueryBuilder) GetAliases(studioID int) ([]string, error) {
|
||||
return qb.aliasRepository().get(studioID)
|
||||
}
|
||||
|
||||
func (qb *studioQueryBuilder) UpdateAliases(studioID int, aliases []string) error {
|
||||
return qb.aliasRepository().replace(studioID, aliases)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user