Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions FINISH_BOUNTY.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# FAST FINISH - Copy & Run in NEW PowerShell

cd C:\Users\Admin\Desktop\myrepo\stashapp

# 1. Setup (30 sec)
if (!(Test-Path "ui\v2.5\build")) { mkdir ui\v2.5\build }
echo $null > ui\v2.5\build\index.html

# 2. Install gqlgen (10 sec)
go install github.com/99designs/gqlgen@latest

# 3. Generate GraphQL (20 sec)
go generate ./cmd/stash

# 4. Build backend (1 min)
go build ./cmd/stash

# 5. Install npm packages (3 min)
cd ui\v2.5
npm install

# 6. Build frontend (5 min)
npm run build

# 7. Done!
cd ..\..
Write-Host "✅ BUILD COMPLETE! Run: .\stash.exe" -ForegroundColor Green
Write-Host "Then test & submit PR for $450!" -ForegroundColor Yellow
41 changes: 41 additions & 0 deletions PUSH_TO_GITHUB.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Stashapp Branch Creation and Push Script
# Run this in PowerShell

cd C:\Users\Admin\Desktop\myrepo\stashapp

Write-Host "=== STEP 1: Current Git Status ===" -ForegroundColor Cyan
git status

Write-Host "`n=== STEP 2: Current Branch ===" -ForegroundColor Cyan
git branch

Write-Host "`n=== STEP 3: Remote Configuration ===" -ForegroundColor Cyan
git remote -v

Write-Host "`n=== STEP 4: Creating feature/scene-segments branch ===" -ForegroundColor Cyan
git checkout -b feature/scene-segments 2>&1

Write-Host "`n=== STEP 5: Adding scene segment files ===" -ForegroundColor Cyan
git add pkg/models/model_scene_segment.go
git add pkg/models/repository_scene_segment.go
git add pkg/sqlite/scene_segment.go
git add pkg/sqlite/migrations/76_scene_segments.up.sql
git add pkg/sqlite/migrations/76_scene_segments.down.sql
git add graphql/schema/types/scene-segment.graphql
git add internal/api/resolver_model_scene_segment.go
git add internal/api/resolver_mutation_scene_segment.go
git add internal/api/resolver_query_scene_segment.go
git add ui/v2.5/src/components/Scenes/SceneDetails/SceneSegmentForm.tsx
git add ui/v2.5/src/components/Scenes/SceneDetails/SceneSegmentsPanel.tsx

Write-Host "`n=== STEP 6: Checking what will be committed ===" -ForegroundColor Cyan
git status

Write-Host "`n=== STEP 7: Committing changes ===" -ForegroundColor Cyan
git commit -m "feat: implement scene segments feature for issue #3530"

Write-Host "`n=== STEP 8: Pushing to GitHub ===" -ForegroundColor Cyan
git push -u origin feature/scene-segments

Write-Host "`n=== DONE! ===" -ForegroundColor Green
Write-Host "Check https://github.com/SBALAVIGNESH123/stash/tree/feature/scene-segments" -ForegroundColor Yellow
9 changes: 9 additions & 0 deletions graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ type Query {
ids: [ID!]
): FindSceneMarkersResultType!

"Find scene segments by scene ID"
findSceneSegments(scene_id: ID!): [SceneSegment!]!
"Find a single scene segment by ID"
findSceneSegment(id: ID!): SceneSegment

findImage(id: ID, checksum: String): Image

"A function which queries Scene objects"
Expand Down Expand Up @@ -338,6 +343,10 @@ type Mutation {
sceneMarkerDestroy(id: ID!): Boolean!
sceneMarkersDestroy(ids: [ID!]!): Boolean!

sceneSegmentCreate(input: SceneSegmentCreateInput!): SceneSegment
sceneSegmentUpdate(input: SceneSegmentUpdateInput!): SceneSegment
sceneSegmentDestroy(id: ID!): Boolean!

sceneAssignFile(input: AssignSceneFileInput!): Boolean!

imageUpdate(input: ImageUpdateInput!): Image
Expand Down
24 changes: 24 additions & 0 deletions graphql/schema/types/scene-segment.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type SceneSegment {
id: ID!
title: String!
scene_id: ID!
scene: Scene!
start_seconds: Float!
end_seconds: Float!
created_at: Time!
updated_at: Time!
}

input SceneSegmentCreateInput {
title: String!
scene_id: ID!
start_seconds: Float!
end_seconds: Float!
}

input SceneSegmentUpdateInput {
id: ID!
title: String
start_seconds: Float
end_seconds: Float
}
1 change: 1 addition & 0 deletions graphql/schema/types/scene.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type Scene {
files: [VideoFile!]!
paths: ScenePathsType! # Resolver
scene_markers: [SceneMarker!]!
scene_segments: [SceneSegment!]!
galleries: [Gallery!]!
studio: Studio
groups: [SceneGroup!]!
Expand Down
4 changes: 4 additions & 0 deletions internal/api/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ func (r *Resolver) Image() ImageResolver {
func (r *Resolver) SceneMarker() SceneMarkerResolver {
return &sceneMarkerResolver{r}
}
func (r *Resolver) SceneSegment() SceneSegmentResolver {
return &sceneSegmentResolver{r}
}
func (r *Resolver) Studio() StudioResolver {
return &studioResolver{r}
}
Expand Down Expand Up @@ -120,6 +123,7 @@ type galleryChapterResolver struct{ *Resolver }
type performerResolver struct{ *Resolver }
type sceneResolver struct{ *Resolver }
type sceneMarkerResolver struct{ *Resolver }
type sceneSegmentResolver struct{ *Resolver }
type imageResolver struct{ *Resolver }
type studioResolver struct{ *Resolver }

Expand Down
11 changes: 11 additions & 0 deletions internal/api/resolver_model_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) (re
return ret, nil
}

func (r *sceneResolver) SceneSegments(ctx context.Context, obj *models.Scene) (ret []*models.SceneSegment, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.SceneSegment.FindBySceneID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}

return ret, nil
}

func (r *sceneResolver) Captions(ctx context.Context, obj *models.Scene) (ret []*models.VideoCaption, err error) {
primaryFile, err := r.getPrimaryFile(ctx, obj)
if err != nil {
Expand Down
18 changes: 18 additions & 0 deletions internal/api/resolver_model_scene_segment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package api

import (
"context"

"github.com/stashapp/stash/pkg/models"
)

func (r *sceneSegmentResolver) Scene(ctx context.Context, obj *models.SceneSegment) (ret *models.Scene, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Scene.Find(ctx, obj.SceneID)
return err
}); err != nil {
return nil, err
}

return ret, nil
}
85 changes: 85 additions & 0 deletions internal/api/resolver_mutation_scene_segment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package api

import (
"context"
"fmt"
"strconv"
"strings"

"github.com/stashapp/stash/pkg/models"
)

func (r *mutationResolver) SceneSegmentCreate(ctx context.Context, input SceneSegmentCreateInput) (*models.SceneSegment, error) {
sceneID, err := strconv.Atoi(input.SceneID)
if err != nil {
return nil, fmt.Errorf("converting scene id: %w", err)
}

// Populate a new scene segment from the input
newSegment := models.NewSceneSegment()

newSegment.Title = strings.TrimSpace(input.Title)
newSegment.SceneID = sceneID
newSegment.StartSeconds = input.StartSeconds
newSegment.EndSeconds = input.EndSeconds

// Validate
if err := newSegment.Validate(); err != nil {
return nil, err
}

if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.SceneSegment

return qb.Create(ctx, &newSegment)
}); err != nil {
return nil, err
}

return &newSegment, nil
}

func (r *mutationResolver) SceneSegmentUpdate(ctx context.Context, input SceneSegmentUpdateInput) (*models.SceneSegment, error) {
segmentID, err := strconv.Atoi(input.ID)
if err != nil {
return nil, fmt.Errorf("converting id: %w", err)
}

translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}

// Populate scene segment from the input
updatedSegment := models.SceneSegmentPartial{}

updatedSegment.Title = translator.optionalString(input.Title, "title")
updatedSegment.StartSeconds = translator.optionalFloat64(input.StartSeconds, "start_seconds")
updatedSegment.EndSeconds = translator.optionalFloat64(input.EndSeconds, "end_seconds")

var ret *models.SceneSegment
if err := r.withTxn(ctx, func(ctx context.Context) error {
qb := r.repository.SceneSegment

ret, err = qb.UpdatePartial(ctx, segmentID, updatedSegment)
return err
}); err != nil {
return nil, err
}

return ret, nil
}

func (r *mutationResolver) SceneSegmentDestroy(ctx context.Context, id string) (bool, error) {
segmentID, err := strconv.Atoi(id)
if err != nil {
return false, fmt.Errorf("converting id: %w", err)
}

if err := r.withTxn(ctx, func(ctx context.Context) error {
return r.repository.SceneSegment.Destroy(ctx, segmentID)
}); err != nil {
return false, err
}

return true, nil
}
43 changes: 43 additions & 0 deletions internal/api/resolver_query_scene_segment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package api

import (
"context"
"fmt"
"strconv"

"github.com/stashapp/stash/pkg/models"
)

func (r *queryResolver) FindSceneSegment(ctx context.Context, id string) (*models.SceneSegment, error) {
segmentID, err := strconv.Atoi(id)
if err != nil {
return nil, fmt.Errorf("converting id: %w", err)
}

var segment *models.SceneSegment
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
segment, err = r.repository.SceneSegment.Find(ctx, segmentID)
return err
}); err != nil {
return nil, err
}

return segment, nil
}

func (r *queryResolver) FindSceneSegments(ctx context.Context, sceneID string) ([]*models.SceneSegment, error) {
sid, err := strconv.Atoi(sceneID)
if err != nil {
return nil, fmt.Errorf("converting scene id: %w", err)
}

var segments []*models.SceneSegment
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
segments, err = r.repository.SceneSegment.FindBySceneID(ctx, sid)
return err
}); err != nil {
return nil, err
}

return segments, nil
}
51 changes: 51 additions & 0 deletions pkg/models/model_scene_segment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package models

import (
"fmt"
"time"
)

type SceneSegment struct {
ID int `json:"id"`
SceneID int `json:"scene_id"`
Title string `json:"title"`
StartSeconds float64 `json:"start_seconds"`
EndSeconds float64 `json:"end_seconds"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

type SceneSegmentPartial struct {
ID int
SceneID OptionalInt
Title OptionalString
StartSeconds OptionalFloat64
EndSeconds OptionalFloat64
CreatedAt OptionalTime
UpdatedAt OptionalTime
}

func NewSceneSegment() SceneSegment {
currentTime := time.Now()
return SceneSegment{
CreatedAt: currentTime,
UpdatedAt: currentTime,
}
}

func (s *SceneSegment) LoadRelationships(r SceneSegmentReader) error {
return nil
}

func (s *SceneSegment) Validate() error {
if s.Title == "" {
return fmt.Errorf("title is required")
}
if s.StartSeconds < 0 {
return fmt.Errorf("start_seconds must be >= 0")
}
if s.EndSeconds <= s.StartSeconds {
return fmt.Errorf("end_seconds must be > start_seconds")
}
return nil
}
1 change: 1 addition & 0 deletions pkg/models/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Repository struct {
Performer PerformerReaderWriter
Scene SceneReaderWriter
SceneMarker SceneMarkerReaderWriter
SceneSegment SceneSegmentReaderWriter
Studio StudioReaderWriter
Tag TagReaderWriter
SavedFilter SavedFilterReaderWriter
Expand Down
Loading