Add Studio to movie and fix movie schema (#458)

* Add movie migration
* Update server and UI code for type changes
* Add studio to movies
* Movie blobs to end
* Document movie duration
* Add filtering on movie studio
This commit is contained in:
WithoutPants
2020-04-22 11:22:14 +10:00
committed by GitHub
parent f21e04dcbc
commit eee7adfb85
30 changed files with 531 additions and 144 deletions

View File

@@ -1,14 +1,16 @@
package models
import "database/sql"
type PerformersScenes struct {
PerformerID int `db:"performer_id" json:"performer_id"`
SceneID int `db:"scene_id" json:"scene_id"`
}
type MoviesScenes struct {
MovieID int `db:"movie_id" json:"movie_id"`
SceneID int `db:"scene_id" json:"scene_id"`
SceneIndex string `db:"scene_index" json:"scene_index"`
MovieID int `db:"movie_id" json:"movie_id"`
SceneID int `db:"scene_id" json:"scene_id"`
SceneIndex sql.NullInt64 `db:"scene_index" json:"scene_index"`
}
type ScenesTags struct {

View File

@@ -5,20 +5,39 @@ import (
)
type Movie struct {
ID int `db:"id" json:"id"`
FrontImage []byte `db:"front_image" json:"front_image"`
BackImage []byte `db:"back_image" json:"back_image"`
Checksum string `db:"checksum" json:"checksum"`
Name sql.NullString `db:"name" json:"name"`
Aliases sql.NullString `db:"aliases" json:"aliases"`
Duration sql.NullString `db:"duration" json:"duration"`
Date SQLiteDate `db:"date" json:"date"`
Rating sql.NullString `db:"rating" json:"rating"`
Director sql.NullString `db:"director" json:"director"`
Synopsis sql.NullString `db:"synopsis" json:"synopsis"`
URL sql.NullString `db:"url" json:"url"`
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
ID int `db:"id" json:"id"`
FrontImage []byte `db:"front_image" json:"front_image"`
BackImage []byte `db:"back_image" json:"back_image"`
Checksum string `db:"checksum" json:"checksum"`
Name sql.NullString `db:"name" json:"name"`
Aliases sql.NullString `db:"aliases" json:"aliases"`
Duration sql.NullInt64 `db:"duration" json:"duration"`
Date SQLiteDate `db:"date" json:"date"`
Rating sql.NullInt64 `db:"rating" json:"rating"`
StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
Director sql.NullString `db:"director" json:"director"`
Synopsis sql.NullString `db:"synopsis" json:"synopsis"`
URL sql.NullString `db:"url" json:"url"`
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
}
type MoviePartial struct {
ID int `db:"id" json:"id"`
FrontImage *[]byte `db:"front_image" json:"front_image"`
BackImage *[]byte `db:"back_image" json:"back_image"`
Checksum *string `db:"checksum" json:"checksum"`
Name *sql.NullString `db:"name" json:"name"`
Aliases *sql.NullString `db:"aliases" json:"aliases"`
Duration *sql.NullInt64 `db:"duration" json:"duration"`
Date *SQLiteDate `db:"date" json:"date"`
Rating *sql.NullInt64 `db:"rating" json:"rating"`
StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
Director *sql.NullString `db:"director" json:"director"`
Synopsis *sql.NullString `db:"synopsis" json:"synopsis"`
URL *sql.NullString `db:"url" json:"url"`
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
}
var DefaultMovieImage string = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wgVBQsJl1CMZAAAASJJREFUeNrt3N0JwyAYhlEj3cj9R3Cm5rbkqtAP+qrnGaCYHPwJpLlaa++mmLpbAERAgAgIEAEBIiBABERAgAgIEAEBIiBABERAgAgIEAHZuVflj40x4i94zhk9vqsVvEq6AsQqMP1EjORx20OACAgQRRx7T+zzcFBxcjNDfoB4ntQqTm5Awo7MlqywZxcgYQ+RlqywJ3ozJAQCSBiEJSsQA0gYBpDAgAARECACAkRAgAgIEAERECACAmSjUv6eAOSB8m8YIGGzBUjYbAESBgMkbBkDEjZbgITBAClcxiqQvEoatreYIWEBASIgJ4Gkf11ntXH3nS9uxfGWfJ5J9hAgAgJEQAQEiIAAERAgAgJEQAQEiIAAERAgAgJEQAQEiL7qBuc6RKLHxr0CAAAAAElFTkSuQmCC"

View File

@@ -161,7 +161,7 @@ func (qb *JoinsQueryBuilder) CreateMoviesScenes(newJoins []MoviesScenes, tx *sql
// if the movie already exists on the scene. It returns true if scene
// movie was added.
func (qb *JoinsQueryBuilder) AddMoviesScene(sceneID int, movieID int, sceneIdx string, tx *sqlx.Tx) (bool, error) {
func (qb *JoinsQueryBuilder) AddMoviesScene(sceneID int, movieID int, sceneIdx *int, tx *sqlx.Tx) (bool, error) {
ensureTx(tx)
existingMovies, err := qb.GetSceneMovies(sceneID, tx)
@@ -178,9 +178,15 @@ func (qb *JoinsQueryBuilder) AddMoviesScene(sceneID int, movieID int, sceneIdx s
}
movieJoin := MoviesScenes{
MovieID: movieID,
SceneID: sceneID,
SceneIndex: sceneIdx,
MovieID: movieID,
SceneID: sceneID,
}
if sceneIdx != nil {
movieJoin.SceneIndex = sql.NullInt64{
Int64: int64(*sceneIdx),
Valid: true,
}
}
movieJoins := append(existingMovies, movieJoin)

View File

@@ -2,6 +2,7 @@ package models
import (
"database/sql"
"strconv"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/database"
@@ -16,8 +17,8 @@ func NewMovieQueryBuilder() MovieQueryBuilder {
func (qb *MovieQueryBuilder) Create(newMovie Movie, tx *sqlx.Tx) (*Movie, error) {
ensureTx(tx)
result, err := tx.NamedExec(
`INSERT INTO movies (front_image, back_image, checksum, name, aliases, duration, date, rating, director, synopsis, url, created_at, updated_at)
VALUES (:front_image, :back_image, :checksum, :name, :aliases, :duration, :date, :rating, :director, :synopsis, :url, :created_at, :updated_at)
`INSERT INTO movies (front_image, back_image, checksum, name, aliases, duration, date, rating, studio_id, director, synopsis, url, created_at, updated_at)
VALUES (:front_image, :back_image, :checksum, :name, :aliases, :duration, :date, :rating, :studio_id, :director, :synopsis, :url, :created_at, :updated_at)
`,
newMovie,
)
@@ -35,20 +36,17 @@ func (qb *MovieQueryBuilder) Create(newMovie Movie, tx *sqlx.Tx) (*Movie, error)
return &newMovie, nil
}
func (qb *MovieQueryBuilder) Update(updatedMovie Movie, tx *sqlx.Tx) (*Movie, error) {
func (qb *MovieQueryBuilder) Update(updatedMovie MoviePartial, tx *sqlx.Tx) (*Movie, error) {
ensureTx(tx)
_, err := tx.NamedExec(
`UPDATE movies SET `+SQLGenKeys(updatedMovie)+` WHERE movies.id = :id`,
`UPDATE movies SET `+SQLGenKeysPartial(updatedMovie)+` WHERE movies.id = :id`,
updatedMovie,
)
if err != nil {
return nil, err
}
if err := tx.Get(&updatedMovie, `SELECT * FROM movies WHERE id = ? LIMIT 1`, updatedMovie.ID); err != nil {
return nil, err
}
return &updatedMovie, nil
return qb.Find(updatedMovie.ID, tx)
}
func (qb *MovieQueryBuilder) Destroy(id string, tx *sqlx.Tx) error {
@@ -113,10 +111,13 @@ func (qb *MovieQueryBuilder) AllSlim() ([]*Movie, error) {
return qb.queryMovies("SELECT movies.id, movies.name FROM movies "+qb.getMovieSort(nil), nil, nil)
}
func (qb *MovieQueryBuilder) Query(findFilter *FindFilterType) ([]*Movie, int) {
func (qb *MovieQueryBuilder) Query(movieFilter *MovieFilterType, findFilter *FindFilterType) ([]*Movie, int) {
if findFilter == nil {
findFilter = &FindFilterType{}
}
if movieFilter == nil {
movieFilter = &MovieFilterType{}
}
var whereClauses []string
var havingClauses []string
@@ -125,6 +126,7 @@ func (qb *MovieQueryBuilder) Query(findFilter *FindFilterType) ([]*Movie, int) {
body += `
left join movies_scenes as scenes_join on scenes_join.movie_id = movies.id
left join scenes on scenes_join.scene_id = scenes.id
left join studios as studio on studio.id = movies.studio_id
`
if q := findFilter.Q; q != nil && *q != "" {
@@ -134,6 +136,16 @@ func (qb *MovieQueryBuilder) Query(findFilter *FindFilterType) ([]*Movie, int) {
args = append(args, thisArgs...)
}
if studiosFilter := movieFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
for _, studioID := range studiosFilter.Value {
args = append(args, studioID)
}
whereClause, havingClause := qb.getMultiCriterionClause("studio", "", "studio_id", studiosFilter)
whereClauses = appendClause(whereClauses, whereClause)
havingClauses = appendClause(havingClauses, havingClause)
}
sortAndPagination := qb.getMovieSort(findFilter) + getPagination(findFilter)
idsResult, countResult := executeFindQuery("movies", body, args, sortAndPagination, whereClauses, havingClauses)
@@ -146,6 +158,29 @@ func (qb *MovieQueryBuilder) Query(findFilter *FindFilterType) ([]*Movie, int) {
return movies, countResult
}
// returns where clause and having clause
func (qb *MovieQueryBuilder) getMultiCriterionClause(table string, joinTable string, joinTableField string, criterion *MultiCriterionInput) (string, string) {
whereClause := ""
havingClause := ""
if criterion.Modifier == CriterionModifierIncludes {
// includes any of the provided ids
whereClause = table + ".id IN " + getInBinding(len(criterion.Value))
} else if criterion.Modifier == CriterionModifierIncludesAll {
// includes all of the provided ids
whereClause = table + ".id IN " + getInBinding(len(criterion.Value))
havingClause = "count(distinct " + table + ".id) IS " + strconv.Itoa(len(criterion.Value))
} else if criterion.Modifier == CriterionModifierExcludes {
// excludes all of the provided ids
if joinTable != "" {
whereClause = "not exists (select " + joinTable + ".movie_id from " + joinTable + " where " + joinTable + ".movie_id = movies.id and " + joinTable + "." + joinTableField + " in " + getInBinding(len(criterion.Value)) + ")"
} else {
whereClause = "not exists (select m.id from movies as m where m.id = movies.id and m." + joinTableField + " in " + getInBinding(len(criterion.Value)) + ")"
}
}
return whereClause, havingClause
}
func (qb *MovieQueryBuilder) getMovieSort(findFilter *FindFilterType) string {
var sort string
var direction string