Add group graphql interfaces (#5017)

* Deprecate movie and add group interfaces
* UI changes
This commit is contained in:
WithoutPants
2024-07-03 13:59:40 +10:00
committed by GitHub
parent f477b996b5
commit 2739696813
108 changed files with 1437 additions and 567 deletions

View File

@@ -15,6 +15,7 @@ const (
FilterModeGalleries FilterMode = "GALLERIES"
FilterModeSceneMarkers FilterMode = "SCENE_MARKERS"
FilterModeMovies FilterMode = "MOVIES"
FilterModeGroups FilterMode = "GROUPS"
FilterModeTags FilterMode = "TAGS"
FilterModeImages FilterMode = "IMAGES"
)
@@ -25,6 +26,7 @@ var AllFilterMode = []FilterMode{
FilterModeStudios,
FilterModeGalleries,
FilterModeSceneMarkers,
FilterModeGroups,
FilterModeMovies,
FilterModeTags,
FilterModeImages,
@@ -32,7 +34,7 @@ var AllFilterMode = []FilterMode{
func (e FilterMode) IsValid() bool {
switch e {
case FilterModeScenes, FilterModePerformers, FilterModeStudios, FilterModeGalleries, FilterModeSceneMarkers, FilterModeMovies, FilterModeTags, FilterModeImages:
case FilterModeScenes, FilterModePerformers, FilterModeStudios, FilterModeGalleries, FilterModeSceneMarkers, FilterModeMovies, FilterModeGroups, FilterModeTags, FilterModeImages:
return true
}
return false

View File

@@ -414,3 +414,24 @@ type ScrapedMovie struct {
}
func (ScrapedMovie) IsScrapedContent() {}
// ScrapedGroup is a group from a scraping operation
type ScrapedGroup struct {
StoredID *string `json:"stored_id"`
Name *string `json:"name"`
Aliases *string `json:"aliases"`
Duration *string `json:"duration"`
Date *string `json:"date"`
Rating *string `json:"rating"`
Director *string `json:"director"`
URLs []string `json:"urls"`
Synopsis *string `json:"synopsis"`
Studio *ScrapedStudio `json:"studio"`
Tags []*ScrapedTag `json:"tags"`
// This should be a base64 encoded data URL
FrontImage *string `json:"front_image"`
// This should be a base64 encoded data URL
BackImage *string `json:"back_image"`
}
func (ScrapedGroup) IsScrapedContent() {}

View File

@@ -55,6 +55,8 @@ type SceneFilterType struct {
IsMissing *string `json:"is_missing"`
// Filter to only include scenes with this studio
Studios *HierarchicalMultiCriterionInput `json:"studios"`
// Filter to only include scenes with this group
Groups *MultiCriterionInput `json:"groups"`
// Filter to only include scenes with this movie
Movies *MultiCriterionInput `json:"movies"`
// Filter to only include scenes with this gallery
@@ -103,6 +105,8 @@ type SceneFilterType struct {
StudiosFilter *StudioFilterType `json:"studios_filter"`
// Filter by related tags that meet this criteria
TagsFilter *TagFilterType `json:"tags_filter"`
// Filter by related groups that meet this criteria
GroupsFilter *MovieFilterType `json:"groups_filter"`
// Filter by related movies that meet this criteria
MoviesFilter *MovieFilterType `json:"movies_filter"`
// Filter by related markers that meet this criteria
@@ -131,11 +135,17 @@ type SceneQueryResult struct {
resolveErr error
}
// SceneMovieInput is used for groups and movies
type SceneMovieInput struct {
MovieID string `json:"movie_id"`
SceneIndex *int `json:"scene_index"`
}
type SceneGroupInput struct {
GroupID string `json:"group_id"`
SceneIndex *int `json:"scene_index"`
}
type SceneCreateInput struct {
Title *string `json:"title"`
Code *string `json:"code"`
@@ -150,6 +160,7 @@ type SceneCreateInput struct {
GalleryIds []string `json:"gallery_ids"`
PerformerIds []string `json:"performer_ids"`
Movies []SceneMovieInput `json:"movies"`
Groups []SceneGroupInput `json:"groups"`
TagIds []string `json:"tag_ids"`
// This should be a URL or a base64 encoded data URL
CoverImage *string `json:"cover_image"`
@@ -177,6 +188,7 @@ type SceneUpdateInput struct {
GalleryIds []string `json:"gallery_ids"`
PerformerIds []string `json:"performer_ids"`
Movies []SceneMovieInput `json:"movies"`
Groups []SceneGroupInput `json:"groups"`
TagIds []string `json:"tag_ids"`
// This should be a URL or a base64 encoded data URL
CoverImage *string `json:"cover_image"`

View File

@@ -22,6 +22,8 @@ type TagFilterType struct {
PerformerCount *IntCriterionInput `json:"performer_count"`
// Filter by number of studios with this tag
StudioCount *IntCriterionInput `json:"studio_count"`
// Filter by number of groups with this tag
GroupCount *IntCriterionInput `json:"group_count"`
// Filter by number of movies with this tag
MovieCount *IntCriterionInput `json:"movie_count"`
// Filter by number of markers with this tag

View File

@@ -26,10 +26,16 @@ const (
GalleryChapterUpdatePost TriggerEnum = "GalleryChapter.Update.Post"
GalleryChapterDestroyPost TriggerEnum = "GalleryChapter.Destroy.Post"
// deprecated - use Group hooks instead
// for now, both movie and group hooks will be executed
MovieCreatePost TriggerEnum = "Movie.Create.Post"
MovieUpdatePost TriggerEnum = "Movie.Update.Post"
MovieDestroyPost TriggerEnum = "Movie.Destroy.Post"
GroupCreatePost TriggerEnum = "Group.Create.Post"
GroupUpdatePost TriggerEnum = "Group.Update.Post"
GroupDestroyPost TriggerEnum = "Group.Destroy.Post"
PerformerCreatePost TriggerEnum = "Performer.Create.Post"
PerformerUpdatePost TriggerEnum = "Performer.Update.Post"
PerformerDestroyPost TriggerEnum = "Performer.Destroy.Post"

View File

@@ -299,6 +299,7 @@ func (c config) spec() Scraper {
if len(movie.SupportedScrapes) > 0 {
ret.Movie = &movie
ret.Group = &movie
}
return ret
@@ -312,7 +313,7 @@ func (c config) supports(ty ScrapeContentType) bool {
return (c.SceneByName != nil && c.SceneByQueryFragment != nil) || c.SceneByFragment != nil || len(c.SceneByURL) > 0
case ScrapeContentTypeGallery:
return c.GalleryByFragment != nil || len(c.GalleryByURL) > 0
case ScrapeContentTypeMovie:
case ScrapeContentTypeMovie, ScrapeContentTypeGroup:
return len(c.MovieByURL) > 0
}
@@ -339,7 +340,7 @@ func (c config) matchesURL(url string, ty ScrapeContentType) bool {
return true
}
}
case ScrapeContentTypeMovie:
case ScrapeContentTypeMovie, ScrapeContentTypeGroup:
for _, scraper := range c.MovieByURL {
if scraper.matchesURL(url) {
return true

View File

@@ -81,7 +81,7 @@ func loadUrlCandidates(c config, ty ScrapeContentType) []*scrapeByURLConfig {
return c.PerformerByURL
case ScrapeContentTypeScene:
return c.SceneByURL
case ScrapeContentTypeMovie:
case ScrapeContentTypeMovie, ScrapeContentTypeGroup:
return c.MovieByURL
case ScrapeContentTypeGallery:
return c.GalleryByURL

View File

@@ -102,7 +102,7 @@ func (s *jsonScraper) scrapeByURL(ctx context.Context, url string, ty ScrapeCont
return nil, err
}
return ret, nil
case ScrapeContentTypeMovie:
case ScrapeContentTypeMovie, ScrapeContentTypeGroup:
ret, err := scraper.scrapeMovie(ctx, q)
if err != nil || ret == nil {
return nil, err

View File

@@ -18,6 +18,7 @@ type ScrapedScene struct {
Studio *models.ScrapedStudio `json:"studio"`
Tags []*models.ScrapedTag `json:"tags"`
Performers []*models.ScrapedPerformer `json:"performers"`
Groups []*models.ScrapedGroup `json:"groups"`
Movies []*models.ScrapedMovie `json:"movies"`
RemoteSiteID *string `json:"remote_site_id"`
Duration *int `json:"duration"`

View File

@@ -31,6 +31,7 @@ type ScrapeContentType string
const (
ScrapeContentTypeGallery ScrapeContentType = "GALLERY"
ScrapeContentTypeMovie ScrapeContentType = "MOVIE"
ScrapeContentTypeGroup ScrapeContentType = "GROUP"
ScrapeContentTypePerformer ScrapeContentType = "PERFORMER"
ScrapeContentTypeScene ScrapeContentType = "SCENE"
)
@@ -38,13 +39,14 @@ const (
var AllScrapeContentType = []ScrapeContentType{
ScrapeContentTypeGallery,
ScrapeContentTypeMovie,
ScrapeContentTypeGroup,
ScrapeContentTypePerformer,
ScrapeContentTypeScene,
}
func (e ScrapeContentType) IsValid() bool {
switch e {
case ScrapeContentTypeGallery, ScrapeContentTypeMovie, ScrapeContentTypePerformer, ScrapeContentTypeScene:
case ScrapeContentTypeGallery, ScrapeContentTypeMovie, ScrapeContentTypeGroup, ScrapeContentTypePerformer, ScrapeContentTypeScene:
return true
}
return false
@@ -81,6 +83,8 @@ type Scraper struct {
// Details for gallery scraper
Gallery *ScraperSpec `json:"gallery"`
// Details for movie scraper
Group *ScraperSpec `json:"group"`
// Details for movie scraper
Movie *ScraperSpec `json:"movie"`
}

View File

@@ -384,7 +384,7 @@ func (s *scriptScraper) scrape(ctx context.Context, input string, ty ScrapeConte
var scene *ScrapedScene
err := s.runScraperScript(ctx, input, &scene)
return scene, err
case ScrapeContentTypeMovie:
case ScrapeContentTypeMovie, ScrapeContentTypeGroup:
var movie *models.ScrapedMovie
err := s.runScraperScript(ctx, input, &movie)
return movie, err

View File

@@ -83,7 +83,7 @@ func (s *xpathScraper) scrapeByURL(ctx context.Context, url string, ty ScrapeCon
return nil, err
}
return ret, nil
case ScrapeContentTypeMovie:
case ScrapeContentTypeMovie, ScrapeContentTypeGroup:
ret, err := scraper.scrapeMovie(ctx, q)
if err != nil || ret == nil {
return nil, err

View File

@@ -1881,7 +1881,7 @@ func TestGalleryQueryIsMissingPerformers(t *testing.T) {
assert.True(t, len(galleries) > 0)
// ensure non of the ids equal the one with movies
// ensure non of the ids equal the one with galleries
for _, gallery := range galleries {
assert.NotEqual(t, galleryIDs[galleryIdxWithPerformer], gallery.ID)
}

View File

@@ -2053,7 +2053,7 @@ func TestImageQueryIsMissingPerformers(t *testing.T) {
assert.True(t, len(images) > 0)
// ensure non of the ids equal the one with movies
// ensure non of the ids equal the one with performers
for _, image := range images {
assert.NotEqual(t, imageIDs[imageIdxWithPerformer], image.ID)
}

View File

@@ -1330,7 +1330,7 @@ func verifyPerformerQuery(t *testing.T, filter models.PerformerFilterType, verif
for _, performer := range performers {
if err := performer.LoadURLs(ctx, db.Performer); err != nil {
t.Errorf("Error loading movie relationships: %v", err)
t.Errorf("Error loading url relationships: %v", err)
}
}

View File

@@ -228,10 +228,21 @@ func (qb *SavedFilterStore) getMany(ctx context.Context, q *goqu.SelectDataset)
func (qb *SavedFilterStore) FindByMode(ctx context.Context, mode models.FilterMode) ([]*models.SavedFilter, error) {
// SELECT * FROM %s WHERE mode = ? AND name != ? ORDER BY name ASC
table := qb.table()
sq := qb.selectDataset().Prepared(true).Where(
table.Col("mode").Eq(mode),
table.Col("name").Neq(savedFilterDefaultName),
).Order(table.Col("name").Asc())
// TODO - querying on groups needs to include movies
// remove this when we migrate to remove the movies filter mode in the database
var whereClause exp.Expression
if mode == models.FilterModeGroups || mode == models.FilterModeMovies {
whereClause = goqu.Or(
table.Col("mode").Eq(models.FilterModeGroups),
table.Col("mode").Eq(models.FilterModeMovies),
)
} else {
whereClause = table.Col("mode").Eq(mode)
}
sq := qb.selectDataset().Prepared(true).Where(whereClause).Order(table.Col("name").Asc())
ret, err := qb.getMany(ctx, sq)
if err != nil {

View File

@@ -1074,6 +1074,7 @@ var sceneSortOptions = sortOptions{
"duration",
"file_mod_time",
"framerate",
"group_scene_number",
"id",
"interactive",
"interactive_speed",
@@ -1140,7 +1141,7 @@ func (qb *SceneStore) setSceneSort(query *queryBuilder, findFilter *models.FindF
direction := findFilter.GetDirection()
switch sort {
case "movie_scene_number":
case "movie_scene_number", "group_scene_number":
query.join(moviesScenesTable, "", "scenes.id = movies_scenes.scene_id")
query.sortAndPagination += getSort("scene_index", direction, moviesScenesTable)
case "tag_count":

View File

@@ -147,7 +147,10 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
qb.performersCriterionHandler(sceneFilter.Performers),
qb.performerCountCriterionHandler(sceneFilter.PerformerCount),
studioCriterionHandler(sceneTable, sceneFilter.Studios),
qb.moviesCriterionHandler(sceneFilter.Movies),
qb.groupsCriterionHandler(sceneFilter.Groups),
qb.groupsCriterionHandler(sceneFilter.Movies),
qb.galleriesCriterionHandler(sceneFilter.Galleries),
qb.performerTagsCriterionHandler(sceneFilter.PerformerTags),
qb.performerFavoriteCriterionHandler(sceneFilter.PerformerFavorite),
@@ -480,7 +483,7 @@ func (qb *sceneFilterHandler) performerAgeCriterionHandler(performerAge *models.
}
}
func (qb *sceneFilterHandler) moviesCriterionHandler(movies *models.MultiCriterionInput) criterionHandlerFunc {
func (qb *sceneFilterHandler) groupsCriterionHandler(movies *models.MultiCriterionInput) criterionHandlerFunc {
addJoinsFunc := func(f *filterBuilder) {
sceneRepository.movies.join(f, "", "scenes.id")
f.addLeftJoin("movies", "", "movies_scenes.movie_id = movies.id")

View File

@@ -278,9 +278,7 @@ const (
)
const (
savedFilterIdxDefaultScene = iota
savedFilterIdxDefaultImage
savedFilterIdxScene
savedFilterIdxScene = iota
savedFilterIdxImage
// new indexes above
@@ -1777,9 +1775,9 @@ func createChapter(ctx context.Context, mqb models.GalleryChapterReaderWriter, c
func getSavedFilterMode(index int) models.FilterMode {
switch index {
case savedFilterIdxScene, savedFilterIdxDefaultScene:
case savedFilterIdxScene:
return models.FilterModeScenes
case savedFilterIdxImage, savedFilterIdxDefaultImage:
case savedFilterIdxImage:
return models.FilterModeImages
default:
return models.FilterModeScenes
@@ -1787,11 +1785,6 @@ func getSavedFilterMode(index int) models.FilterMode {
}
func getSavedFilterName(index int) string {
if index <= savedFilterIdxDefaultImage {
// empty string for default filters
return ""
}
if index <= savedFilterIdxImage {
// use the same name for the first two - should be possible
return firstSavedFilterName

View File

@@ -683,7 +683,7 @@ func (qb *TagStore) getTagSort(query *queryBuilder, findFilter *models.FindFilte
sortQuery += getCountSort(tagTable, performersTagsTable, tagIDColumn, direction)
case "studios_count":
sortQuery += getCountSort(tagTable, studiosTagsTable, tagIDColumn, direction)
case "movies_count":
case "movies_count", "groups_count":
sortQuery += getCountSort(tagTable, moviesTagsTable, tagIDColumn, direction)
default:
sortQuery += getSort(sort, direction, "tags")

View File

@@ -67,7 +67,10 @@ func (qb *tagFilterHandler) criterionHandler() criterionHandler {
qb.galleryCountCriterionHandler(tagFilter.GalleryCount),
qb.performerCountCriterionHandler(tagFilter.PerformerCount),
qb.studioCountCriterionHandler(tagFilter.StudioCount),
qb.movieCountCriterionHandler(tagFilter.MovieCount),
qb.groupCountCriterionHandler(tagFilter.GroupCount),
qb.groupCountCriterionHandler(tagFilter.MovieCount),
qb.markerCountCriterionHandler(tagFilter.MarkerCount),
qb.parentsCriterionHandler(tagFilter.Parents),
qb.childrenCriterionHandler(tagFilter.Children),
@@ -187,7 +190,7 @@ func (qb *tagFilterHandler) studioCountCriterionHandler(studioCount *models.IntC
}
}
func (qb *tagFilterHandler) movieCountCriterionHandler(movieCount *models.IntCriterionInput) criterionHandlerFunc {
func (qb *tagFilterHandler) groupCountCriterionHandler(movieCount *models.IntCriterionInput) criterionHandlerFunc {
return func(ctx context.Context, f *filterBuilder) {
if movieCount != nil {
f.addLeftJoin("movies_tags", "", "movies_tags.tag_id = tags.id")