Moved everything out of internal

This commit is contained in:
Stash Dev
2019-02-09 18:53:08 -08:00
parent 5b5a72364f
commit 59782a377f
92 changed files with 87 additions and 85 deletions

8
api/api-packr.go Normal file
View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package api
import _ "github.com/stashapp/stash/packrd"

156
api/resolver.go Normal file
View File

@@ -0,0 +1,156 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
"github.com/stashapp/stash/scraper"
"sort"
"strconv"
)
type Resolver struct{}
func (r *Resolver) Gallery() models.GalleryResolver {
return &galleryResolver{r}
}
func (r *Resolver) Mutation() models.MutationResolver {
return &mutationResolver{r}
}
func (r *Resolver) Performer() models.PerformerResolver {
return &performerResolver{r}
}
func (r *Resolver) Query() models.QueryResolver {
return &queryResolver{r}
}
func (r *Resolver) Scene() models.SceneResolver {
return &sceneResolver{r}
}
func (r *Resolver) SceneMarker() models.SceneMarkerResolver {
return &sceneMarkerResolver{r}
}
func (r *Resolver) Studio() models.StudioResolver {
return &studioResolver{r}
}
func (r *Resolver) Subscription() models.SubscriptionResolver {
return &subscriptionResolver{r}
}
func (r *Resolver) Tag() models.TagResolver {
return &tagResolver{r}
}
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type subscriptionResolver struct{ *Resolver }
type galleryResolver struct{ *Resolver }
type performerResolver struct{ *Resolver }
type sceneResolver struct{ *Resolver }
type sceneMarkerResolver struct{ *Resolver }
type studioResolver struct{ *Resolver }
type tagResolver struct{ *Resolver }
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) ([]models.SceneMarker, error) {
qb := models.NewSceneMarkerQueryBuilder()
return qb.Wall(q)
}
func (r *queryResolver) SceneWall(ctx context.Context, q *string) ([]models.Scene, error) {
qb := models.NewSceneQueryBuilder()
return qb.Wall(q)
}
func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *string) ([]*models.MarkerStringsResultType, error) {
qb := models.NewSceneMarkerQueryBuilder()
return qb.GetMarkerStrings(q, sort)
}
func (r *queryResolver) ValidGalleriesForScene(ctx context.Context, scene_id *string) ([]models.Gallery, error) {
if scene_id == nil {
panic("nil scene id") // TODO make scene_id mandatory
}
sceneID, _ := strconv.Atoi(*scene_id)
sqb := models.NewSceneQueryBuilder()
scene, err := sqb.Find(sceneID)
if err != nil {
return nil, err
}
qb := models.NewGalleryQueryBuilder()
validGalleries, err := qb.ValidGalleriesForScenePath(scene.Path)
sceneGallery, _ := qb.FindBySceneID(sceneID, nil)
if sceneGallery != nil {
validGalleries = append(validGalleries, *sceneGallery)
}
return validGalleries, nil
}
func (r *queryResolver) Stats(ctx context.Context) (models.StatsResultType, error) {
//scenesCount, _ := runCountQuery(buildCountQuery(selectAll("scenes")), nil)
//galleryCount, _ := runCountQuery(buildCountQuery(selectAll("galleries")), nil)
//performersCount, _ := runCountQuery(buildCountQuery(selectAll("performers")), nil)
//studiosCount, _ := runCountQuery(buildCountQuery(selectAll("studios")), nil)
//tagsCount, _ := runCountQuery(buildCountQuery(selectAll("tags")), nil)
//return StatsResultType{
// SceneCount: scenesCount,
// GalleryCount: galleryCount,
// PerformerCount: performersCount,
// StudioCount: studiosCount,
// TagCount: tagsCount,
//}, nil
return models.StatsResultType{}, nil // TODO
}
// Get scene marker tags which show up under the video.
func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([]models.SceneMarkerTag, error) {
sceneID, _ := strconv.Atoi(scene_id)
sqb := models.NewSceneMarkerQueryBuilder()
sceneMarkers, err := sqb.FindBySceneID(sceneID, nil)
if err != nil {
return nil, err
}
tags := make(map[int]*models.SceneMarkerTag)
var keys []int
tqb := models.NewTagQueryBuilder()
for _, sceneMarker := range sceneMarkers {
if !sceneMarker.PrimaryTagID.Valid {
panic("missing primary tag id")
}
markerPrimaryTag, err := tqb.Find(int(sceneMarker.PrimaryTagID.Int64), nil)
if err != nil {
return nil, err
}
_, hasKey := tags[markerPrimaryTag.ID]
var sceneMarkerTag *models.SceneMarkerTag
if !hasKey {
sceneMarkerTag = &models.SceneMarkerTag{ Tag: *markerPrimaryTag }
tags[markerPrimaryTag.ID] = sceneMarkerTag
keys = append(keys, markerPrimaryTag.ID)
} else {
sceneMarkerTag = tags[markerPrimaryTag.ID]
}
tags[markerPrimaryTag.ID].SceneMarkers = append(tags[markerPrimaryTag.ID].SceneMarkers, sceneMarker)
}
// Sort so that primary tags that show up earlier in the video are first.
sort.Slice(keys, func(i, j int) bool {
a := tags[keys[i]]
b := tags[keys[j]]
return a.SceneMarkers[0].Seconds < b.SceneMarkers[0].Seconds
})
var result []models.SceneMarkerTag
for _, key := range keys {
result = append(result, *tags[key])
}
return result, nil
}
func (r *queryResolver) ScrapeFreeones(ctx context.Context, performer_name string) (*models.ScrapedPerformer, error) {
return scraper.GetPerformer(performer_name)
}
func (r *queryResolver) ScrapeFreeonesPerformerList(ctx context.Context, query string) ([]string, error) {
return scraper.GetPerformerNames(query)
}

View File

@@ -0,0 +1,20 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *galleryResolver) ID(ctx context.Context, obj *models.Gallery) (string, error) {
return strconv.Itoa(obj.ID), nil
}
func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*string, error) {
return nil, nil // TODO remove this from schema
}
func (r *galleryResolver) Files(ctx context.Context, obj *models.Gallery) ([]models.GalleryFilesType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
return obj.GetFiles(baseURL), nil
}

View File

@@ -0,0 +1,141 @@
package api
import (
"context"
"github.com/stashapp/stash/api/urlbuilders"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *performerResolver) ID(ctx context.Context, obj *models.Performer) (string, error) {
return strconv.Itoa(obj.ID), nil
}
func (r *performerResolver) Name(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Name.Valid {
return &obj.Name.String, nil
}
return nil, nil
}
func (r *performerResolver) URL(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Url.Valid {
return &obj.Url.String, nil
}
return nil, nil
}
func (r *performerResolver) Twitter(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Twitter.Valid {
return &obj.Twitter.String, nil
}
return nil, nil
}
func (r *performerResolver) Instagram(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Instagram.Valid {
return &obj.Instagram.String, nil
}
return nil, nil
}
func (r *performerResolver) Birthdate(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Birthdate.Valid {
return &obj.Birthdate.String, nil
}
return nil, nil
}
func (r *performerResolver) Ethnicity(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Ethnicity.Valid {
return &obj.Ethnicity.String, nil
}
return nil, nil
}
func (r *performerResolver) Country(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Country.Valid {
return &obj.Country.String, nil
}
return nil, nil
}
func (r *performerResolver) EyeColor(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.EyeColor.Valid {
return &obj.EyeColor.String, nil
}
return nil, nil
}
func (r *performerResolver) Height(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Height.Valid {
return &obj.Height.String, nil
}
return nil, nil
}
func (r *performerResolver) Measurements(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Measurements.Valid {
return &obj.Measurements.String, nil
}
return nil, nil
}
func (r *performerResolver) FakeTits(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.FakeTits.Valid {
return &obj.FakeTits.String, nil
}
return nil, nil
}
func (r *performerResolver) CareerLength(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.CareerLength.Valid {
return &obj.CareerLength.String, nil
}
return nil, nil
}
func (r *performerResolver) Tattoos(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Tattoos.Valid {
return &obj.Tattoos.String, nil
}
return nil, nil
}
func (r *performerResolver) Piercings(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Piercings.Valid {
return &obj.Piercings.String, nil
}
return nil, nil
}
func (r *performerResolver) Aliases(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Aliases.Valid {
return &obj.Aliases.String, nil
}
return nil, nil
}
func (r *performerResolver) Favorite(ctx context.Context, obj *models.Performer) (bool, error) {
if obj.Favorite.Valid {
return obj.Favorite.Bool, nil
}
return false, nil
}
func (r *performerResolver) ImagePath(ctx context.Context, obj *models.Performer) (*string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
imagePath := urlbuilders.NewPerformerURLBuilder(baseURL, obj.ID).GetPerformerImageUrl()
return &imagePath, nil
}
func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (*int, error) {
qb := models.NewSceneQueryBuilder()
res, err := qb.CountByPerformerID(obj.ID)
return &res, err
}
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) ([]models.Scene, error) {
qb := models.NewSceneQueryBuilder()
return qb.FindByPerformerID(obj.ID)
}

116
api/resolver_model_scene.go Normal file
View File

@@ -0,0 +1,116 @@
package api
import (
"context"
"github.com/stashapp/stash/api/urlbuilders"
"github.com/stashapp/stash/manager"
"github.com/stashapp/stash/models"
"github.com/stashapp/stash/utils"
"strconv"
)
func (r *sceneResolver) ID(ctx context.Context, obj *models.Scene) (string, error) {
return strconv.Itoa(obj.ID), nil
}
func (r *sceneResolver) Title(ctx context.Context, obj *models.Scene) (*string, error) {
if obj.Title.Valid {
return &obj.Title.String, nil
}
return nil, nil
}
func (r *sceneResolver) Details(ctx context.Context, obj *models.Scene) (*string, error) {
if obj.Details.Valid {
return &obj.Details.String, nil
}
return nil, nil
}
func (r *sceneResolver) URL(ctx context.Context, obj *models.Scene) (*string, error) {
if obj.Url.Valid {
return &obj.Url.String, nil
}
return nil, nil
}
func (r *sceneResolver) Date(ctx context.Context, obj *models.Scene) (*string, error) {
if obj.Date.Valid {
result := utils.GetYMDFromDatabaseDate(obj.Date.String)
return &result, nil
}
return nil, nil
}
func (r *sceneResolver) Rating(ctx context.Context, obj *models.Scene) (*int, error) {
if obj.Rating.Valid {
rating := int(obj.Rating.Int64)
return &rating, nil
}
return nil, nil
}
func (r *sceneResolver) File(ctx context.Context, obj *models.Scene) (models.SceneFileType, error) {
width := int(obj.Width.Int64)
height := int(obj.Height.Int64)
bitrate := int(obj.Bitrate.Int64)
return models.SceneFileType{
Size: &obj.Size.String,
Duration: &obj.Duration.Float64,
VideoCodec: &obj.VideoCodec.String,
AudioCodec: &obj.AudioCodec.String,
Width: &width,
Height: &height,
Framerate: &obj.Framerate.Float64,
Bitrate: &bitrate,
}, nil
}
func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (models.ScenePathsType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
builder := urlbuilders.NewSceneURLBuilder(baseURL, obj.ID)
screenshotPath := builder.GetScreenshotUrl()
previewPath := builder.GetStreamPreviewUrl()
streamPath := builder.GetStreamUrl()
webpPath := builder.GetStreamPreviewImageUrl()
vttPath := builder.GetSpriteVttUrl()
chaptersVttPath := builder.GetChaptersVttUrl()
return models.ScenePathsType{
Screenshot: &screenshotPath,
Preview: &previewPath,
Stream: &streamPath,
Webp: &webpPath,
Vtt: &vttPath,
ChaptersVtt: &chaptersVttPath,
}, nil
}
func (r *sceneResolver) IsStreamable(ctx context.Context, obj *models.Scene) (bool, error) {
return manager.IsStreamable(obj.Path, obj.Checksum)
}
func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) ([]models.SceneMarker, error) {
qb := models.NewSceneMarkerQueryBuilder()
return qb.FindBySceneID(obj.ID, nil)
}
func (r *sceneResolver) Gallery(ctx context.Context, obj *models.Scene) (*models.Gallery, error) {
qb := models.NewGalleryQueryBuilder()
return qb.FindBySceneID(obj.ID, nil)
}
func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (*models.Studio, error) {
qb := models.NewStudioQueryBuilder()
return qb.FindBySceneID(obj.ID)
}
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) ([]models.Tag, error) {
qb := models.NewTagQueryBuilder()
return qb.FindBySceneID(obj.ID, nil)
}
func (r *sceneResolver) Performers(ctx context.Context, obj *models.Scene) ([]models.Performer, error) {
qb := models.NewPerformerQueryBuilder()
return qb.FindBySceneID(obj.ID, nil)
}

View File

@@ -0,0 +1,48 @@
package api
import (
"context"
"github.com/stashapp/stash/api/urlbuilders"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *sceneMarkerResolver) ID(ctx context.Context, obj *models.SceneMarker) (string, error) {
return strconv.Itoa(obj.ID), nil
}
func (r *sceneMarkerResolver) Scene(ctx context.Context, obj *models.SceneMarker) (models.Scene, error) {
if !obj.SceneID.Valid {
panic("Invalid scene id")
}
qb := models.NewSceneQueryBuilder()
sceneID := int(obj.SceneID.Int64)
scene, err := qb.Find(sceneID)
return *scene, err
}
func (r *sceneMarkerResolver) PrimaryTag(ctx context.Context, obj *models.SceneMarker) (models.Tag, error) {
qb := models.NewTagQueryBuilder()
if !obj.PrimaryTagID.Valid {
panic("TODO no primary tag id")
}
tag, err := qb.Find(int(obj.PrimaryTagID.Int64), nil) // TODO make primary tag id not null in DB
return *tag, err
}
func (r *sceneMarkerResolver) Tags(ctx context.Context, obj *models.SceneMarker) ([]models.Tag, error) {
qb := models.NewTagQueryBuilder()
return qb.FindBySceneMarkerID(obj.ID, nil)
}
func (r *sceneMarkerResolver) Stream(ctx context.Context, obj *models.SceneMarker) (string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
sceneID := int(obj.SceneID.Int64)
return urlbuilders.NewSceneURLBuilder(baseURL, sceneID).GetSceneMarkerStreamUrl(obj.ID), nil
}
func (r *sceneMarkerResolver) Preview(ctx context.Context, obj *models.SceneMarker) (string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
sceneID := int(obj.SceneID.Int64)
return urlbuilders.NewSceneURLBuilder(baseURL, sceneID).GetSceneMarkerStreamPreviewUrl(obj.ID), nil
}

View File

@@ -0,0 +1,38 @@
package api
import (
"context"
"github.com/stashapp/stash/api/urlbuilders"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *studioResolver) ID(ctx context.Context, obj *models.Studio) (string, error) {
return strconv.Itoa(obj.ID), nil
}
func (r *studioResolver) Name(ctx context.Context, obj *models.Studio) (string, error) {
if obj.Name.Valid {
return obj.Name.String, nil
}
panic("null name") // TODO make name required
}
func (r *studioResolver) URL(ctx context.Context, obj *models.Studio) (*string, error) {
if obj.Url.Valid {
return &obj.Url.String, nil
}
return nil, nil
}
func (r *studioResolver) ImagePath(ctx context.Context, obj *models.Studio) (*string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
imagePath := urlbuilders.NewStudioURLBuilder(baseURL, obj.ID).GetStudioImageUrl()
return &imagePath, nil
}
func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio) (*int, error) {
qb := models.NewSceneQueryBuilder()
res, err := qb.CountByStudioID(obj.ID)
return &res, err
}

29
api/resolver_model_tag.go Normal file
View File

@@ -0,0 +1,29 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *tagResolver) ID(ctx context.Context, obj *models.Tag) (string, error) {
return strconv.Itoa(obj.ID), nil
}
func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag) (*int, error) {
qb := models.NewSceneQueryBuilder()
if obj == nil {
return nil, nil
}
count, err := qb.CountByTagID(obj.ID)
return &count, err
}
func (r *tagResolver) SceneMarkerCount(ctx context.Context, obj *models.Tag) (*int, error) {
qb := models.NewSceneMarkerQueryBuilder()
if obj == nil {
return nil, nil
}
count, err := qb.CountByTagID(obj.ID)
return &count, err
}

View File

@@ -0,0 +1,173 @@
package api
import (
"context"
"database/sql"
"github.com/stashapp/stash/database"
"github.com/stashapp/stash/models"
"github.com/stashapp/stash/utils"
"strconv"
"time"
)
func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.PerformerCreateInput) (*models.Performer, error) {
// Process the base 64 encoded image string
checksum, imageData, err := utils.ProcessBase64Image(input.Image)
if err != nil {
return nil, err
}
// Populate a new performer from the input
currentTime := time.Now()
newPerformer := models.Performer{
Image: imageData,
Checksum: checksum,
CreatedAt: models.SQLiteTimestamp{ Timestamp: currentTime },
UpdatedAt: models.SQLiteTimestamp{ Timestamp: currentTime },
}
if input.Name != nil {
newPerformer.Name = sql.NullString{ String: *input.Name, Valid: true }
}
if input.URL != nil {
newPerformer.Url = sql.NullString{ String: *input.URL, Valid: true }
}
if input.Birthdate != nil {
newPerformer.Birthdate = sql.NullString{ String: *input.Birthdate, Valid: true }
}
if input.Ethnicity != nil {
newPerformer.Ethnicity = sql.NullString{ String: *input.Ethnicity, Valid: true }
}
if input.Country != nil {
newPerformer.Country = sql.NullString{ String: *input.Country, Valid: true }
}
if input.EyeColor != nil {
newPerformer.EyeColor = sql.NullString{ String: *input.EyeColor, Valid: true }
}
if input.Height != nil {
newPerformer.Height = sql.NullString{ String: *input.Height, Valid: true }
}
if input.Measurements != nil {
newPerformer.Measurements = sql.NullString{ String: *input.Measurements, Valid: true }
}
if input.FakeTits != nil {
newPerformer.FakeTits = sql.NullString{ String: *input.FakeTits, Valid: true }
}
if input.CareerLength != nil {
newPerformer.CareerLength = sql.NullString{ String: *input.CareerLength, Valid: true }
}
if input.Tattoos != nil {
newPerformer.Tattoos = sql.NullString{ String: *input.Tattoos, Valid: true }
}
if input.Piercings != nil {
newPerformer.Piercings = sql.NullString{ String: *input.Piercings, Valid: true }
}
if input.Aliases != nil {
newPerformer.Aliases = sql.NullString{ String: *input.Aliases, Valid: true }
}
if input.Twitter != nil {
newPerformer.Twitter = sql.NullString{ String: *input.Twitter, Valid: true }
}
if input.Instagram != nil {
newPerformer.Instagram = sql.NullString{ String: *input.Instagram, Valid: true }
}
if input.Favorite != nil {
newPerformer.Favorite = sql.NullBool{ Bool: *input.Favorite, Valid: true }
}
// Start the transaction and save the performer
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewPerformerQueryBuilder()
performer, err := qb.Create(newPerformer, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return performer, nil
}
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.PerformerUpdateInput) (*models.Performer, error) {
// Populate performer from the input
performerID, _ := strconv.Atoi(input.ID)
updatedPerformer := models.Performer{
ID: performerID,
UpdatedAt: models.SQLiteTimestamp{ Timestamp: time.Now() },
}
if input.Image != nil {
checksum, imageData, err := utils.ProcessBase64Image(*input.Image)
if err != nil {
return nil, err
}
updatedPerformer.Image = imageData
updatedPerformer.Checksum = checksum
}
if input.Name != nil {
updatedPerformer.Name = sql.NullString{ String: *input.Name, Valid: true }
}
if input.URL != nil {
updatedPerformer.Url = sql.NullString{ String: *input.URL, Valid: true }
}
if input.Birthdate != nil {
updatedPerformer.Birthdate = sql.NullString{ String: *input.Birthdate, Valid: true }
}
if input.Ethnicity != nil {
updatedPerformer.Ethnicity = sql.NullString{ String: *input.Ethnicity, Valid: true }
}
if input.Country != nil {
updatedPerformer.Country = sql.NullString{ String: *input.Country, Valid: true }
}
if input.EyeColor != nil {
updatedPerformer.EyeColor = sql.NullString{ String: *input.EyeColor, Valid: true }
}
if input.Height != nil {
updatedPerformer.Height = sql.NullString{ String: *input.Height, Valid: true }
}
if input.Measurements != nil {
updatedPerformer.Measurements = sql.NullString{ String: *input.Measurements, Valid: true }
}
if input.FakeTits != nil {
updatedPerformer.FakeTits = sql.NullString{ String: *input.FakeTits, Valid: true }
}
if input.CareerLength != nil {
updatedPerformer.CareerLength = sql.NullString{ String: *input.CareerLength, Valid: true }
}
if input.Tattoos != nil {
updatedPerformer.Tattoos = sql.NullString{ String: *input.Tattoos, Valid: true }
}
if input.Piercings != nil {
updatedPerformer.Piercings = sql.NullString{ String: *input.Piercings, Valid: true }
}
if input.Aliases != nil {
updatedPerformer.Aliases = sql.NullString{ String: *input.Aliases, Valid: true }
}
if input.Twitter != nil {
updatedPerformer.Twitter = sql.NullString{ String: *input.Twitter, Valid: true }
}
if input.Instagram != nil {
updatedPerformer.Instagram = sql.NullString{ String: *input.Instagram, Valid: true }
}
if input.Favorite != nil {
updatedPerformer.Favorite = sql.NullBool{ Bool: *input.Favorite, Valid: true }
}
// Start the transaction and save the performer
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewPerformerQueryBuilder()
performer, err := qb.Update(updatedPerformer, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return performer, nil
}

View File

@@ -0,0 +1,208 @@
package api
import (
"context"
"database/sql"
"github.com/stashapp/stash/database"
"github.com/stashapp/stash/models"
"strconv"
"time"
)
func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUpdateInput) (*models.Scene, error) {
// Populate scene from the input
sceneID, _ := strconv.Atoi(input.ID)
updatedTime := time.Now()
updatedScene := models.Scene{
ID: sceneID,
UpdatedAt: models.SQLiteTimestamp{ Timestamp: updatedTime },
}
if input.Title != nil {
updatedScene.Title = sql.NullString{ String: *input.Title, Valid: true }
}
if input.Details != nil {
updatedScene.Details = sql.NullString{ String: *input.Details, Valid: true }
}
if input.URL != nil {
updatedScene.Url = sql.NullString{ String: *input.URL, Valid: true }
}
if input.Date != nil {
updatedScene.Date = sql.NullString{ String: *input.Date, Valid: true }
}
if input.Rating != nil {
updatedScene.Rating = sql.NullInt64{ Int64: int64(*input.Rating), Valid: true }
}
if input.StudioID != nil {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
updatedScene.StudioID = sql.NullInt64{ Int64: studioID, Valid: true }
}
// Start the transaction and save the scene marker
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewSceneQueryBuilder()
jqb := models.NewJoinsQueryBuilder()
scene, err := qb.Update(updatedScene, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
if input.GalleryID != nil {
// Save the gallery
galleryID, _ := strconv.Atoi(*input.GalleryID)
updatedGallery := models.Gallery{
ID: galleryID,
SceneID: sql.NullInt64{ Int64: int64(sceneID), Valid: true },
UpdatedAt: models.SQLiteTimestamp{Timestamp: updatedTime},
}
gqb := models.NewGalleryQueryBuilder()
_, err := gqb.Update(updatedGallery, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
}
// Save the performers
var performerJoins []models.PerformersScenes
for _, pid := range input.PerformerIds {
performerID, _ := strconv.Atoi(pid)
performerJoin := models.PerformersScenes{
PerformerID: performerID,
SceneID: sceneID,
}
performerJoins = append(performerJoins, performerJoin)
}
if err := jqb.UpdatePerformersScenes(sceneID, performerJoins, tx); err != nil {
_ = tx.Rollback()
return nil, err
}
// Save the tags
var tagJoins []models.ScenesTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
tagJoin := models.ScenesTags{
SceneID: sceneID,
TagID: tagID,
}
tagJoins = append(tagJoins, tagJoin)
}
if err := jqb.UpdateScenesTags(sceneID, tagJoins, tx); err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return scene, nil
}
func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input models.SceneMarkerCreateInput) (*models.SceneMarker, error) {
primaryTagID, _ := strconv.Atoi(input.PrimaryTagID)
sceneID, _ := strconv.Atoi(input.SceneID)
currentTime := time.Now()
newSceneMarker := models.SceneMarker{
Title: input.Title,
Seconds: input.Seconds,
PrimaryTagID: sql.NullInt64{ Int64: int64(primaryTagID), Valid: primaryTagID != 0 },
SceneID: sql.NullInt64{ Int64: int64(sceneID), Valid: sceneID != 0 },
CreatedAt: models.SQLiteTimestamp{ Timestamp: currentTime },
UpdatedAt: models.SQLiteTimestamp{ Timestamp: currentTime },
}
// Start the transaction and save the scene marker
tx := database.DB.MustBeginTx(ctx, nil)
smqb := models.NewSceneMarkerQueryBuilder()
jqb := models.NewJoinsQueryBuilder()
sceneMarker, err := smqb.Create(newSceneMarker, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
// Save the marker tags
var markerTagJoins []models.SceneMarkersTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
markerTag := models.SceneMarkersTags{
SceneMarkerID: sceneMarker.ID,
TagID: tagID,
}
markerTagJoins = append(markerTagJoins, markerTag)
}
if err := jqb.CreateSceneMarkersTags(markerTagJoins, tx); err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return sceneMarker, nil
}
func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input models.SceneMarkerUpdateInput) (*models.SceneMarker, error) {
// Populate scene marker from the input
sceneMarkerID, _ := strconv.Atoi(input.ID)
sceneID, _ := strconv.Atoi(input.SceneID)
primaryTagID, _ := strconv.Atoi(input.PrimaryTagID)
updatedSceneMarker := models.SceneMarker{
ID: sceneMarkerID,
Title: input.Title,
Seconds: input.Seconds,
SceneID: sql.NullInt64{ Int64: int64(sceneID), Valid: sceneID != 0 },
PrimaryTagID: sql.NullInt64{ Int64: int64(primaryTagID), Valid: primaryTagID != 0 },
UpdatedAt: models.SQLiteTimestamp{ Timestamp: time.Now() },
}
// Start the transaction and save the scene marker
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewSceneMarkerQueryBuilder()
jqb := models.NewJoinsQueryBuilder()
sceneMarker, err := qb.Update(updatedSceneMarker, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
// Save the marker tags
var markerTagJoins []models.SceneMarkersTags
for _, tid := range input.TagIds {
tagID, _ := strconv.Atoi(tid)
markerTag := models.SceneMarkersTags{
SceneMarkerID: sceneMarkerID,
TagID: tagID,
}
markerTagJoins = append(markerTagJoins, markerTag)
}
if err := jqb.UpdateSceneMarkersTags(sceneMarkerID, markerTagJoins, tx); err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return sceneMarker, nil
}
func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (bool, error) {
qb := models.NewSceneMarkerQueryBuilder()
tx := database.DB.MustBeginTx(ctx, nil)
if err := qb.Destroy(id, tx); err != nil {
_ = tx.Rollback()
return false, err
}
if err := tx.Commit(); err != nil {
return false, err
}
return true, nil
}

View File

@@ -0,0 +1,87 @@
package api
import (
"context"
"database/sql"
"github.com/stashapp/stash/database"
"github.com/stashapp/stash/models"
"github.com/stashapp/stash/utils"
"strconv"
"time"
)
func (r *mutationResolver) StudioCreate(ctx context.Context, input models.StudioCreateInput) (*models.Studio, error) {
// Process the base 64 encoded image string
checksum, imageData, err := utils.ProcessBase64Image(input.Image)
if err != nil {
return nil, err
}
// Populate a new studio from the input
currentTime := time.Now()
newStudio := models.Studio{
Image: imageData,
Checksum: checksum,
Name: sql.NullString{ String: input.Name, Valid: true },
CreatedAt: models.SQLiteTimestamp{ Timestamp: currentTime },
UpdatedAt: models.SQLiteTimestamp{ Timestamp: currentTime },
}
if input.URL != nil {
newStudio.Url = sql.NullString{ String: *input.URL, Valid: true }
}
// Start the transaction and save the studio
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewStudioQueryBuilder()
studio, err := qb.Create(newStudio, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return studio, nil
}
func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.StudioUpdateInput) (*models.Studio, error) {
// Populate studio from the input
studioID, _ := strconv.Atoi(input.ID)
updatedStudio := models.Studio{
ID: studioID,
UpdatedAt: models.SQLiteTimestamp{ Timestamp: time.Now() },
}
if input.Image != nil {
checksum, imageData, err := utils.ProcessBase64Image(*input.Image)
if err != nil {
return nil, err
}
updatedStudio.Image = imageData
updatedStudio.Checksum = checksum
}
if input.Name != nil {
updatedStudio.Name = sql.NullString{ String: *input.Name, Valid: true }
}
if input.URL != nil {
updatedStudio.Url = sql.NullString{ String: *input.URL, Valid: true }
}
// Start the transaction and save the studio
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewStudioQueryBuilder()
studio, err := qb.Update(updatedStudio, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return studio, nil
}

View File

@@ -0,0 +1,74 @@
package api
import (
"context"
"github.com/stashapp/stash/database"
"github.com/stashapp/stash/models"
"strconv"
"time"
)
func (r *mutationResolver) TagCreate(ctx context.Context, input models.TagCreateInput) (*models.Tag, error) {
// Populate a new tag from the input
currentTime := time.Now()
newTag := models.Tag{
Name: input.Name,
CreatedAt: models.SQLiteTimestamp{ Timestamp: currentTime },
UpdatedAt: models.SQLiteTimestamp{ Timestamp: currentTime },
}
// Start the transaction and save the studio
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewTagQueryBuilder()
tag, err := qb.Create(newTag, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return tag, nil
}
func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdateInput) (*models.Tag, error) {
// Populate tag from the input
tagID, _ := strconv.Atoi(input.ID)
updatedTag := models.Tag{
ID: tagID,
Name: input.Name,
UpdatedAt: models.SQLiteTimestamp{ Timestamp: time.Now() },
}
// Start the transaction and save the tag
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewTagQueryBuilder()
tag, err := qb.Update(updatedTag, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
// Commit
if err := tx.Commit(); err != nil {
return nil, err
}
return tag, nil
}
func (r *mutationResolver) TagDestroy(ctx context.Context, input models.TagDestroyInput) (bool, error) {
qb := models.NewTagQueryBuilder()
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
}

View File

@@ -0,0 +1,22 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *queryResolver) FindGallery(ctx context.Context, id string) (*models.Gallery, error) {
qb := models.NewGalleryQueryBuilder()
idInt, _ := strconv.Atoi(id)
return qb.Find(idInt)
}
func (r *queryResolver) FindGalleries(ctx context.Context, filter *models.FindFilterType) (models.FindGalleriesResultType, error) {
qb := models.NewGalleryQueryBuilder()
galleries, total := qb.Query(filter)
return models.FindGalleriesResultType{
Count: total,
Galleries: galleries,
}, nil
}

View File

@@ -0,0 +1,27 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *queryResolver) FindPerformer(ctx context.Context, id string) (*models.Performer, error) {
qb := models.NewPerformerQueryBuilder()
idInt, _ := strconv.Atoi(id)
return qb.Find(idInt)
}
func (r *queryResolver) FindPerformers(ctx context.Context, performer_filter *models.PerformerFilterType, filter *models.FindFilterType) (models.FindPerformersResultType, error) {
qb := models.NewPerformerQueryBuilder()
performers, total := qb.Query(performer_filter, filter)
return models.FindPerformersResultType{
Count: total,
Performers: performers,
}, nil
}
func (r *queryResolver) AllPerformers(ctx context.Context) ([]models.Performer, error) {
qb := models.NewPerformerQueryBuilder()
return qb.All()
}

View File

@@ -0,0 +1,29 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *queryResolver) FindScene(ctx context.Context, id *string, checksum *string) (*models.Scene, error) {
qb := models.NewSceneQueryBuilder()
idInt, _ := strconv.Atoi(*id)
var scene *models.Scene
var err error
if id != nil {
scene, err = qb.Find(idInt)
} else if checksum != nil {
scene, err = qb.FindByChecksum(*checksum)
}
return scene, err
}
func (r *queryResolver) FindScenes(ctx context.Context, scene_filter *models.SceneFilterType, scene_ids []int, filter *models.FindFilterType) (models.FindScenesResultType, error) {
qb := models.NewSceneQueryBuilder()
scenes, total := qb.Query(scene_filter, filter)
return models.FindScenesResultType{
Count: total,
Scenes: scenes,
}, nil
}

View File

@@ -0,0 +1,15 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
)
func (r *queryResolver) FindSceneMarkers(ctx context.Context, scene_marker_filter *models.SceneMarkerFilterType, filter *models.FindFilterType) (models.FindSceneMarkersResultType, error) {
qb := models.NewSceneMarkerQueryBuilder()
sceneMarkers, total := qb.Query(scene_marker_filter, filter)
return models.FindSceneMarkersResultType{
Count: total,
SceneMarkers: sceneMarkers,
}, nil
}

View File

@@ -0,0 +1,27 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *queryResolver) FindStudio(ctx context.Context, id string) (*models.Studio, error) {
qb := models.NewStudioQueryBuilder()
idInt, _ := strconv.Atoi(id)
return qb.Find(idInt, nil)
}
func (r *queryResolver) FindStudios(ctx context.Context, filter *models.FindFilterType) (models.FindStudiosResultType, error) {
qb := models.NewStudioQueryBuilder()
studios, total := qb.Query(filter)
return models.FindStudiosResultType{
Count: total,
Studios: studios,
}, nil
}
func (r *queryResolver) AllStudios(ctx context.Context) ([]models.Studio, error) {
qb := models.NewStudioQueryBuilder()
return qb.All()
}

View File

@@ -0,0 +1,18 @@
package api
import (
"context"
"github.com/stashapp/stash/models"
"strconv"
)
func (r *queryResolver) FindTag(ctx context.Context, id string) (*models.Tag, error) {
qb := models.NewTagQueryBuilder()
idInt, _ := strconv.Atoi(id)
return qb.Find(idInt, nil)
}
func (r *queryResolver) AllTags(ctx context.Context) ([]models.Tag, error) {
qb := models.NewTagQueryBuilder()
return qb.All()
}

View File

@@ -0,0 +1,29 @@
package api
import (
"context"
"github.com/stashapp/stash/manager"
)
func (r *queryResolver) MetadataScan(ctx context.Context) (string, error) {
manager.GetInstance().Scan()
return "todo", nil
}
func (r *queryResolver) MetadataImport(ctx context.Context) (string, error) {
manager.GetInstance().Import()
return "todo", nil
}
func (r *queryResolver) MetadataExport(ctx context.Context) (string, error) {
manager.GetInstance().Export()
return "todo", nil
}
func (r *queryResolver) MetadataGenerate(ctx context.Context) (string, error) {
panic("not implemented")
}
func (r *queryResolver) MetadataClean(ctx context.Context) (string, error) {
panic("not implemented")
}

View File

@@ -0,0 +1,7 @@
package api
import "context"
func (r *subscriptionResolver) MetadataUpdate(ctx context.Context) (<-chan string, error) {
panic("not implemented")
}

54
api/routes_gallery.go Normal file
View File

@@ -0,0 +1,54 @@
package api
import (
"context"
"github.com/go-chi/chi"
"github.com/stashapp/stash/models"
"net/http"
"strconv"
)
type galleryRoutes struct{}
func (rs galleryRoutes) Routes() chi.Router {
r := chi.NewRouter()
r.Route("/{galleryId}", func(r chi.Router) {
r.Use(GalleryCtx)
r.Get("/{fileIndex}", rs.File)
})
return r
}
func (rs galleryRoutes) File(w http.ResponseWriter, r *http.Request) {
gallery := r.Context().Value("gallery").(*models.Gallery)
fileIndex, _ := strconv.Atoi(chi.URLParam(r, "fileIndex"))
thumb := r.URL.Query().Get("thumb")
w.Header().Add("Cache-Control", "max-age=604800000") // 1 Week
if thumb == "true" {
_, _ = w.Write(gallery.GetThumbnail(fileIndex))
} else {
_, _ = w.Write(gallery.GetImage(fileIndex))
}
}
func GalleryCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
galleryID, err := strconv.Atoi(chi.URLParam(r, "galleryId"))
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
qb := models.NewGalleryQueryBuilder()
gallery, err := qb.Find(galleryID)
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
ctx := context.WithValue(r.Context(), "gallery", gallery)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

47
api/routes_performer.go Normal file
View File

@@ -0,0 +1,47 @@
package api
import (
"context"
"github.com/go-chi/chi"
"github.com/stashapp/stash/models"
"net/http"
"strconv"
)
type performerRoutes struct{}
func (rs performerRoutes) Routes() chi.Router {
r := chi.NewRouter()
r.Route("/{performerId}", func(r chi.Router) {
r.Use(PerformerCtx)
r.Get("/image", rs.Image)
})
return r
}
func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
performer := r.Context().Value("performer").(*models.Performer)
_, _ = w.Write(performer.Image)
}
func PerformerCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
performerID, err := strconv.Atoi(chi.URLParam(r, "performerId"))
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
qb := models.NewPerformerQueryBuilder()
performer, err := qb.Find(performerID)
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
ctx := context.WithValue(r.Context(), "performer", performer)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

151
api/routes_scene.go Normal file
View File

@@ -0,0 +1,151 @@
package api
import (
"context"
"github.com/go-chi/chi"
"github.com/stashapp/stash/logger"
"github.com/stashapp/stash/manager"
"github.com/stashapp/stash/models"
"github.com/stashapp/stash/utils"
"net/http"
"strconv"
"strings"
)
type sceneRoutes struct{}
func (rs sceneRoutes) Routes() chi.Router {
r := chi.NewRouter()
r.Route("/{sceneId}", func(r chi.Router) {
r.Use(SceneCtx)
r.Get("/stream", rs.Stream)
r.Get("/stream.mp4", rs.Stream)
r.Get("/screenshot", rs.Screenshot)
r.Get("/preview", rs.Preview)
r.Get("/webp", rs.Webp)
r.Get("/vtt/chapter", rs.ChapterVtt)
r.Get("/scene_marker/{sceneMarkerId}/stream", rs.SceneMarkerStream)
r.Get("/scene_marker/{sceneMarkerId}/preview", rs.SceneMarkerPreview)
})
r.With(SceneCtx).Get("/{sceneId}_thumbs.vtt", rs.VttThumbs)
r.With(SceneCtx).Get("/{sceneId}_sprite.jpg", rs.VttSprite)
return r
}
// region Handlers
func (rs sceneRoutes) Stream(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
filepath := manager.GetInstance().Paths.Scene.GetStreamPath(scene.Path, scene.Checksum)
http.ServeFile(w, r, filepath)
}
func (rs sceneRoutes) Screenshot(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
filepath := manager.GetInstance().Paths.Scene.GetScreenshotPath(scene.Checksum)
http.ServeFile(w, r, filepath)
}
func (rs sceneRoutes) Preview(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
filepath := manager.GetInstance().Paths.Scene.GetStreamPreviewPath(scene.Checksum)
http.ServeFile(w, r, filepath)
}
func (rs sceneRoutes) Webp(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
filepath := manager.GetInstance().Paths.Scene.GetStreamPreviewImagePath(scene.Checksum)
http.ServeFile(w, r, filepath)
}
func (rs sceneRoutes) ChapterVtt(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
qb := models.NewSceneMarkerQueryBuilder()
sceneMarkers, err := qb.FindBySceneID(scene.ID, nil)
if err != nil {
panic("invalid scene markers for chapter vtt")
}
vttLines := []string{"WEBVTT", ""}
for _, marker := range sceneMarkers {
time := utils.GetVTTTime(marker.Seconds)
vttLines = append(vttLines, time + " --> " + time)
vttLines = append(vttLines, marker.Title)
vttLines = append(vttLines, "")
}
vtt := strings.Join(vttLines, "\n")
w.Header().Set("Content-Type", "text/vtt")
_, _ = w.Write([]byte(vtt))
}
func (rs sceneRoutes) VttThumbs(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
w.Header().Set("Content-Type", "text/vtt")
filepath := manager.GetInstance().Paths.Scene.GetSpriteVttFilePath(scene.Checksum)
http.ServeFile(w, r, filepath)
}
func (rs sceneRoutes) VttSprite(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
w.Header().Set("Content-Type", "image/jpeg")
filepath := manager.GetInstance().Paths.Scene.GetSpriteImageFilePath(scene.Checksum)
http.ServeFile(w, r, filepath)
}
func (rs sceneRoutes) SceneMarkerStream(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId"))
qb := models.NewSceneMarkerQueryBuilder()
sceneMarker, err := qb.Find(sceneMarkerID)
if err != nil {
logger.Warn("Error when getting scene marker for stream")
http.Error(w, http.StatusText(404), 404)
return
}
filepath := manager.GetInstance().Paths.SceneMarkers.GetStreamPath(scene.Checksum, int(sceneMarker.Seconds))
http.ServeFile(w, r, filepath)
}
func (rs sceneRoutes) SceneMarkerPreview(w http.ResponseWriter, r *http.Request) {
scene := r.Context().Value("scene").(*models.Scene)
sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId"))
qb := models.NewSceneMarkerQueryBuilder()
sceneMarker, err := qb.Find(sceneMarkerID)
if err != nil {
logger.Warn("Error when getting scene marker for stream")
http.Error(w, http.StatusText(404), 404)
return
}
filepath := manager.GetInstance().Paths.SceneMarkers.GetStreamPreviewImagePath(scene.Checksum, int(sceneMarker.Seconds))
http.ServeFile(w, r, filepath)
}
// endregion
func SceneCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sceneIdentifierQueryParam := chi.URLParam(r, "sceneId")
sceneID, _ := strconv.Atoi(sceneIdentifierQueryParam)
var scene *models.Scene
var err error
qb := models.NewSceneQueryBuilder()
if sceneID == 0 {
scene, err = qb.FindByChecksum(sceneIdentifierQueryParam)
} else {
scene, err = qb.Find(sceneID)
}
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
ctx := context.WithValue(r.Context(), "scene", scene)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

47
api/routes_studio.go Normal file
View File

@@ -0,0 +1,47 @@
package api
import (
"context"
"github.com/go-chi/chi"
"github.com/stashapp/stash/models"
"net/http"
"strconv"
)
type studioRoutes struct{}
func (rs studioRoutes) Routes() chi.Router {
r := chi.NewRouter()
r.Route("/{studioId}", func(r chi.Router) {
r.Use(StudioCtx)
r.Get("/image", rs.Image)
})
return r
}
func (rs studioRoutes) Image(w http.ResponseWriter, r *http.Request) {
studio := r.Context().Value("studio").(*models.Studio)
_, _ = w.Write(studio.Image)
}
func StudioCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
studioID, err := strconv.Atoi(chi.URLParam(r, "studioId"))
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
qb := models.NewStudioQueryBuilder()
studio, err := qb.Find(studioID, nil)
if err != nil {
http.Error(w, http.StatusText(404), 404)
return
}
ctx := context.WithValue(r.Context(), "studio", studio)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

139
api/server.go Normal file
View File

@@ -0,0 +1,139 @@
package api
import (
"context"
"crypto/tls"
"errors"
"fmt"
"github.com/99designs/gqlgen/handler"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/gobuffalo/packr/v2"
"github.com/rs/cors"
"github.com/stashapp/stash/logger"
"github.com/stashapp/stash/models"
"net/http"
"path"
"runtime/debug"
"strings"
)
const httpPort = "9998"
const httpsPort = "9999"
var certsBox *packr.Box
var uiBox *packr.Box
func Start() {
//port := os.Getenv("PORT")
//if port == "" {
// port = defaultPort
//}
certsBox = packr.New("Cert Box", "../../certs")
uiBox = packr.New("UI Box", "../../ui/v1/dist/stash-frontend")
r := chi.NewRouter()
r.Use(middleware.Recoverer)
r.Use(middleware.Logger)
r.Use(middleware.DefaultCompress)
r.Use(middleware.StripSlashes)
r.Use(cors.AllowAll().Handler)
r.Use(BaseURLMiddleware)
recoverFunc := handler.RecoverFunc(func(ctx context.Context, err interface{}) error {
logger.Error(err)
debug.PrintStack()
message := fmt.Sprintf("Internal system error. Error <%v>", err)
return errors.New(message)
})
requestMiddleware := handler.RequestMiddleware(func(ctx context.Context, next func(ctx context.Context) []byte) []byte {
//api.GetRequestContext(ctx).Variables[]
return next(ctx)
})
gqlHandler := handler.GraphQL(models.NewExecutableSchema(models.Config{Resolvers: &Resolver{}}), recoverFunc, requestMiddleware)
// https://stash.server:9999/certs/server.crt
r.Handle("/certs/*", http.FileServer(certsBox))
r.Handle("/graphql", gqlHandler)
r.Handle("/playground", handler.Playground("GraphQL playground", "/graphql"))
r.Mount("/gallery", galleryRoutes{}.Routes())
r.Mount("/performer", performerRoutes{}.Routes())
r.Mount("/scene", sceneRoutes{}.Routes())
r.Mount("/studio", studioRoutes{}.Routes())
// Serve the angular app
r.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
ext := path.Ext(r.URL.Path)
if ext == ".html" || ext == "" {
data := uiBox.Bytes("index.html")
_, _ = w.Write(data)
} else {
http.FileServer(uiBox).ServeHTTP(w, r)
}
})
httpsServer := &http.Server{
Addr: ":"+httpsPort,
Handler: r,
TLSConfig: makeTLSConfig(),
}
server := &http.Server{
Addr: ":"+httpPort,
Handler: r,
}
go func() {
logger.Infof("stash is running on HTTP at http://localhost:9998/")
logger.Fatal(server.ListenAndServe())
}()
logger.Infof("stash is running on HTTPS at https://localhost:9999/")
logger.Fatal(httpsServer.ListenAndServeTLS("", ""))
}
func makeTLSConfig() *tls.Config {
cert, err := certsBox.Find("server.crt")
key, err := certsBox.Find("server.key")
certs := make([]tls.Certificate, 1)
certs[0], err = tls.X509KeyPair(cert, key)
if err != nil {
panic(err)
}
tlsConfig := &tls.Config{
Certificates: certs,
}
return tlsConfig
}
type contextKey struct {
name string
}
var (
BaseURLCtxKey = &contextKey{"BaseURL"}
)
func BaseURLMiddleware(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var scheme string
if strings.Compare("https", r.URL.Scheme) == 0 || r.Proto == "HTTP/2.0" {
scheme = "https"
} else {
scheme = "http"
}
baseURL := scheme + "://" + r.Host
r = r.WithContext(context.WithValue(ctx, BaseURLCtxKey, baseURL))
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}

View File

@@ -0,0 +1,19 @@
package urlbuilders
import "strconv"
type galleryURLBuilder struct {
BaseURL string
GalleryID string
}
func NewGalleryURLBuilder(baseURL string, galleryID int) galleryURLBuilder {
return galleryURLBuilder{
BaseURL: baseURL,
GalleryID: strconv.Itoa(galleryID),
}
}
func (b galleryURLBuilder) GetGalleryImageUrl(fileIndex int) string {
return b.BaseURL + "/gallery/" + b.GalleryID + "/" + strconv.Itoa(fileIndex)
}

View File

@@ -0,0 +1,19 @@
package urlbuilders
import "strconv"
type performerURLBuilder struct {
BaseURL string
PerformerID string
}
func NewPerformerURLBuilder(baseURL string, performerID int) performerURLBuilder {
return performerURLBuilder{
BaseURL: baseURL,
PerformerID: strconv.Itoa(performerID),
}
}
func (b performerURLBuilder) GetPerformerImageUrl() string {
return b.BaseURL + "/performer/" + b.PerformerID + "/image"
}

47
api/urlbuilders/scene.go Normal file
View File

@@ -0,0 +1,47 @@
package urlbuilders
import "strconv"
type sceneURLBuilder struct {
BaseURL string
SceneID string
}
func NewSceneURLBuilder(baseURL string, sceneID int) sceneURLBuilder {
return sceneURLBuilder{
BaseURL: baseURL,
SceneID: strconv.Itoa(sceneID),
}
}
func (b sceneURLBuilder) GetStreamUrl() string {
return b.BaseURL + "/scene/" + b.SceneID + "/stream.mp4"
}
func (b sceneURLBuilder) GetStreamPreviewUrl() string {
return b.BaseURL + "/scene/" + b.SceneID + "/preview"
}
func (b sceneURLBuilder) GetStreamPreviewImageUrl() string {
return b.BaseURL + "/scene/" + b.SceneID + "/webp"
}
func (b sceneURLBuilder) GetSpriteVttUrl() string {
return b.BaseURL + "/scene/" + b.SceneID + "_thumbs.vtt"
}
func (b sceneURLBuilder) GetScreenshotUrl() string {
return b.BaseURL + "/scene/" + b.SceneID + "/screenshot"
}
func (b sceneURLBuilder) GetChaptersVttUrl() string {
return b.BaseURL + "/scene/" + b.SceneID + "/vtt/chapter"
}
func (b sceneURLBuilder) GetSceneMarkerStreamUrl(sceneMarkerId int) string {
return b.BaseURL + "/scene/" + b.SceneID + "/scene_marker/" + strconv.Itoa(sceneMarkerId) + "/stream"
}
func (b sceneURLBuilder) GetSceneMarkerStreamPreviewUrl(sceneMarkerId int) string {
return b.BaseURL + "/scene/" + b.SceneID + "/scene_marker/" + strconv.Itoa(sceneMarkerId) + "/preview"
}

19
api/urlbuilders/studio.go Normal file
View File

@@ -0,0 +1,19 @@
package urlbuilders
import "strconv"
type studioURLBuilder struct {
BaseURL string
StudioID string
}
func NewStudioURLBuilder(baseURL string, studioID int) studioURLBuilder {
return studioURLBuilder{
BaseURL: baseURL,
StudioID: strconv.Itoa(studioID),
}
}
func (b studioURLBuilder) GetStudioImageUrl() string {
return b.BaseURL + "/studio/" + b.StudioID + "/image"
}