mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
Movies Section (#338)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -9,4 +9,5 @@ const (
|
||||
performerKey key = 1
|
||||
sceneKey key = 2
|
||||
studioKey key = 3
|
||||
movieKey key = 4
|
||||
)
|
||||
|
||||
@@ -33,6 +33,9 @@ func (r *Resolver) SceneMarker() models.SceneMarkerResolver {
|
||||
func (r *Resolver) Studio() models.StudioResolver {
|
||||
return &studioResolver{r}
|
||||
}
|
||||
func (r *Resolver) Movie() models.MovieResolver {
|
||||
return &movieResolver{r}
|
||||
}
|
||||
func (r *Resolver) Subscription() models.SubscriptionResolver {
|
||||
return &subscriptionResolver{r}
|
||||
}
|
||||
@@ -49,6 +52,7 @@ type performerResolver struct{ *Resolver }
|
||||
type sceneResolver struct{ *Resolver }
|
||||
type sceneMarkerResolver struct{ *Resolver }
|
||||
type studioResolver struct{ *Resolver }
|
||||
type movieResolver struct{ *Resolver }
|
||||
type tagResolver struct{ *Resolver }
|
||||
|
||||
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) ([]*models.SceneMarker, error) {
|
||||
@@ -95,6 +99,8 @@ func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, err
|
||||
performersCount, _ := performersQB.Count()
|
||||
studiosQB := models.NewStudioQueryBuilder()
|
||||
studiosCount, _ := studiosQB.Count()
|
||||
moviesQB := models.NewMovieQueryBuilder()
|
||||
moviesCount, _ := moviesQB.Count()
|
||||
tagsQB := models.NewTagQueryBuilder()
|
||||
tagsCount, _ := tagsQB.Count()
|
||||
return &models.StatsResultType{
|
||||
@@ -102,6 +108,7 @@ func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, err
|
||||
GalleryCount: galleryCount,
|
||||
PerformerCount: performersCount,
|
||||
StudioCount: studiosCount,
|
||||
MovieCount: moviesCount,
|
||||
TagCount: tagsCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
84
pkg/api/resolver_model_movie.go
Normal file
84
pkg/api/resolver_model_movie.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
func (r *movieResolver) Name(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
if obj.Name.Valid {
|
||||
return &obj.Name.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) URL(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
if obj.URL.Valid {
|
||||
return &obj.URL.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) Aliases(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
if obj.Aliases.Valid {
|
||||
return &obj.Aliases.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) Duration(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
if obj.Duration.Valid {
|
||||
return &obj.Duration.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) Date(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
if obj.Date.Valid {
|
||||
result := utils.GetYMDFromDatabaseDate(obj.Date.String)
|
||||
return &result, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) Rating(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
if obj.Rating.Valid {
|
||||
return &obj.Rating.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) Director(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
if obj.Director.Valid {
|
||||
return &obj.Director.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) Synopsis(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
if obj.Synopsis.Valid {
|
||||
return &obj.Synopsis.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) FrontImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
frontimagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj.ID).GetMovieFrontImageURL()
|
||||
return &frontimagePath, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
backimagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj.ID).GetMovieBackImageURL()
|
||||
return &backimagePath, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (*int, error) {
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
res, err := qb.CountByMovieID(obj.ID)
|
||||
return &res, err
|
||||
}
|
||||
@@ -100,6 +100,31 @@ func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (*models.
|
||||
return qb.FindBySceneID(obj.ID)
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) ([]*models.SceneMovie, error) {
|
||||
joinQB := models.NewJoinsQueryBuilder()
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
|
||||
sceneMovies, err := joinQB.GetSceneMovies(obj.ID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret []*models.SceneMovie
|
||||
for _, sm := range sceneMovies {
|
||||
movie, err := qb.Find(sm.MovieID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sceneIdx := sm.SceneIndex
|
||||
ret = append(ret, &models.SceneMovie{
|
||||
Movie: movie,
|
||||
SceneIndex: &sceneIdx,
|
||||
})
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) ([]*models.Tag, error) {
|
||||
qb := models.NewTagQueryBuilder()
|
||||
return qb.FindBySceneID(obj.ID, nil)
|
||||
|
||||
178
pkg/api/resolver_mutation_movie.go
Normal file
178
pkg/api/resolver_mutation_movie.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/database"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCreateInput) (*models.Movie, error) {
|
||||
// generate checksum from movie name rather than image
|
||||
checksum := utils.MD5FromString(input.Name)
|
||||
|
||||
var frontimageData []byte
|
||||
var backimageData []byte
|
||||
var err error
|
||||
|
||||
if input.FrontImage == nil {
|
||||
input.FrontImage = &models.DefaultMovieImage
|
||||
}
|
||||
if input.BackImage == nil {
|
||||
input.BackImage = &models.DefaultMovieImage
|
||||
}
|
||||
// Process the base 64 encoded image string
|
||||
_, frontimageData, err = utils.ProcessBase64Image(*input.FrontImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Process the base 64 encoded image string
|
||||
_, backimageData, err = utils.ProcessBase64Image(*input.BackImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Populate a new movie from the input
|
||||
currentTime := time.Now()
|
||||
newMovie := models.Movie{
|
||||
BackImage: backimageData,
|
||||
FrontImage: frontimageData,
|
||||
Checksum: checksum,
|
||||
Name: sql.NullString{String: input.Name, Valid: true},
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
}
|
||||
|
||||
if input.Aliases != nil {
|
||||
newMovie.Aliases = sql.NullString{String: *input.Aliases, Valid: true}
|
||||
}
|
||||
if input.Duration != nil {
|
||||
newMovie.Duration = sql.NullString{String: *input.Duration, Valid: true}
|
||||
}
|
||||
|
||||
if input.Date != nil {
|
||||
newMovie.Date = models.SQLiteDate{String: *input.Date, Valid: true}
|
||||
}
|
||||
|
||||
if input.Rating != nil {
|
||||
newMovie.Rating = sql.NullString{String: *input.Rating, Valid: true}
|
||||
}
|
||||
|
||||
if input.Director != nil {
|
||||
newMovie.Director = sql.NullString{String: *input.Director, Valid: true}
|
||||
}
|
||||
|
||||
if input.Synopsis != nil {
|
||||
newMovie.Synopsis = sql.NullString{String: *input.Synopsis, Valid: true}
|
||||
}
|
||||
|
||||
if input.URL != nil {
|
||||
newMovie.URL = sql.NullString{String: *input.URL, Valid: true}
|
||||
}
|
||||
|
||||
// Start the transaction and save the movie
|
||||
tx := database.DB.MustBeginTx(ctx, nil)
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
movie, err := qb.Create(newMovie, tx)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Commit
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUpdateInput) (*models.Movie, error) {
|
||||
// Populate movie from the input
|
||||
movieID, _ := strconv.Atoi(input.ID)
|
||||
updatedMovie := models.Movie{
|
||||
ID: movieID,
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: time.Now()},
|
||||
}
|
||||
if input.FrontImage != nil {
|
||||
_, frontimageData, err := utils.ProcessBase64Image(*input.FrontImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updatedMovie.FrontImage = frontimageData
|
||||
}
|
||||
if input.BackImage != nil {
|
||||
_, backimageData, err := utils.ProcessBase64Image(*input.BackImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updatedMovie.BackImage = backimageData
|
||||
}
|
||||
|
||||
if input.Name != nil {
|
||||
// generate checksum from movie name rather than image
|
||||
checksum := utils.MD5FromString(*input.Name)
|
||||
updatedMovie.Name = sql.NullString{String: *input.Name, Valid: true}
|
||||
updatedMovie.Checksum = checksum
|
||||
}
|
||||
|
||||
if input.Aliases != nil {
|
||||
updatedMovie.Aliases = sql.NullString{String: *input.Aliases, Valid: true}
|
||||
}
|
||||
if input.Duration != nil {
|
||||
updatedMovie.Duration = sql.NullString{String: *input.Duration, Valid: true}
|
||||
}
|
||||
|
||||
if input.Date != nil {
|
||||
updatedMovie.Date = models.SQLiteDate{String: *input.Date, Valid: true}
|
||||
}
|
||||
|
||||
if input.Rating != nil {
|
||||
updatedMovie.Rating = sql.NullString{String: *input.Rating, Valid: true}
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
// Start the transaction and save the movie
|
||||
tx := database.DB.MustBeginTx(ctx, nil)
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
movie, err := qb.Update(updatedMovie, tx)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Commit
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return movie, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) MovieDestroy(ctx context.Context, input models.MovieDestroyInput) (bool, error) {
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
tx := database.DB.MustBeginTx(ctx, nil)
|
||||
if err := qb.Destroy(input.ID, tx); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return false, err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -147,6 +147,28 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save the movies
|
||||
var movieJoins []models.MoviesScenes
|
||||
|
||||
for _, movie := range input.Movies {
|
||||
|
||||
movieID, _ := strconv.Atoi(movie.MovieID)
|
||||
sceneIdx := ""
|
||||
if movie.SceneIndex != nil {
|
||||
sceneIdx = *movie.SceneIndex
|
||||
}
|
||||
|
||||
movieJoin := models.MoviesScenes{
|
||||
MovieID: movieID,
|
||||
SceneID: sceneID,
|
||||
SceneIndex: sceneIdx,
|
||||
}
|
||||
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 {
|
||||
|
||||
28
pkg/api/resolver_query_find_movie.go
Normal file
28
pkg/api/resolver_query_find_movie.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
func (r *queryResolver) FindMovie(ctx context.Context, id string) (*models.Movie, error) {
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
idInt, _ := strconv.Atoi(id)
|
||||
return qb.Find(idInt, nil)
|
||||
}
|
||||
|
||||
func (r *queryResolver) FindMovies(ctx context.Context, filter *models.FindFilterType) (*models.FindMoviesResultType, error) {
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
movies, total := qb.Query(filter)
|
||||
return &models.FindMoviesResultType{
|
||||
Count: total,
|
||||
Movies: movies,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) AllMovies(ctx context.Context) ([]*models.Movie, error) {
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
return qb.All()
|
||||
}
|
||||
54
pkg/api/routes_movie.go
Normal file
54
pkg/api/routes_movie.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type movieRoutes struct{}
|
||||
|
||||
func (rs movieRoutes) Routes() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Route("/{movieId}", func(r chi.Router) {
|
||||
r.Use(MovieCtx)
|
||||
r.Get("/frontimage", rs.FrontImage)
|
||||
r.Get("/backimage", rs.BackImage)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (rs movieRoutes) FrontImage(w http.ResponseWriter, r *http.Request) {
|
||||
movie := r.Context().Value(movieKey).(*models.Movie)
|
||||
_, _ = w.Write(movie.FrontImage)
|
||||
}
|
||||
|
||||
func (rs movieRoutes) BackImage(w http.ResponseWriter, r *http.Request) {
|
||||
movie := r.Context().Value(movieKey).(*models.Movie)
|
||||
_, _ = w.Write(movie.BackImage)
|
||||
}
|
||||
|
||||
func MovieCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
movieID, err := strconv.Atoi(chi.URLParam(r, "movieId"))
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(404), 404)
|
||||
return
|
||||
}
|
||||
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
movie, err := qb.Find(movieID, nil)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(404), 404)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), movieKey, movie)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
@@ -109,6 +109,7 @@ func Start() {
|
||||
r.Mount("/performer", performerRoutes{}.Routes())
|
||||
r.Mount("/scene", sceneRoutes{}.Routes())
|
||||
r.Mount("/studio", studioRoutes{}.Routes())
|
||||
r.Mount("/movie", movieRoutes{}.Routes())
|
||||
|
||||
r.HandleFunc("/css", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
|
||||
24
pkg/api/urlbuilders/movie.go
Normal file
24
pkg/api/urlbuilders/movie.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package urlbuilders
|
||||
|
||||
import "strconv"
|
||||
|
||||
type MovieURLBuilder struct {
|
||||
BaseURL string
|
||||
MovieID string
|
||||
}
|
||||
|
||||
func NewMovieURLBuilder(baseURL string, movieID int) MovieURLBuilder {
|
||||
return MovieURLBuilder{
|
||||
BaseURL: baseURL,
|
||||
MovieID: strconv.Itoa(movieID),
|
||||
}
|
||||
}
|
||||
|
||||
func (b MovieURLBuilder) GetMovieFrontImageURL() string {
|
||||
return b.BaseURL + "/movie/" + b.MovieID + "/frontimage"
|
||||
}
|
||||
|
||||
func (b MovieURLBuilder) GetMovieBackImageURL() string {
|
||||
return b.BaseURL + "/movie/" + b.MovieID + "/backimage"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user