mirror of
https://github.com/stashapp/stash.git
synced 2026-01-17 13:44:02 +01:00
Merge pull request #3217 from stashapp/develop
Merge to master for 0.18
This commit is contained in:
commit
be8f57d6ca
415 changed files with 18334 additions and 5901 deletions
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
|
|
@ -12,7 +12,7 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COMPILER_IMAGE: stashapp/compiler:6
|
||||
COMPILER_IMAGE: stashapp/compiler:7
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
run: docker pull $COMPILER_IMAGE
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-node_modules
|
||||
with:
|
||||
|
|
@ -35,7 +35,7 @@ jobs:
|
|||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock') }}
|
||||
|
||||
- name: Cache UI build
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
id: cache-ui
|
||||
env:
|
||||
cache-name: cache-ui
|
||||
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock', 'ui/v2.5/public/**', 'ui/v2.5/src/**', 'graphql/**/*.graphql') }}
|
||||
|
||||
- name: Cache go build
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
# increment the number suffix to bump the cache
|
||||
cache-name: cache-go-cache-1
|
||||
|
|
|
|||
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
|
|
@ -9,7 +9,7 @@ on:
|
|||
pull_request:
|
||||
|
||||
env:
|
||||
COMPILER_IMAGE: stashapp/compiler:6
|
||||
COMPILER_IMAGE: stashapp/compiler:7
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
|
|
|
|||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -23,6 +23,12 @@ ui/v2.5/src/core/generated-*.tsx
|
|||
# Jetbrains
|
||||
####
|
||||
|
||||
|
||||
####
|
||||
# Visual Studio
|
||||
####
|
||||
/.vs
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
|
|
@ -57,4 +63,4 @@ node_modules
|
|||
|
||||
/stash
|
||||
dist
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
34
Makefile
34
Makefile
|
|
@ -14,11 +14,6 @@ else
|
|||
SET := export
|
||||
endif
|
||||
|
||||
IS_WIN_OS =
|
||||
ifeq ($(OS),Windows_NT)
|
||||
IS_WIN_OS = true
|
||||
endif
|
||||
|
||||
# set LDFLAGS environment variable to any extra ldflags required
|
||||
# set OUTPUT to generate a specific binary name
|
||||
|
||||
|
|
@ -29,9 +24,14 @@ endif
|
|||
|
||||
export CGO_ENABLED = 1
|
||||
|
||||
# including netgo causes name resolution to go through the Go resolver
|
||||
# and isn't necessary for static builds on Windows
|
||||
GO_BUILD_TAGS_WINDOWS := sqlite_omit_load_extension sqlite_stat4 osusergo
|
||||
GO_BUILD_TAGS_DEFAULT = $(GO_BUILD_TAGS_WINDOWS) netgo
|
||||
|
||||
.PHONY: release pre-build
|
||||
|
||||
release: generate ui build-release
|
||||
release: pre-ui generate ui build-release
|
||||
|
||||
pre-build:
|
||||
ifndef BUILD_DATE
|
||||
|
|
@ -47,14 +47,21 @@ ifndef STASH_VERSION
|
|||
endif
|
||||
|
||||
ifndef OFFICIAL_BUILD
|
||||
$(eval OFFICIAL_BUILD := false)
|
||||
$(eval OFFICIAL_BUILD := false)
|
||||
endif
|
||||
|
||||
ifndef GO_BUILD_TAGS
|
||||
$(eval GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT))
|
||||
endif
|
||||
|
||||
|
||||
# NOTE: the build target still includes netgo because we cannot detect
|
||||
# Windows easily from the Makefile.
|
||||
build: pre-build
|
||||
build:
|
||||
$(eval LDFLAGS := $(LDFLAGS) -X 'github.com/stashapp/stash/internal/api.version=$(STASH_VERSION)' -X 'github.com/stashapp/stash/internal/api.buildstamp=$(BUILD_DATE)' -X 'github.com/stashapp/stash/internal/api.githash=$(GITHASH)')
|
||||
$(eval LDFLAGS := $(LDFLAGS) -X 'github.com/stashapp/stash/internal/manager/config.officialBuild=$(OFFICIAL_BUILD)')
|
||||
go build $(OUTPUT) -mod=vendor -v -tags "sqlite_omit_load_extension sqlite_stat4 osusergo netgo" $(GO_BUILD_FLAGS) -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS) $(PLATFORM_SPECIFIC_LDFLAGS)" ./cmd/stash
|
||||
go build $(OUTPUT) -mod=vendor -v -tags "$(GO_BUILD_TAGS)" $(GO_BUILD_FLAGS) -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS) $(PLATFORM_SPECIFIC_LDFLAGS)" ./cmd/stash
|
||||
|
||||
# strips debug symbols from the release build
|
||||
build-release: EXTRA_LDFLAGS := -s -w
|
||||
|
|
@ -71,6 +78,7 @@ cross-compile-windows: export GOARCH := amd64
|
|||
cross-compile-windows: export CC := x86_64-w64-mingw32-gcc
|
||||
cross-compile-windows: export CXX := x86_64-w64-mingw32-g++
|
||||
cross-compile-windows: OUTPUT := -o dist/stash-win.exe
|
||||
cross-compile-windows: GO_BUILD_TAGS := $(GO_BUILD_TAGS_WINDOWS)
|
||||
cross-compile-windows: build-release-static
|
||||
|
||||
cross-compile-macos-intel: export GOOS := darwin
|
||||
|
|
@ -78,6 +86,7 @@ cross-compile-macos-intel: export GOARCH := amd64
|
|||
cross-compile-macos-intel: export CC := o64-clang
|
||||
cross-compile-macos-intel: export CXX := o64-clang++
|
||||
cross-compile-macos-intel: OUTPUT := -o dist/stash-macos-intel
|
||||
cross-compile-macos-intel: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
|
||||
# can't use static build for OSX
|
||||
cross-compile-macos-intel: build-release
|
||||
|
||||
|
|
@ -86,6 +95,7 @@ cross-compile-macos-applesilicon: export GOARCH := arm64
|
|||
cross-compile-macos-applesilicon: export CC := oa64e-clang
|
||||
cross-compile-macos-applesilicon: export CXX := oa64e-clang++
|
||||
cross-compile-macos-applesilicon: OUTPUT := -o dist/stash-macos-applesilicon
|
||||
cross-compile-macos-applesilicon: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
|
||||
# can't use static build for OSX
|
||||
cross-compile-macos-applesilicon: build-release
|
||||
|
||||
|
|
@ -106,17 +116,20 @@ cross-compile-macos:
|
|||
cross-compile-freebsd: export GOOS := freebsd
|
||||
cross-compile-freebsd: export GOARCH := amd64
|
||||
cross-compile-freebsd: OUTPUT := -o dist/stash-freebsd
|
||||
cross-compile-freebsd: GO_BUILD_TAGS += netgo
|
||||
cross-compile-freebsd: build-release-static
|
||||
|
||||
cross-compile-linux: export GOOS := linux
|
||||
cross-compile-linux: export GOARCH := amd64
|
||||
cross-compile-linux: OUTPUT := -o dist/stash-linux
|
||||
cross-compile-linux: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
|
||||
cross-compile-linux: build-release-static
|
||||
|
||||
cross-compile-linux-arm64v8: export GOOS := linux
|
||||
cross-compile-linux-arm64v8: export GOARCH := arm64
|
||||
cross-compile-linux-arm64v8: export CC := aarch64-linux-gnu-gcc
|
||||
cross-compile-linux-arm64v8: OUTPUT := -o dist/stash-linux-arm64v8
|
||||
cross-compile-linux-arm64v8: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
|
||||
cross-compile-linux-arm64v8: build-release-static
|
||||
|
||||
cross-compile-linux-arm32v7: export GOOS := linux
|
||||
|
|
@ -124,6 +137,7 @@ cross-compile-linux-arm32v7: export GOARCH := arm
|
|||
cross-compile-linux-arm32v7: export GOARM := 7
|
||||
cross-compile-linux-arm32v7: export CC := arm-linux-gnueabihf-gcc
|
||||
cross-compile-linux-arm32v7: OUTPUT := -o dist/stash-linux-arm32v7
|
||||
cross-compile-linux-arm32v7: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
|
||||
cross-compile-linux-arm32v7: build-release-static
|
||||
|
||||
cross-compile-linux-arm32v6: export GOOS := linux
|
||||
|
|
@ -131,11 +145,13 @@ cross-compile-linux-arm32v6: export GOARCH := arm
|
|||
cross-compile-linux-arm32v6: export GOARM := 6
|
||||
cross-compile-linux-arm32v6: export CC := arm-linux-gnueabi-gcc
|
||||
cross-compile-linux-arm32v6: OUTPUT := -o dist/stash-linux-arm32v6
|
||||
cross-compile-linux-arm32v6: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
|
||||
cross-compile-linux-arm32v6: build-release-static
|
||||
|
||||
cross-compile-all:
|
||||
make cross-compile-windows
|
||||
make cross-compile-macos
|
||||
make cross-compile-macos-intel
|
||||
make cross-compile-macos-applesilicon
|
||||
make cross-compile-linux
|
||||
make cross-compile-linux-arm64v8
|
||||
make cross-compile-linux-arm32v7
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ Many community-maintained scrapers are available for download at the [Community
|
|||
|
||||
# Translation
|
||||
[](https://translate.stashapp.cc/engage/stash/)
|
||||
🇧🇷 🇨🇳 🇩🇰 🇳🇱 🇬🇧 🇫🇮 🇫🇷 🇩🇪 🇮🇹 🇯🇵 🇰🇷 🇵🇱 🇪🇸 🇸🇪 🇹🇼 🇹🇷
|
||||
🇧🇷 🇨🇳 🇩🇰 🇳🇱 🇬🇧 🇪🇪 🇫🇮 🇫🇷 🇩🇪 🇮🇹 🇯🇵 🇰🇷 🇵🇱 🇷🇺 🇪🇸 🇸🇪 🇹🇼 🇹🇷
|
||||
|
||||
Stash is available in 16 languages (so far!) and it could be in your language too. If you want to help us translate Stash into your language, you can make an account at [translate.stashapp.cc](https://translate.stashapp.cc/projects/stash/stash-desktop-client/) to get started contributing new languages or improving existing ones. Thanks!
|
||||
Stash is available in 18 languages (so far!) and it could be in your language too. If you want to help us translate Stash into your language, you can make an account at [translate.stashapp.cc](https://translate.stashapp.cc/projects/stash/stash-desktop-client/) to get started contributing new languages or improving existing ones. Thanks!
|
||||
|
||||
# Support (FAQ)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ ARG STASH_VERSION
|
|||
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
|
||||
|
||||
# Build Backend
|
||||
FROM golang:1.17-alpine as backend
|
||||
FROM golang:1.19-alpine as backend
|
||||
RUN apk add --no-cache make alpine-sdk
|
||||
WORKDIR /stash
|
||||
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then BIN=stash-linux-arm32v6; \
|
|||
FROM --platform=$TARGETPLATFORM alpine:latest AS app
|
||||
COPY --from=binary /stash /usr/bin/
|
||||
RUN apk add --no-cache ca-certificates python3 py3-requests py3-requests-toolbelt py3-lxml py3-pip ffmpeg vips-tools ruby && pip install --no-cache-dir mechanicalsoup cloudscraper && gem install faraday
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
|
||||
|
||||
EXPOSE 9999
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.17
|
||||
FROM golang:1.19
|
||||
|
||||
LABEL maintainer="https://discord.gg/2TsNFKt"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
user=stashapp
|
||||
repo=compiler
|
||||
version=6
|
||||
version=7
|
||||
|
||||
latest:
|
||||
docker build -t ${user}/${repo}:latest .
|
||||
|
|
|
|||
|
|
@ -43,9 +43,10 @@ NOTE: The `make` command in Windows will be `mingw32-make` with MingW. For examp
|
|||
|
||||
## Building a release
|
||||
|
||||
1. Run `make generate` to create generated files
|
||||
2. Run `make ui` to compile the frontend
|
||||
3. Run `make build` to build the executable for your current platform
|
||||
1. Run `make pre-ui` to install UI dependencies
|
||||
2. Run `make generate` to create generated files
|
||||
3. Run `make ui` to compile the frontend
|
||||
4. Run `make build` to build the executable for your current platform
|
||||
|
||||
## Cross compiling
|
||||
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -108,4 +108,4 @@ require (
|
|||
|
||||
replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999
|
||||
|
||||
go 1.17
|
||||
go 1.19
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -700,7 +700,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
|||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/snowflakedb/gosnowflake v1.4.3/go.mod h1:1kyg2XEduwti88V11PKRHImhXLK5WpGiayY6lFNYb98=
|
||||
|
|
@ -773,7 +772,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
|
|
@ -958,7 +956,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
@ -1049,11 +1046,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs=
|
||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
|||
showStudioAsText
|
||||
css
|
||||
cssEnabled
|
||||
javascript
|
||||
javascriptEnabled
|
||||
customLocales
|
||||
customLocalesEnabled
|
||||
language
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ fragment VideoFileData on VideoFile {
|
|||
id
|
||||
path
|
||||
size
|
||||
mod_time
|
||||
duration
|
||||
video_codec
|
||||
audio_codec
|
||||
|
|
@ -24,6 +25,7 @@ fragment ImageFileData on ImageFile {
|
|||
id
|
||||
path
|
||||
size
|
||||
mod_time
|
||||
width
|
||||
height
|
||||
fingerprints {
|
||||
|
|
@ -36,6 +38,7 @@ fragment GalleryFileData on GalleryFile {
|
|||
id
|
||||
path
|
||||
size
|
||||
mod_time
|
||||
fingerprints {
|
||||
type
|
||||
value
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ fragment SlimGalleryData on Gallery {
|
|||
date
|
||||
url
|
||||
details
|
||||
rating
|
||||
rating100
|
||||
organized
|
||||
files {
|
||||
...GalleryFileData
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ fragment GalleryData on Gallery {
|
|||
date
|
||||
url
|
||||
details
|
||||
rating
|
||||
rating100
|
||||
organized
|
||||
|
||||
files {
|
||||
|
|
@ -16,9 +16,6 @@ fragment GalleryData on Gallery {
|
|||
...FolderData
|
||||
}
|
||||
|
||||
images {
|
||||
...SlimImageData
|
||||
}
|
||||
cover {
|
||||
...SlimImageData
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
fragment SlimImageData on Image {
|
||||
id
|
||||
title
|
||||
rating
|
||||
rating100
|
||||
organized
|
||||
o_counter
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
fragment ImageData on Image {
|
||||
id
|
||||
title
|
||||
rating
|
||||
rating100
|
||||
organized
|
||||
o_counter
|
||||
created_at
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ fragment SlimMovieData on Movie {
|
|||
id
|
||||
name
|
||||
front_image_path
|
||||
rating100
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ fragment MovieData on Movie {
|
|||
aliases
|
||||
duration
|
||||
date
|
||||
rating
|
||||
rating100
|
||||
director
|
||||
|
||||
studio {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ fragment SlimPerformerData on Performer {
|
|||
ethnicity
|
||||
hair_color
|
||||
eye_color
|
||||
height
|
||||
height_cm
|
||||
fake_tits
|
||||
career_length
|
||||
tattoos
|
||||
|
|
@ -26,7 +26,7 @@ fragment SlimPerformerData on Performer {
|
|||
endpoint
|
||||
stash_id
|
||||
}
|
||||
rating
|
||||
rating100
|
||||
death_date
|
||||
weight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ fragment PerformerData on Performer {
|
|||
ethnicity
|
||||
country
|
||||
eye_color
|
||||
height
|
||||
height_cm
|
||||
measurements
|
||||
fake_tits
|
||||
career_length
|
||||
|
|
@ -33,7 +33,7 @@ fragment PerformerData on Performer {
|
|||
stash_id
|
||||
endpoint
|
||||
}
|
||||
rating
|
||||
rating100
|
||||
details
|
||||
death_date
|
||||
hair_color
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
fragment SlimSceneData on Scene {
|
||||
id
|
||||
title
|
||||
code
|
||||
details
|
||||
director
|
||||
url
|
||||
date
|
||||
rating
|
||||
rating100
|
||||
o_counter
|
||||
organized
|
||||
interactive
|
||||
interactive_speed
|
||||
resume_time
|
||||
play_duration
|
||||
play_count
|
||||
|
||||
files {
|
||||
...VideoFileData
|
||||
|
|
@ -20,7 +25,6 @@ fragment SlimSceneData on Scene {
|
|||
stream
|
||||
webp
|
||||
vtt
|
||||
chapters_vtt
|
||||
sprite
|
||||
funscript
|
||||
interactive_heatmap
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
fragment SceneData on Scene {
|
||||
id
|
||||
title
|
||||
code
|
||||
details
|
||||
director
|
||||
url
|
||||
date
|
||||
rating
|
||||
rating100
|
||||
o_counter
|
||||
organized
|
||||
interactive
|
||||
|
|
@ -15,6 +17,10 @@ fragment SceneData on Scene {
|
|||
}
|
||||
created_at
|
||||
updated_at
|
||||
resume_time
|
||||
last_played_at
|
||||
play_duration
|
||||
play_count
|
||||
|
||||
files {
|
||||
...VideoFileData
|
||||
|
|
@ -26,7 +32,6 @@ fragment SceneData on Scene {
|
|||
stream
|
||||
webp
|
||||
vtt
|
||||
chapters_vtt
|
||||
sprite
|
||||
funscript
|
||||
interactive_heatmap
|
||||
|
|
|
|||
|
|
@ -105,7 +105,9 @@ fragment ScrapedSceneTagData on ScrapedTag {
|
|||
|
||||
fragment ScrapedSceneData on ScrapedScene {
|
||||
title
|
||||
code
|
||||
details
|
||||
director
|
||||
url
|
||||
date
|
||||
image
|
||||
|
|
@ -166,7 +168,9 @@ fragment ScrapedGalleryData on ScrapedGallery {
|
|||
|
||||
fragment ScrapedStashBoxSceneData on ScrapedScene {
|
||||
title
|
||||
code
|
||||
details
|
||||
director
|
||||
url
|
||||
date
|
||||
image
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ fragment SlimStudioData on Studio {
|
|||
id
|
||||
}
|
||||
details
|
||||
rating
|
||||
rating100
|
||||
aliases
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ fragment StudioData on Studio {
|
|||
endpoint
|
||||
}
|
||||
details
|
||||
rating
|
||||
rating100
|
||||
aliases
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
mutation SceneCreate(
|
||||
$input: SceneCreateInput!) {
|
||||
|
||||
sceneCreate(input: $input) {
|
||||
...SceneData
|
||||
}
|
||||
}
|
||||
|
||||
mutation SceneUpdate(
|
||||
$input: SceneUpdateInput!) {
|
||||
|
||||
|
|
@ -20,6 +28,14 @@ mutation ScenesUpdate($input : [SceneUpdateInput!]!) {
|
|||
}
|
||||
}
|
||||
|
||||
mutation SceneSaveActivity($id: ID!, $resume_time: Float, $playDuration: Float) {
|
||||
sceneSaveActivity(id: $id, resume_time: $resume_time, playDuration: $playDuration)
|
||||
}
|
||||
|
||||
mutation SceneIncrementPlayCount($id: ID!) {
|
||||
sceneIncrementPlayCount(id: $id)
|
||||
}
|
||||
|
||||
mutation SceneIncrementO($id: ID!) {
|
||||
sceneIncrementO(id: $id)
|
||||
}
|
||||
|
|
@ -43,3 +59,13 @@ mutation ScenesDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated :
|
|||
mutation SceneGenerateScreenshot($id: ID!, $at: Float) {
|
||||
sceneGenerateScreenshot(id: $id, at: $at)
|
||||
}
|
||||
|
||||
mutation SceneAssignFile($input: AssignSceneFileInput!) {
|
||||
sceneAssignFile(input: $input)
|
||||
}
|
||||
|
||||
mutation SceneMerge($input: SceneMergeInput!) {
|
||||
sceneMerge(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +52,9 @@ query ParseSceneFilenames($filter: FindFilterType!, $config: SceneParserInput!)
|
|||
...SlimSceneData
|
||||
}
|
||||
title
|
||||
code
|
||||
details
|
||||
director
|
||||
url
|
||||
date
|
||||
rating
|
||||
|
|
|
|||
|
|
@ -162,7 +162,9 @@ type Mutation {
|
|||
setup(input: SetupInput!): Boolean!
|
||||
migrate(input: MigrateInput!): Boolean!
|
||||
|
||||
sceneCreate(input: SceneCreateInput!): Scene
|
||||
sceneUpdate(input: SceneUpdateInput!): Scene
|
||||
sceneMerge(input: SceneMergeInput!): Scene
|
||||
bulkSceneUpdate(input: BulkSceneUpdateInput!): [Scene!]
|
||||
sceneDestroy(input: SceneDestroyInput!): Boolean!
|
||||
scenesDestroy(input: ScenesDestroyInput!): Boolean!
|
||||
|
|
@ -175,6 +177,12 @@ type Mutation {
|
|||
"""Resets the o-counter for a scene to 0. Returns the new value"""
|
||||
sceneResetO(id: ID!): Int!
|
||||
|
||||
"""Sets the resume time point (if provided) and adds the provided duration to the scene's play duration"""
|
||||
sceneSaveActivity(id: ID!, resume_time: Float, playDuration: Float): Boolean!
|
||||
|
||||
"""Increments the play count for the scene. Returns the new play count value."""
|
||||
sceneIncrementPlayCount(id: ID!): Int!
|
||||
|
||||
"""Generates screenshot at specified time in seconds. Leave empty to generate default screenshot"""
|
||||
sceneGenerateScreenshot(id: ID!, at: Float): String!
|
||||
|
||||
|
|
@ -182,6 +190,8 @@ type Mutation {
|
|||
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
|
||||
sceneMarkerDestroy(id: ID!): Boolean!
|
||||
|
||||
sceneAssignFile(input: AssignSceneFileInput!): Boolean!
|
||||
|
||||
imageUpdate(input: ImageUpdateInput!): Image
|
||||
bulkImageUpdate(input: BulkImageUpdateInput!): [Image!]
|
||||
imageDestroy(input: ImageDestroyInput!): Boolean!
|
||||
|
|
|
|||
|
|
@ -264,6 +264,10 @@ input ConfigInterfaceInput {
|
|||
css: String
|
||||
cssEnabled: Boolean
|
||||
|
||||
"""Custom Javascript"""
|
||||
javascript: String
|
||||
javascriptEnabled: Boolean
|
||||
|
||||
"""Custom Locales"""
|
||||
customLocales: String
|
||||
customLocalesEnabled: Boolean
|
||||
|
|
@ -330,6 +334,10 @@ type ConfigInterfaceResult {
|
|||
css: String
|
||||
cssEnabled: Boolean
|
||||
|
||||
"""Custom Javascript"""
|
||||
javascript: String
|
||||
javascriptEnabled: Boolean
|
||||
|
||||
"""Custom Locales"""
|
||||
customLocales: String
|
||||
customLocalesEnabled: Boolean
|
||||
|
|
|
|||
|
|
@ -39,6 +39,14 @@ input PHashDuplicationCriterionInput {
|
|||
distance: Int
|
||||
}
|
||||
|
||||
input StashIDCriterionInput {
|
||||
"""If present, this value is treated as a predicate.
|
||||
That is, it will filter based on stash_ids with the matching endpoint"""
|
||||
endpoint: String
|
||||
stash_id: String
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
input PerformerFilterType {
|
||||
AND: PerformerFilterType
|
||||
OR: PerformerFilterType
|
||||
|
|
@ -60,7 +68,9 @@ input PerformerFilterType {
|
|||
"""Filter by eye color"""
|
||||
eye_color: StringCriterionInput
|
||||
"""Filter by height"""
|
||||
height: StringCriterionInput
|
||||
height: StringCriterionInput @deprecated(reason: "Use height_cm instead")
|
||||
"""Filter by height in cm"""
|
||||
height_cm: IntCriterionInput
|
||||
"""Filter by measurements"""
|
||||
measurements: StringCriterionInput
|
||||
"""Filter by fake tits value"""
|
||||
|
|
@ -88,9 +98,13 @@ input PerformerFilterType {
|
|||
"""Filter by gallery count"""
|
||||
gallery_count: IntCriterionInput
|
||||
"""Filter by StashID"""
|
||||
stash_id: StringCriterionInput
|
||||
stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
|
||||
"""Filter by StashID"""
|
||||
stash_id_endpoint: StashIDCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: IntCriterionInput
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
"""Filter by hair color"""
|
||||
|
|
@ -103,6 +117,14 @@ input PerformerFilterType {
|
|||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
"""Filter by birthdate"""
|
||||
birthdate: DateCriterionInput
|
||||
"""Filter by death date"""
|
||||
death_date: DateCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input SceneMarkerFilterType {
|
||||
|
|
@ -114,6 +136,16 @@ input SceneMarkerFilterType {
|
|||
scene_tags: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include scene markers with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
"""Filter by scene date"""
|
||||
scene_date: DateCriterionInput
|
||||
"""Filter by cscene reation time"""
|
||||
scene_created_at: TimestampCriterionInput
|
||||
"""Filter by lscene ast update time"""
|
||||
scene_updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input SceneFilterType {
|
||||
|
|
@ -121,8 +153,11 @@ input SceneFilterType {
|
|||
OR: SceneFilterType
|
||||
NOT: SceneFilterType
|
||||
|
||||
id: IntCriterionInput
|
||||
title: StringCriterionInput
|
||||
code: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
director: StringCriterionInput
|
||||
|
||||
"""Filter by file oshash"""
|
||||
oshash: StringCriterionInput
|
||||
|
|
@ -135,7 +170,9 @@ input SceneFilterType {
|
|||
"""Filter by file count"""
|
||||
file_count: IntCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: IntCriterionInput
|
||||
"""Filter by organized"""
|
||||
organized: Boolean
|
||||
"""Filter by o-counter"""
|
||||
|
|
@ -169,7 +206,9 @@ input SceneFilterType {
|
|||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter by StashID"""
|
||||
stash_id: StringCriterionInput
|
||||
stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
|
||||
"""Filter by StashID"""
|
||||
stash_id_endpoint: StashIDCriterionInput
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
"""Filter by interactive"""
|
||||
|
|
@ -178,6 +217,18 @@ input SceneFilterType {
|
|||
interactive_speed: IntCriterionInput
|
||||
"""Filter by captions"""
|
||||
captions: StringCriterionInput
|
||||
"""Filter by resume time"""
|
||||
resume_time: IntCriterionInput
|
||||
"""Filter by play count"""
|
||||
play_count: IntCriterionInput
|
||||
"""Filter by play duration (in seconds)"""
|
||||
play_duration: IntCriterionInput
|
||||
"""Filter by date"""
|
||||
date: DateCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input MovieFilterType {
|
||||
|
|
@ -189,7 +240,9 @@ input MovieFilterType {
|
|||
"""Filter by duration (in seconds)"""
|
||||
duration: IntCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: IntCriterionInput
|
||||
"""Filter to only include movies with this studio"""
|
||||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include movies missing this property"""
|
||||
|
|
@ -198,6 +251,12 @@ input MovieFilterType {
|
|||
url: StringCriterionInput
|
||||
"""Filter to only include movies where performer appears in a scene"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by date"""
|
||||
date: DateCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input StudioFilterType {
|
||||
|
|
@ -210,11 +269,15 @@ input StudioFilterType {
|
|||
"""Filter to only include studios with this parent studio"""
|
||||
parents: MultiCriterionInput
|
||||
"""Filter by StashID"""
|
||||
stash_id: StringCriterionInput
|
||||
stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
|
||||
"""Filter by StashID"""
|
||||
stash_id_endpoint: StashIDCriterionInput
|
||||
"""Filter to only include studios missing this property"""
|
||||
is_missing: String
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: IntCriterionInput
|
||||
"""Filter by scene count"""
|
||||
scene_count: IntCriterionInput
|
||||
"""Filter by image count"""
|
||||
|
|
@ -227,6 +290,10 @@ input StudioFilterType {
|
|||
aliases: StringCriterionInput
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input GalleryFilterType {
|
||||
|
|
@ -234,6 +301,7 @@ input GalleryFilterType {
|
|||
OR: GalleryFilterType
|
||||
NOT: GalleryFilterType
|
||||
|
||||
id: IntCriterionInput
|
||||
title: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
||||
|
|
@ -248,7 +316,9 @@ input GalleryFilterType {
|
|||
"""Filter to include/exclude galleries that were created from zip"""
|
||||
is_zip: Boolean
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: IntCriterionInput
|
||||
"""Filter by organized"""
|
||||
organized: Boolean
|
||||
"""Filter by average image resolution"""
|
||||
|
|
@ -273,6 +343,12 @@ input GalleryFilterType {
|
|||
image_count: IntCriterionInput
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
"""Filter by date"""
|
||||
date: DateCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input TagFilterType {
|
||||
|
|
@ -286,6 +362,9 @@ input TagFilterType {
|
|||
"""Filter by tag aliases"""
|
||||
aliases: StringCriterionInput
|
||||
|
||||
"""Filter by tag description"""
|
||||
description: StringCriterionInput
|
||||
|
||||
"""Filter to only include tags missing this property"""
|
||||
is_missing: String
|
||||
|
||||
|
|
@ -318,6 +397,12 @@ input TagFilterType {
|
|||
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input ImageFilterType {
|
||||
|
|
@ -327,6 +412,8 @@ input ImageFilterType {
|
|||
|
||||
title: StringCriterionInput
|
||||
|
||||
""" Filter by image id"""
|
||||
id: IntCriterionInput
|
||||
"""Filter by file checksum"""
|
||||
checksum: StringCriterionInput
|
||||
"""Filter by path"""
|
||||
|
|
@ -334,7 +421,9 @@ input ImageFilterType {
|
|||
"""Filter by file count"""
|
||||
file_count: IntCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: IntCriterionInput
|
||||
"""Filter by organized"""
|
||||
organized: Boolean
|
||||
"""Filter by o-counter"""
|
||||
|
|
@ -359,6 +448,10 @@ input ImageFilterType {
|
|||
performer_favorite: Boolean
|
||||
"""Filter to only include images with these galleries"""
|
||||
galleries: MultiCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
enum CriterionModifier {
|
||||
|
|
@ -415,6 +508,18 @@ input HierarchicalMultiCriterionInput {
|
|||
depth: Int
|
||||
}
|
||||
|
||||
input DateCriterionInput {
|
||||
value: String!
|
||||
value2: String
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
input TimestampCriterionInput {
|
||||
value: String!
|
||||
value2: String
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
enum FilterMode {
|
||||
SCENES,
|
||||
PERFORMERS,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@ type Gallery {
|
|||
url: String
|
||||
date: String
|
||||
details: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
|
|
@ -23,7 +26,7 @@ type Gallery {
|
|||
performers: [Performer!]!
|
||||
|
||||
"""The images in the gallery"""
|
||||
images: [Image!]! # Resolver
|
||||
images: [Image!]! @deprecated(reason: "Use findImages")
|
||||
cover: Image
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +35,10 @@ input GalleryCreateInput {
|
|||
url: String
|
||||
date: String
|
||||
details: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean
|
||||
scene_ids: [ID!]
|
||||
studio_id: ID
|
||||
|
|
@ -47,7 +53,10 @@ input GalleryUpdateInput {
|
|||
url: String
|
||||
date: String
|
||||
details: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean
|
||||
scene_ids: [ID!]
|
||||
studio_id: ID
|
||||
|
|
@ -63,7 +72,10 @@ input BulkGalleryUpdateInput {
|
|||
url: String
|
||||
date: String
|
||||
details: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean
|
||||
scene_ids: BulkUpdateIds
|
||||
studio_id: ID
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ type Image {
|
|||
id: ID!
|
||||
checksum: String @deprecated(reason: "Use files.fingerprints")
|
||||
title: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
o_counter: Int
|
||||
organized: Boolean!
|
||||
path: String! @deprecated(reason: "Use files.path")
|
||||
|
|
@ -37,7 +40,10 @@ input ImageUpdateInput {
|
|||
clientMutationId: String
|
||||
id: ID!
|
||||
title: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean
|
||||
|
||||
studio_id: ID
|
||||
|
|
@ -52,7 +58,10 @@ input BulkImageUpdateInput {
|
|||
clientMutationId: String
|
||||
ids: [ID!]
|
||||
title: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean
|
||||
|
||||
studio_id: ID
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ type Movie {
|
|||
"""Duration in seconds"""
|
||||
duration: Int
|
||||
date: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
studio: Studio
|
||||
director: String
|
||||
synopsis: String
|
||||
|
|
@ -26,7 +29,10 @@ input MovieCreateInput {
|
|||
"""Duration in seconds"""
|
||||
duration: Int
|
||||
date: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
studio_id: ID
|
||||
director: String
|
||||
synopsis: String
|
||||
|
|
@ -43,7 +49,10 @@ input MovieUpdateInput {
|
|||
aliases: String
|
||||
duration: Int
|
||||
date: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
studio_id: ID
|
||||
director: String
|
||||
synopsis: String
|
||||
|
|
@ -57,7 +66,10 @@ input MovieUpdateInput {
|
|||
input BulkMovieUpdateInput {
|
||||
clientMutationId: String
|
||||
ids: [ID!]
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
studio_id: ID
|
||||
director: String
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ type Performer {
|
|||
ethnicity: String
|
||||
country: String
|
||||
eye_color: String
|
||||
height: String
|
||||
height: String @deprecated(reason: "Use height_cm")
|
||||
height_cm: Int
|
||||
measurements: String
|
||||
fake_tits: String
|
||||
career_length: String
|
||||
|
|
@ -36,7 +37,10 @@ type Performer {
|
|||
gallery_count: Int # Resolver
|
||||
scenes: [Scene!]!
|
||||
stash_ids: [StashID!]!
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
|
|
@ -55,7 +59,9 @@ input PerformerCreateInput {
|
|||
ethnicity: String
|
||||
country: String
|
||||
eye_color: String
|
||||
height: String
|
||||
# height must be parsable into an integer
|
||||
height: String @deprecated(reason: "Use height_cm")
|
||||
height_cm: Int
|
||||
measurements: String
|
||||
fake_tits: String
|
||||
career_length: String
|
||||
|
|
@ -69,7 +75,10 @@ input PerformerCreateInput {
|
|||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
|
|
@ -86,7 +95,9 @@ input PerformerUpdateInput {
|
|||
ethnicity: String
|
||||
country: String
|
||||
eye_color: String
|
||||
height: String
|
||||
# height must be parsable into an integer
|
||||
height: String @deprecated(reason: "Use height_cm")
|
||||
height_cm: Int
|
||||
measurements: String
|
||||
fake_tits: String
|
||||
career_length: String
|
||||
|
|
@ -100,7 +111,10 @@ input PerformerUpdateInput {
|
|||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
|
|
@ -117,7 +131,9 @@ input BulkPerformerUpdateInput {
|
|||
ethnicity: String
|
||||
country: String
|
||||
eye_color: String
|
||||
height: String
|
||||
# height must be parsable into an integer
|
||||
height: String @deprecated(reason: "Use height_cm")
|
||||
height_cm: Int
|
||||
measurements: String
|
||||
fake_tits: String
|
||||
career_length: String
|
||||
|
|
@ -128,7 +144,10 @@ input BulkPerformerUpdateInput {
|
|||
instagram: String
|
||||
favorite: Boolean
|
||||
tag_ids: BulkUpdateIds
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ type ScenePathsType {
|
|||
stream: String # Resolver
|
||||
webp: String # Resolver
|
||||
vtt: String # Resolver
|
||||
chapters_vtt: String # Resolver
|
||||
chapters_vtt: String @deprecated
|
||||
sprite: String # Resolver
|
||||
funscript: String # Resolver
|
||||
interactive_heatmap: String # Resolver
|
||||
|
|
@ -37,10 +37,15 @@ type Scene {
|
|||
checksum: String @deprecated(reason: "Use files.fingerprints")
|
||||
oshash: String @deprecated(reason: "Use files.fingerprints")
|
||||
title: String
|
||||
code: String
|
||||
details: String
|
||||
director: String
|
||||
url: String
|
||||
date: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean!
|
||||
o_counter: Int
|
||||
path: String! @deprecated(reason: "Use files.path")
|
||||
|
|
@ -51,6 +56,14 @@ type Scene {
|
|||
created_at: Time!
|
||||
updated_at: Time!
|
||||
file_mod_time: Time
|
||||
"""The last time play count was updated"""
|
||||
last_played_at: Time
|
||||
"""The time index a scene was left at"""
|
||||
resume_time: Float
|
||||
"""The total time a scene has spent playing"""
|
||||
play_duration: Float
|
||||
"""The number ot times a scene has been played"""
|
||||
play_count: Int
|
||||
|
||||
file: SceneFileType! @deprecated(reason: "Use files")
|
||||
files: [VideoFile!]!
|
||||
|
|
@ -73,14 +86,17 @@ input SceneMovieInput {
|
|||
scene_index: Int
|
||||
}
|
||||
|
||||
input SceneUpdateInput {
|
||||
clientMutationId: String
|
||||
id: ID!
|
||||
input SceneCreateInput {
|
||||
title: String
|
||||
code: String
|
||||
details: String
|
||||
director: String
|
||||
url: String
|
||||
date: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean
|
||||
studio_id: ID
|
||||
gallery_ids: [ID!]
|
||||
|
|
@ -91,6 +107,42 @@ input SceneUpdateInput {
|
|||
cover_image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
|
||||
"""The first id will be assigned as primary. Files will be reassigned from
|
||||
existing scenes if applicable. Files must not already be primary for another scene"""
|
||||
file_ids: [ID!]
|
||||
}
|
||||
|
||||
input SceneUpdateInput {
|
||||
clientMutationId: String
|
||||
id: ID!
|
||||
title: String
|
||||
code: String
|
||||
details: String
|
||||
director: String
|
||||
url: String
|
||||
date: String
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
o_counter: Int
|
||||
organized: Boolean
|
||||
studio_id: ID
|
||||
gallery_ids: [ID!]
|
||||
performer_ids: [ID!]
|
||||
movies: [SceneMovieInput!]
|
||||
tag_ids: [ID!]
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
cover_image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
|
||||
"""The time index a scene was left at"""
|
||||
resume_time: Float
|
||||
"""The total time a scene has spent playing"""
|
||||
play_duration: Float
|
||||
"""The number ot times a scene has been played"""
|
||||
play_count: Int
|
||||
|
||||
primary_file_id: ID
|
||||
}
|
||||
|
||||
|
|
@ -109,10 +161,15 @@ input BulkSceneUpdateInput {
|
|||
clientMutationId: String
|
||||
ids: [ID!]
|
||||
title: String
|
||||
code: String
|
||||
details: String
|
||||
director: String
|
||||
url: String
|
||||
date: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
organized: Boolean
|
||||
studio_id: ID
|
||||
gallery_ids: BulkUpdateIds
|
||||
|
|
@ -157,10 +214,15 @@ type SceneMovieID {
|
|||
type SceneParserResult {
|
||||
scene: Scene!
|
||||
title: String
|
||||
code: String
|
||||
details: String
|
||||
director: String
|
||||
url: String
|
||||
date: String
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
studio_id: ID
|
||||
gallery_ids: [ID!]
|
||||
performer_ids: [ID!]
|
||||
|
|
@ -183,3 +245,17 @@ type SceneStreamEndpoint {
|
|||
mime_type: String
|
||||
label: String
|
||||
}
|
||||
|
||||
input AssignSceneFileInput {
|
||||
scene_id: ID!
|
||||
file_id: ID!
|
||||
}
|
||||
|
||||
input SceneMergeInput {
|
||||
"""If destination scene has no files, then the primary file of the
|
||||
first source scene will be assigned as primary"""
|
||||
source: [ID!]!
|
||||
destination: ID!
|
||||
# values defined here will override values in the destination
|
||||
values: SceneUpdateInput
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,9 @@ type ScrapedTag {
|
|||
|
||||
type ScrapedScene {
|
||||
title: String
|
||||
code: String
|
||||
details: String
|
||||
director: String
|
||||
url: String
|
||||
date: String
|
||||
|
||||
|
|
@ -82,7 +84,9 @@ type ScrapedScene {
|
|||
|
||||
input ScrapedSceneInput {
|
||||
title: String
|
||||
code: String
|
||||
details: String
|
||||
director: String
|
||||
url: String
|
||||
date: String
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ type Studio {
|
|||
image_count: Int # Resolver
|
||||
gallery_count: Int # Resolver
|
||||
stash_ids: [StashID!]!
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
details: String
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
|
|
@ -28,7 +31,10 @@ input StudioCreateInput {
|
|||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
details: String
|
||||
aliases: [String!]
|
||||
ignore_auto_tag: Boolean
|
||||
|
|
@ -42,7 +48,10 @@ input StudioUpdateInput {
|
|||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
rating: Int
|
||||
# rating expressed as 1-5
|
||||
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
|
||||
# rating expressed as 1-100
|
||||
rating100: Int
|
||||
details: String
|
||||
aliases: [String!]
|
||||
ignore_auto_tag: Boolean
|
||||
|
|
|
|||
|
|
@ -94,7 +94,9 @@ fragment FingerprintFragment on Fingerprint {
|
|||
fragment SceneFragment on Scene {
|
||||
id
|
||||
title
|
||||
code
|
||||
details
|
||||
director
|
||||
duration
|
||||
date
|
||||
urls {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
|
|
@ -19,19 +20,35 @@ func getArgumentMap(ctx context.Context) map[string]interface{} {
|
|||
}
|
||||
|
||||
func getUpdateInputMap(ctx context.Context) map[string]interface{} {
|
||||
return getNamedUpdateInputMap(ctx, updateInputField)
|
||||
}
|
||||
|
||||
func getNamedUpdateInputMap(ctx context.Context, field string) map[string]interface{} {
|
||||
args := getArgumentMap(ctx)
|
||||
|
||||
input := args[updateInputField]
|
||||
var ret map[string]interface{}
|
||||
if input != nil {
|
||||
ret, _ = input.(map[string]interface{})
|
||||
// field can be qualified
|
||||
fields := strings.Split(field, ".")
|
||||
|
||||
currArgs := args
|
||||
|
||||
for _, f := range fields {
|
||||
v, found := currArgs[f]
|
||||
if !found {
|
||||
currArgs = nil
|
||||
break
|
||||
}
|
||||
|
||||
currArgs, _ = v.(map[string]interface{})
|
||||
if currArgs == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ret == nil {
|
||||
ret = make(map[string]interface{})
|
||||
if currArgs != nil {
|
||||
return currArgs
|
||||
}
|
||||
|
||||
return ret
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
|
||||
func getUpdateInputMaps(ctx context.Context) []map[string]interface{} {
|
||||
|
|
@ -90,6 +107,14 @@ func (t changesetTranslator) nullString(value *string, field string) *sql.NullSt
|
|||
return ret
|
||||
}
|
||||
|
||||
func (t changesetTranslator) string(value *string, field string) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *value
|
||||
}
|
||||
|
||||
func (t changesetTranslator) optionalString(value *string, field string) models.OptionalString {
|
||||
if !t.hasField(field) {
|
||||
return models.OptionalString{}
|
||||
|
|
@ -128,6 +153,27 @@ func (t changesetTranslator) optionalDate(value *string, field string) models.Op
|
|||
return models.NewOptionalDate(models.NewDate(*value))
|
||||
}
|
||||
|
||||
func (t changesetTranslator) datePtr(value *string, field string) *models.Date {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
d := models.NewDate(*value)
|
||||
return &d
|
||||
}
|
||||
|
||||
func (t changesetTranslator) intPtrFromString(value *string, field string) (*int, error) {
|
||||
if value == nil || *value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
vv, err := strconv.Atoi(*value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting %v to int: %w", *value, err)
|
||||
}
|
||||
return &vv, nil
|
||||
}
|
||||
|
||||
func (t changesetTranslator) nullInt64(value *int, field string) *sql.NullInt64 {
|
||||
if !t.hasField(field) {
|
||||
return nil
|
||||
|
|
@ -143,6 +189,56 @@ func (t changesetTranslator) nullInt64(value *int, field string) *sql.NullInt64
|
|||
return ret
|
||||
}
|
||||
|
||||
func (t changesetTranslator) ratingConversion(legacyValue *int, rating100Value *int) *sql.NullInt64 {
|
||||
const (
|
||||
legacyField = "rating"
|
||||
rating100Field = "rating100"
|
||||
)
|
||||
|
||||
legacyRating := t.nullInt64(legacyValue, legacyField)
|
||||
if legacyRating != nil {
|
||||
if legacyRating.Valid {
|
||||
legacyRating.Int64 = int64(models.Rating5To100(int(legacyRating.Int64)))
|
||||
}
|
||||
return legacyRating
|
||||
}
|
||||
return t.nullInt64(rating100Value, rating100Field)
|
||||
}
|
||||
|
||||
func (t changesetTranslator) ratingConversionInt(legacyValue *int, rating100Value *int) *int {
|
||||
const (
|
||||
legacyField = "rating"
|
||||
rating100Field = "rating100"
|
||||
)
|
||||
|
||||
legacyRating := t.optionalInt(legacyValue, legacyField)
|
||||
if legacyRating.Set && !(legacyRating.Null) {
|
||||
ret := int(models.Rating5To100(int(legacyRating.Value)))
|
||||
return &ret
|
||||
}
|
||||
|
||||
o := t.optionalInt(rating100Value, rating100Field)
|
||||
if o.Set && !(o.Null) {
|
||||
return &o.Value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t changesetTranslator) ratingConversionOptional(legacyValue *int, rating100Value *int) models.OptionalInt {
|
||||
const (
|
||||
legacyField = "rating"
|
||||
rating100Field = "rating100"
|
||||
)
|
||||
|
||||
legacyRating := t.optionalInt(legacyValue, legacyField)
|
||||
if legacyRating.Set && !(legacyRating.Null) {
|
||||
legacyRating.Value = int(models.Rating5To100(int(legacyRating.Value)))
|
||||
return legacyRating
|
||||
}
|
||||
return t.optionalInt(rating100Value, rating100Field)
|
||||
}
|
||||
|
||||
func (t changesetTranslator) optionalInt(value *int, field string) models.OptionalInt {
|
||||
if !t.hasField(field) {
|
||||
return models.OptionalInt{}
|
||||
|
|
@ -185,19 +281,12 @@ func (t changesetTranslator) optionalIntFromString(value *string, field string)
|
|||
return models.NewOptionalInt(vv), nil
|
||||
}
|
||||
|
||||
func (t changesetTranslator) nullBool(value *bool, field string) *sql.NullBool {
|
||||
if !t.hasField(field) {
|
||||
return nil
|
||||
func (t changesetTranslator) bool(value *bool, field string) bool {
|
||||
if value == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ret := &sql.NullBool{}
|
||||
|
||||
if value != nil {
|
||||
ret.Bool = *value
|
||||
ret.Valid = true
|
||||
}
|
||||
|
||||
return ret
|
||||
return *value
|
||||
}
|
||||
|
||||
func (t changesetTranslator) optionalBool(value *bool, field string) models.OptionalBool {
|
||||
|
|
@ -207,3 +296,11 @@ func (t changesetTranslator) optionalBool(value *bool, field string) models.Opti
|
|||
|
||||
return models.NewOptionalBoolPtr(value)
|
||||
}
|
||||
|
||||
func (t changesetTranslator) optionalFloat64(value *float64, field string) models.OptionalFloat64 {
|
||||
if !t.hasField(field) {
|
||||
return models.OptionalFloat64{}
|
||||
}
|
||||
|
||||
return models.NewOptionalFloat64Ptr(value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/stashapp/stash/internal/static"
|
||||
"github.com/stashapp/stash/pkg/hash"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type imageBox struct {
|
||||
|
|
@ -86,7 +87,7 @@ func initialiseCustomImages() {
|
|||
}
|
||||
}
|
||||
|
||||
func getRandomPerformerImageUsingName(name, gender, customPath string) ([]byte, error) {
|
||||
func getRandomPerformerImageUsingName(name string, gender models.GenderEnum, customPath string) ([]byte, error) {
|
||||
var box *imageBox
|
||||
|
||||
// If we have a custom path, we should return a new box in the given path.
|
||||
|
|
@ -95,10 +96,10 @@ func getRandomPerformerImageUsingName(name, gender, customPath string) ([]byte,
|
|||
}
|
||||
|
||||
if box == nil {
|
||||
switch strings.ToUpper(gender) {
|
||||
case "FEMALE":
|
||||
switch gender {
|
||||
case models.GenderEnumFemale:
|
||||
box = performerBox
|
||||
case "MALE":
|
||||
case models.GenderEnumMale:
|
||||
box = performerBoxMale
|
||||
default:
|
||||
box = performerBox
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@ var matcher = language.NewMatcher([]language.Tag{
|
|||
language.MustParse("da-DK"),
|
||||
language.MustParse("pl-PL"),
|
||||
language.MustParse("ko-KR"),
|
||||
language.MustParse("cs-CZ"),
|
||||
language.MustParse("bn-BD"),
|
||||
language.MustParse("et-EE"),
|
||||
language.MustParse("fa-IR"),
|
||||
language.MustParse("hu-HU"),
|
||||
language.MustParse("ro-RO"),
|
||||
language.MustParse("th-TH"),
|
||||
language.MustParse("uk-UA"),
|
||||
})
|
||||
|
||||
// newCollator parses a locale into a collator
|
||||
|
|
|
|||
|
|
@ -95,8 +95,12 @@ func (r *Resolver) withTxn(ctx context.Context, fn func(ctx context.Context) err
|
|||
return txn.WithTxn(ctx, r.txnManager, fn)
|
||||
}
|
||||
|
||||
func (r *Resolver) withReadTxn(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||
return txn.WithReadTxn(ctx, r.txnManager, fn)
|
||||
}
|
||||
|
||||
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*models.SceneMarker, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.SceneMarker.Wall(ctx, q)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -106,7 +110,7 @@ func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*model
|
|||
}
|
||||
|
||||
func (r *queryResolver) SceneWall(ctx context.Context, q *string) (ret []*models.Scene, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Scene.Wall(ctx, q)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -117,7 +121,7 @@ func (r *queryResolver) SceneWall(ctx context.Context, q *string) (ret []*models
|
|||
}
|
||||
|
||||
func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *string) (ret []*models.MarkerStringsResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.SceneMarker.GetMarkerStrings(ctx, q, sort)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -129,7 +133,7 @@ func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *stri
|
|||
|
||||
func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
|
||||
var ret StatsResultType
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
repo := r.repository
|
||||
scenesQB := repo.Scene
|
||||
imageQB := repo.Image
|
||||
|
|
@ -205,7 +209,7 @@ func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([
|
|||
var keys []int
|
||||
tags := make(map[int]*SceneMarkerTag)
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
sceneMarkers, err := r.repository.SceneMarker.FindBySceneID(ctx, sceneID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ func (r *galleryResolver) Folder(ctx context.Context, obj *models.Gallery) (*Fol
|
|||
|
||||
var ret *file.Folder
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
ret, err = r.repository.Folder.Find(ctx, *obj.FolderID)
|
||||
|
|
@ -123,8 +123,9 @@ func (r *galleryResolver) FileModTime(ctx context.Context, obj *models.Gallery)
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// Images is deprecated, slow and shouldn't be used
|
||||
func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) (ret []*models.Image, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
// #2376 - sort images by path
|
||||
|
|
@ -143,25 +144,10 @@ func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) (ret
|
|||
}
|
||||
|
||||
func (r *galleryResolver) Cover(ctx context.Context, obj *models.Gallery) (ret *models.Image, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
// doing this via Query is really slow, so stick with FindByGalleryID
|
||||
imgs, err := r.repository.Image.FindByGalleryID(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(imgs) > 0 {
|
||||
ret = imgs[0]
|
||||
}
|
||||
|
||||
for _, img := range imgs {
|
||||
if image.IsCover(img) {
|
||||
ret = img
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
// find cover.jpg first
|
||||
ret, err = image.FindGalleryCover(ctx, r.repository.Image, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -179,7 +165,7 @@ func (r *galleryResolver) Date(ctx context.Context, obj *models.Gallery) (*strin
|
|||
|
||||
func (r *galleryResolver) Checksum(ctx context.Context, obj *models.Gallery) (string, error) {
|
||||
if !obj.Files.PrimaryLoaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadPrimaryFile(ctx, r.repository.File)
|
||||
}); err != nil {
|
||||
return "", err
|
||||
|
|
@ -189,9 +175,21 @@ func (r *galleryResolver) Checksum(ctx context.Context, obj *models.Gallery) (st
|
|||
return obj.PrimaryChecksum(), nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Rating(ctx context.Context, obj *models.Gallery) (*int, error) {
|
||||
if obj.Rating != nil {
|
||||
rating := models.Rating100To5(*obj.Rating)
|
||||
return &rating, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Rating100(ctx context.Context, obj *models.Gallery) (*int, error) {
|
||||
return obj.Rating, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Scenes(ctx context.Context, obj *models.Gallery) (ret []*models.Scene, err error) {
|
||||
if !obj.SceneIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadSceneIDs(ctx, r.repository.Gallery)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -213,7 +211,7 @@ func (r *galleryResolver) Studio(ctx context.Context, obj *models.Gallery) (ret
|
|||
|
||||
func (r *galleryResolver) Tags(ctx context.Context, obj *models.Gallery) (ret []*models.Tag, err error) {
|
||||
if !obj.TagIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadTagIDs(ctx, r.repository.Gallery)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -227,7 +225,7 @@ func (r *galleryResolver) Tags(ctx context.Context, obj *models.Gallery) (ret []
|
|||
|
||||
func (r *galleryResolver) Performers(ctx context.Context, obj *models.Gallery) (ret []*models.Performer, err error) {
|
||||
if !obj.PerformerIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadPerformerIDs(ctx, r.repository.Gallery)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -240,7 +238,7 @@ func (r *galleryResolver) Performers(ctx context.Context, obj *models.Gallery) (
|
|||
}
|
||||
|
||||
func (r *galleryResolver) ImageCount(ctx context.Context, obj *models.Gallery) (ret int, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
ret, err = r.repository.Image.CountByGalleryID(ctx, obj.ID)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ func (r *imageResolver) Paths(ctx context.Context, obj *models.Image) (*ImagePat
|
|||
|
||||
func (r *imageResolver) Galleries(ctx context.Context, obj *models.Image) (ret []*models.Gallery, err error) {
|
||||
if !obj.GalleryIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadGalleryIDs(ctx, r.repository.Image)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -144,6 +144,18 @@ func (r *imageResolver) Galleries(ctx context.Context, obj *models.Image) (ret [
|
|||
return ret, firstError(errs)
|
||||
}
|
||||
|
||||
func (r *imageResolver) Rating(ctx context.Context, obj *models.Image) (*int, error) {
|
||||
if obj.Rating != nil {
|
||||
rating := models.Rating100To5(*obj.Rating)
|
||||
return &rating, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) Rating100(ctx context.Context, obj *models.Image) (*int, error) {
|
||||
return obj.Rating, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) Studio(ctx context.Context, obj *models.Image) (ret *models.Studio, err error) {
|
||||
if obj.StudioID == nil {
|
||||
return nil, nil
|
||||
|
|
@ -154,7 +166,7 @@ func (r *imageResolver) Studio(ctx context.Context, obj *models.Image) (ret *mod
|
|||
|
||||
func (r *imageResolver) Tags(ctx context.Context, obj *models.Image) (ret []*models.Tag, err error) {
|
||||
if !obj.TagIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadTagIDs(ctx, r.repository.Image)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -168,7 +180,7 @@ func (r *imageResolver) Tags(ctx context.Context, obj *models.Image) (ret []*mod
|
|||
|
||||
func (r *imageResolver) Performers(ctx context.Context, obj *models.Image) (ret []*models.Performer, err error) {
|
||||
if !obj.PerformerIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadPerformerIDs(ctx, r.repository.Image)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -48,6 +48,14 @@ func (r *movieResolver) Date(ctx context.Context, obj *models.Movie) (*string, e
|
|||
}
|
||||
|
||||
func (r *movieResolver) Rating(ctx context.Context, obj *models.Movie) (*int, error) {
|
||||
if obj.Rating.Valid {
|
||||
rating := models.Rating100To5(int(obj.Rating.Int64))
|
||||
return &rating, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) Rating100(ctx context.Context, obj *models.Movie) (*int, error) {
|
||||
if obj.Rating.Valid {
|
||||
rating := int(obj.Rating.Int64)
|
||||
return &rating, nil
|
||||
|
|
@ -86,7 +94,7 @@ func (r *movieResolver) FrontImagePath(ctx context.Context, obj *models.Movie) (
|
|||
func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
// don't return any thing if there is no back image
|
||||
var img []byte
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
img, err = r.repository.Movie.GetBackImage(ctx, obj.ID)
|
||||
if err != nil {
|
||||
|
|
@ -109,7 +117,7 @@ func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*
|
|||
|
||||
func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = r.repository.Scene.CountByMovieID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -120,7 +128,7 @@ func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret
|
|||
}
|
||||
|
||||
func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*models.Scene, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
ret, err = r.repository.Scene.FindByMovieID(ctx, obj.ID)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/internal/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/gallery"
|
||||
|
|
@ -10,131 +10,26 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
func (r *performerResolver) Name(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||
if obj.Name.Valid {
|
||||
return &obj.Name.String, nil
|
||||
func (r *performerResolver) Height(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||
if obj.Height != nil {
|
||||
ret := strconv.Itoa(*obj.Height)
|
||||
return &ret, 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) Gender(ctx context.Context, obj *models.Performer) (*models.GenderEnum, error) {
|
||||
var ret models.GenderEnum
|
||||
|
||||
if obj.Gender.Valid {
|
||||
ret = models.GenderEnum(obj.Gender.String)
|
||||
if ret.IsValid() {
|
||||
return &ret, 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) HeightCm(ctx context.Context, obj *models.Performer) (*int, error) {
|
||||
return obj.Height, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) Birthdate(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||
if obj.Birthdate.Valid {
|
||||
return &obj.Birthdate.String, nil
|
||||
if obj.Birthdate != nil {
|
||||
ret := obj.Birthdate.String()
|
||||
return &ret, 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).GetPerformerImageURL()
|
||||
|
|
@ -142,7 +37,7 @@ func (r *performerResolver) ImagePath(ctx context.Context, obj *models.Performer
|
|||
}
|
||||
|
||||
func (r *performerResolver) Tags(ctx context.Context, obj *models.Performer) (ret []*models.Tag, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Tag.FindByPerformerID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -154,7 +49,7 @@ func (r *performerResolver) Tags(ctx context.Context, obj *models.Performer) (re
|
|||
|
||||
func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = r.repository.Scene.CountByPerformerID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -166,7 +61,7 @@ func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performe
|
|||
|
||||
func (r *performerResolver) ImageCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = image.CountByPerformerID(ctx, r.repository.Image, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -178,7 +73,7 @@ func (r *performerResolver) ImageCount(ctx context.Context, obj *models.Performe
|
|||
|
||||
func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = gallery.CountByPerformerID(ctx, r.repository.Gallery, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -189,7 +84,7 @@ func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Perfor
|
|||
}
|
||||
|
||||
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) (ret []*models.Scene, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Scene.FindByPerformerID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -201,7 +96,7 @@ func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) (
|
|||
|
||||
func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer) ([]*models.StashID, error) {
|
||||
var ret []models.StashID
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
ret, err = r.repository.Performer.GetStashIDs(ctx, obj.ID)
|
||||
return err
|
||||
|
|
@ -213,52 +108,27 @@ func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer)
|
|||
}
|
||||
|
||||
func (r *performerResolver) Rating(ctx context.Context, obj *models.Performer) (*int, error) {
|
||||
if obj.Rating.Valid {
|
||||
rating := int(obj.Rating.Int64)
|
||||
if obj.Rating != nil {
|
||||
rating := models.Rating100To5(*obj.Rating)
|
||||
return &rating, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) Details(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||
if obj.Details.Valid {
|
||||
return &obj.Details.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
func (r *performerResolver) Rating100(ctx context.Context, obj *models.Performer) (*int, error) {
|
||||
return obj.Rating, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) DeathDate(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||
if obj.DeathDate.Valid {
|
||||
return &obj.DeathDate.String, nil
|
||||
if obj.DeathDate != nil {
|
||||
ret := obj.DeathDate.String()
|
||||
return &ret, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) HairColor(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||
if obj.HairColor.Valid {
|
||||
return &obj.HairColor.String, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) Weight(ctx context.Context, obj *models.Performer) (*int, error) {
|
||||
if obj.Weight.Valid {
|
||||
weight := int(obj.Weight.Int64)
|
||||
return &weight, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) CreatedAt(ctx context.Context, obj *models.Performer) (*time.Time, error) {
|
||||
return &obj.CreatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) UpdatedAt(ctx context.Context, obj *models.Performer) (*time.Time, error) {
|
||||
return &obj.UpdatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Movie.FindByPerformerID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -270,7 +140,7 @@ func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (
|
|||
|
||||
func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = r.repository.Movie.CountByPerformerID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ func (r *sceneResolver) getPrimaryFile(ctx context.Context, obj *models.Scene) (
|
|||
obj.Files.SetPrimary(ret)
|
||||
|
||||
return ret, nil
|
||||
} else {
|
||||
_ = obj.LoadPrimaryFile(ctx, r.repository.File)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
|
@ -139,6 +141,18 @@ func (r *sceneResolver) Files(ctx context.Context, obj *models.Scene) ([]*VideoF
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Rating(ctx context.Context, obj *models.Scene) (*int, error) {
|
||||
if obj.Rating != nil {
|
||||
rating := models.Rating100To5(*obj.Rating)
|
||||
return &rating, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Rating100(ctx context.Context, obj *models.Scene) (*int, error) {
|
||||
return obj.Rating, nil
|
||||
}
|
||||
|
||||
func resolveFingerprints(f *file.BaseFile) []*Fingerprint {
|
||||
ret := make([]*Fingerprint, len(f.Fingerprints))
|
||||
|
||||
|
|
@ -192,7 +206,7 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*ScenePat
|
|||
}
|
||||
|
||||
func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) (ret []*models.SceneMarker, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.SceneMarker.FindBySceneID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -211,7 +225,7 @@ func (r *sceneResolver) Captions(ctx context.Context, obj *models.Scene) (ret []
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.File.GetCaptions(ctx, primaryFile.Base().ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -223,7 +237,7 @@ func (r *sceneResolver) Captions(ctx context.Context, obj *models.Scene) (ret []
|
|||
|
||||
func (r *sceneResolver) Galleries(ctx context.Context, obj *models.Scene) (ret []*models.Gallery, err error) {
|
||||
if !obj.GalleryIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadGalleryIDs(ctx, r.repository.Scene)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -245,7 +259,7 @@ func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (ret *mod
|
|||
|
||||
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*SceneMovie, err error) {
|
||||
if !obj.Movies.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Scene
|
||||
|
||||
return obj.LoadMovies(ctx, qb)
|
||||
|
|
@ -276,7 +290,7 @@ func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*S
|
|||
|
||||
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) (ret []*models.Tag, err error) {
|
||||
if !obj.TagIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadTagIDs(ctx, r.repository.Scene)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -290,7 +304,7 @@ func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) (ret []*mod
|
|||
|
||||
func (r *sceneResolver) Performers(ctx context.Context, obj *models.Scene) (ret []*models.Performer, err error) {
|
||||
if !obj.PerformerIDs.Loaded() {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadPerformerIDs(ctx, r.repository.Scene)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -313,7 +327,7 @@ func stashIDsSliceToPtrSlice(v []models.StashID) []*models.StashID {
|
|||
}
|
||||
|
||||
func (r *sceneResolver) StashIds(ctx context.Context, obj *models.Scene) (ret []*models.StashID, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
return obj.LoadStashIDs(ctx, r.repository.Scene)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func (r *sceneMarkerResolver) Scene(ctx context.Context, obj *models.SceneMarker
|
|||
panic("Invalid scene id")
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
sceneID := int(obj.SceneID.Int64)
|
||||
ret, err = r.repository.Scene.Find(ctx, sceneID)
|
||||
return err
|
||||
|
|
@ -25,7 +25,7 @@ func (r *sceneMarkerResolver) Scene(ctx context.Context, obj *models.SceneMarker
|
|||
}
|
||||
|
||||
func (r *sceneMarkerResolver) PrimaryTag(ctx context.Context, obj *models.SceneMarker) (ret *models.Tag, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Tag.Find(ctx, obj.PrimaryTagID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -36,7 +36,7 @@ func (r *sceneMarkerResolver) PrimaryTag(ctx context.Context, obj *models.SceneM
|
|||
}
|
||||
|
||||
func (r *sceneMarkerResolver) Tags(ctx context.Context, obj *models.SceneMarker) (ret []*models.Tag, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Tag.FindBySceneMarkerID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func (r *studioResolver) ImagePath(ctx context.Context, obj *models.Studio) (*st
|
|||
imagePath := urlbuilders.NewStudioURLBuilder(baseURL, obj).GetStudioImageURL()
|
||||
|
||||
var hasImage bool
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
hasImage, err = r.repository.Studio.HasImage(ctx, obj.ID)
|
||||
return err
|
||||
|
|
@ -47,7 +47,7 @@ func (r *studioResolver) ImagePath(ctx context.Context, obj *models.Studio) (*st
|
|||
}
|
||||
|
||||
func (r *studioResolver) Aliases(ctx context.Context, obj *models.Studio) (ret []string, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Studio.GetAliases(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -59,7 +59,7 @@ func (r *studioResolver) Aliases(ctx context.Context, obj *models.Studio) (ret [
|
|||
|
||||
func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = r.repository.Scene.CountByStudioID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -71,7 +71,7 @@ func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio) (re
|
|||
|
||||
func (r *studioResolver) ImageCount(ctx context.Context, obj *models.Studio) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = image.CountByStudioID(ctx, r.repository.Image, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -83,7 +83,7 @@ func (r *studioResolver) ImageCount(ctx context.Context, obj *models.Studio) (re
|
|||
|
||||
func (r *studioResolver) GalleryCount(ctx context.Context, obj *models.Studio) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = gallery.CountByStudioID(ctx, r.repository.Gallery, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -102,7 +102,7 @@ func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (
|
|||
}
|
||||
|
||||
func (r *studioResolver) ChildStudios(ctx context.Context, obj *models.Studio) (ret []*models.Studio, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Studio.FindChildren(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -114,7 +114,7 @@ func (r *studioResolver) ChildStudios(ctx context.Context, obj *models.Studio) (
|
|||
|
||||
func (r *studioResolver) StashIds(ctx context.Context, obj *models.Studio) ([]*models.StashID, error) {
|
||||
var ret []models.StashID
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
ret, err = r.repository.Studio.GetStashIDs(ctx, obj.ID)
|
||||
return err
|
||||
|
|
@ -126,6 +126,14 @@ func (r *studioResolver) StashIds(ctx context.Context, obj *models.Studio) ([]*m
|
|||
}
|
||||
|
||||
func (r *studioResolver) Rating(ctx context.Context, obj *models.Studio) (*int, error) {
|
||||
if obj.Rating.Valid {
|
||||
rating := models.Rating100To5(int(obj.Rating.Int64))
|
||||
return &rating, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *studioResolver) Rating100(ctx context.Context, obj *models.Studio) (*int, error) {
|
||||
if obj.Rating.Valid {
|
||||
rating := int(obj.Rating.Int64)
|
||||
return &rating, nil
|
||||
|
|
@ -149,7 +157,7 @@ func (r *studioResolver) UpdatedAt(ctx context.Context, obj *models.Studio) (*ti
|
|||
}
|
||||
|
||||
func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret []*models.Movie, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Movie.FindByStudioID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -161,7 +169,7 @@ func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret []
|
|||
|
||||
func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = r.repository.Movie.CountByStudioID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func (r *tagResolver) Description(ctx context.Context, obj *models.Tag) (*string
|
|||
}
|
||||
|
||||
func (r *tagResolver) Parents(ctx context.Context, obj *models.Tag) (ret []*models.Tag, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Tag.FindByChildTagID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -29,7 +29,7 @@ func (r *tagResolver) Parents(ctx context.Context, obj *models.Tag) (ret []*mode
|
|||
}
|
||||
|
||||
func (r *tagResolver) Children(ctx context.Context, obj *models.Tag) (ret []*models.Tag, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Tag.FindByParentTagID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -40,7 +40,7 @@ func (r *tagResolver) Children(ctx context.Context, obj *models.Tag) (ret []*mod
|
|||
}
|
||||
|
||||
func (r *tagResolver) Aliases(ctx context.Context, obj *models.Tag) (ret []string, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Tag.GetAliases(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -52,7 +52,7 @@ func (r *tagResolver) Aliases(ctx context.Context, obj *models.Tag) (ret []strin
|
|||
|
||||
func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag) (ret *int, err error) {
|
||||
var count int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
count, err = r.repository.Scene.CountByTagID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -64,7 +64,7 @@ func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag) (ret *int
|
|||
|
||||
func (r *tagResolver) SceneMarkerCount(ctx context.Context, obj *models.Tag) (ret *int, err error) {
|
||||
var count int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
count, err = r.repository.SceneMarker.CountByTagID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -76,7 +76,7 @@ func (r *tagResolver) SceneMarkerCount(ctx context.Context, obj *models.Tag) (re
|
|||
|
||||
func (r *tagResolver) ImageCount(ctx context.Context, obj *models.Tag) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = image.CountByTagID(ctx, r.repository.Image, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -88,7 +88,7 @@ func (r *tagResolver) ImageCount(ctx context.Context, obj *models.Tag) (ret *int
|
|||
|
||||
func (r *tagResolver) GalleryCount(ctx context.Context, obj *models.Tag) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
res, err = gallery.CountByTagID(ctx, r.repository.Gallery, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -100,7 +100,7 @@ func (r *tagResolver) GalleryCount(ctx context.Context, obj *models.Tag) (ret *i
|
|||
|
||||
func (r *tagResolver) PerformerCount(ctx context.Context, obj *models.Tag) (ret *int, err error) {
|
||||
var count int
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
count, err = r.repository.Performer.CountByTagID(ctx, obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
|
|||
}
|
||||
|
||||
validateDir := func(key string, value string, optional bool) error {
|
||||
if err := checkConfigOverride(config.Metadata); err != nil {
|
||||
if err := checkConfigOverride(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -365,6 +365,12 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI
|
|||
|
||||
setBool(config.CSSEnabled, input.CSSEnabled)
|
||||
|
||||
if input.Javascript != nil {
|
||||
c.SetJavascript(*input.Javascript)
|
||||
}
|
||||
|
||||
setBool(config.JavascriptEnabled, input.JavascriptEnabled)
|
||||
|
||||
if input.CustomLocales != nil {
|
||||
c.SetCustomLocales(*input.CustomLocales)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,13 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input GalleryCreat
|
|||
d := models.NewDate(*input.Date)
|
||||
newGallery.Date = &d
|
||||
}
|
||||
newGallery.Rating = input.Rating
|
||||
|
||||
if input.Rating100 != nil {
|
||||
newGallery.Rating = input.Rating100
|
||||
} else if input.Rating != nil {
|
||||
rating := models.Rating5To100(*input.Rating)
|
||||
newGallery.Rating = &rating
|
||||
}
|
||||
|
||||
if input.StudioID != nil {
|
||||
studioID, _ := strconv.Atoi(*input.StudioID)
|
||||
|
|
@ -177,8 +183,8 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle
|
|||
|
||||
if input.Title != nil {
|
||||
// ensure title is not empty
|
||||
if *input.Title == "" {
|
||||
return nil, errors.New("title must not be empty")
|
||||
if *input.Title == "" && originalGallery.IsUserCreated() {
|
||||
return nil, errors.New("title must not be empty for user-created galleries")
|
||||
}
|
||||
|
||||
updatedGallery.Title = models.NewOptionalString(*input.Title)
|
||||
|
|
@ -187,7 +193,7 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle
|
|||
updatedGallery.Details = translator.optionalString(input.Details, "details")
|
||||
updatedGallery.URL = translator.optionalString(input.URL, "url")
|
||||
updatedGallery.Date = translator.optionalDate(input.Date, "date")
|
||||
updatedGallery.Rating = translator.optionalInt(input.Rating, "rating")
|
||||
updatedGallery.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100)
|
||||
updatedGallery.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||
|
|
@ -262,8 +268,7 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input BulkGall
|
|||
updatedGallery.Details = translator.optionalString(input.Details, "details")
|
||||
updatedGallery.URL = translator.optionalString(input.URL, "url")
|
||||
updatedGallery.Date = translator.optionalDate(input.Date, "date")
|
||||
updatedGallery.Rating = translator.optionalInt(input.Rating, "rating")
|
||||
|
||||
updatedGallery.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100)
|
||||
var err error
|
||||
updatedGallery.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp
|
|||
|
||||
updatedImage := models.NewImagePartial()
|
||||
updatedImage.Title = translator.optionalString(input.Title, "title")
|
||||
updatedImage.Rating = translator.optionalInt(input.Rating, "rating")
|
||||
updatedImage.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100)
|
||||
updatedImage.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||
|
|
@ -189,7 +189,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageU
|
|||
}
|
||||
|
||||
updatedImage.Title = translator.optionalString(input.Title, "title")
|
||||
updatedImage.Rating = translator.optionalInt(input.Rating, "rating")
|
||||
updatedImage.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100)
|
||||
updatedImage.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||
|
|
|
|||
|
|
@ -76,9 +76,11 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp
|
|||
newMovie.Date = models.SQLiteDate{String: *input.Date, Valid: true}
|
||||
}
|
||||
|
||||
if input.Rating != nil {
|
||||
rating := int64(*input.Rating)
|
||||
newMovie.Rating = sql.NullInt64{Int64: rating, Valid: true}
|
||||
if input.Rating100 != nil {
|
||||
newMovie.Rating = sql.NullInt64{Int64: int64(*input.Rating100), Valid: true}
|
||||
} else if input.Rating != nil {
|
||||
rating := models.Rating5To100(*input.Rating)
|
||||
newMovie.Rating = sql.NullInt64{Int64: int64(rating), Valid: true}
|
||||
}
|
||||
|
||||
if input.StudioID != nil {
|
||||
|
|
@ -166,7 +168,7 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp
|
|||
updatedMovie.Aliases = translator.nullString(input.Aliases, "aliases")
|
||||
updatedMovie.Duration = translator.nullInt64(input.Duration, "duration")
|
||||
updatedMovie.Date = translator.sqliteDate(input.Date, "date")
|
||||
updatedMovie.Rating = translator.nullInt64(input.Rating, "rating")
|
||||
updatedMovie.Rating = translator.ratingConversion(input.Rating, input.Rating100)
|
||||
updatedMovie.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
|
||||
updatedMovie.Director = translator.nullString(input.Director, "director")
|
||||
updatedMovie.Synopsis = translator.nullString(input.Synopsis, "synopsis")
|
||||
|
|
@ -239,7 +241,7 @@ func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieU
|
|||
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
|
||||
}
|
||||
|
||||
updatedMovie.Rating = translator.nullInt64(input.Rating, "rating")
|
||||
updatedMovie.Rating = translator.ratingConversion(input.Rating, input.Rating100)
|
||||
updatedMovie.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
|
||||
updatedMovie.Director = translator.nullString(input.Director, "director")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
|
@ -54,78 +53,85 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
|
|||
// Populate a new performer from the input
|
||||
currentTime := time.Now()
|
||||
newPerformer := models.Performer{
|
||||
Name: input.Name,
|
||||
Checksum: checksum,
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
CreatedAt: currentTime,
|
||||
UpdatedAt: currentTime,
|
||||
}
|
||||
newPerformer.Name = sql.NullString{String: input.Name, Valid: true}
|
||||
if input.URL != nil {
|
||||
newPerformer.URL = sql.NullString{String: *input.URL, Valid: true}
|
||||
newPerformer.URL = *input.URL
|
||||
}
|
||||
if input.Gender != nil {
|
||||
newPerformer.Gender = sql.NullString{String: input.Gender.String(), Valid: true}
|
||||
newPerformer.Gender = *input.Gender
|
||||
}
|
||||
if input.Birthdate != nil {
|
||||
newPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
|
||||
d := models.NewDate(*input.Birthdate)
|
||||
newPerformer.Birthdate = &d
|
||||
}
|
||||
if input.Ethnicity != nil {
|
||||
newPerformer.Ethnicity = sql.NullString{String: *input.Ethnicity, Valid: true}
|
||||
newPerformer.Ethnicity = *input.Ethnicity
|
||||
}
|
||||
if input.Country != nil {
|
||||
newPerformer.Country = sql.NullString{String: *input.Country, Valid: true}
|
||||
newPerformer.Country = *input.Country
|
||||
}
|
||||
if input.EyeColor != nil {
|
||||
newPerformer.EyeColor = sql.NullString{String: *input.EyeColor, Valid: true}
|
||||
newPerformer.EyeColor = *input.EyeColor
|
||||
}
|
||||
if input.Height != nil {
|
||||
newPerformer.Height = sql.NullString{String: *input.Height, Valid: true}
|
||||
// prefer height_cm over height
|
||||
if input.HeightCm != nil {
|
||||
newPerformer.Height = input.HeightCm
|
||||
} else if input.Height != nil {
|
||||
h, err := strconv.Atoi(*input.Height)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid height: %s", *input.Height)
|
||||
}
|
||||
newPerformer.Height = &h
|
||||
}
|
||||
if input.Measurements != nil {
|
||||
newPerformer.Measurements = sql.NullString{String: *input.Measurements, Valid: true}
|
||||
newPerformer.Measurements = *input.Measurements
|
||||
}
|
||||
if input.FakeTits != nil {
|
||||
newPerformer.FakeTits = sql.NullString{String: *input.FakeTits, Valid: true}
|
||||
newPerformer.FakeTits = *input.FakeTits
|
||||
}
|
||||
if input.CareerLength != nil {
|
||||
newPerformer.CareerLength = sql.NullString{String: *input.CareerLength, Valid: true}
|
||||
newPerformer.CareerLength = *input.CareerLength
|
||||
}
|
||||
if input.Tattoos != nil {
|
||||
newPerformer.Tattoos = sql.NullString{String: *input.Tattoos, Valid: true}
|
||||
newPerformer.Tattoos = *input.Tattoos
|
||||
}
|
||||
if input.Piercings != nil {
|
||||
newPerformer.Piercings = sql.NullString{String: *input.Piercings, Valid: true}
|
||||
newPerformer.Piercings = *input.Piercings
|
||||
}
|
||||
if input.Aliases != nil {
|
||||
newPerformer.Aliases = sql.NullString{String: *input.Aliases, Valid: true}
|
||||
newPerformer.Aliases = *input.Aliases
|
||||
}
|
||||
if input.Twitter != nil {
|
||||
newPerformer.Twitter = sql.NullString{String: *input.Twitter, Valid: true}
|
||||
newPerformer.Twitter = *input.Twitter
|
||||
}
|
||||
if input.Instagram != nil {
|
||||
newPerformer.Instagram = sql.NullString{String: *input.Instagram, Valid: true}
|
||||
newPerformer.Instagram = *input.Instagram
|
||||
}
|
||||
if input.Favorite != nil {
|
||||
newPerformer.Favorite = sql.NullBool{Bool: *input.Favorite, Valid: true}
|
||||
} else {
|
||||
newPerformer.Favorite = sql.NullBool{Bool: false, Valid: true}
|
||||
newPerformer.Favorite = *input.Favorite
|
||||
}
|
||||
if input.Rating != nil {
|
||||
newPerformer.Rating = sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
|
||||
} else {
|
||||
newPerformer.Rating = sql.NullInt64{Valid: false}
|
||||
if input.Rating100 != nil {
|
||||
newPerformer.Rating = input.Rating100
|
||||
} else if input.Rating != nil {
|
||||
rating := models.Rating5To100(*input.Rating)
|
||||
newPerformer.Rating = &rating
|
||||
}
|
||||
if input.Details != nil {
|
||||
newPerformer.Details = sql.NullString{String: *input.Details, Valid: true}
|
||||
newPerformer.Details = *input.Details
|
||||
}
|
||||
if input.DeathDate != nil {
|
||||
newPerformer.DeathDate = models.SQLiteDate{String: *input.DeathDate, Valid: true}
|
||||
d := models.NewDate(*input.DeathDate)
|
||||
newPerformer.DeathDate = &d
|
||||
}
|
||||
if input.HairColor != nil {
|
||||
newPerformer.HairColor = sql.NullString{String: *input.HairColor, Valid: true}
|
||||
newPerformer.HairColor = *input.HairColor
|
||||
}
|
||||
if input.Weight != nil {
|
||||
weight := int64(*input.Weight)
|
||||
newPerformer.Weight = sql.NullInt64{Int64: weight, Valid: true}
|
||||
newPerformer.Weight = input.Weight
|
||||
}
|
||||
if input.IgnoreAutoTag != nil {
|
||||
newPerformer.IgnoreAutoTag = *input.IgnoreAutoTag
|
||||
|
|
@ -138,24 +144,23 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
|
|||
}
|
||||
|
||||
// Start the transaction and save the performer
|
||||
var performer *models.Performer
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Performer
|
||||
|
||||
performer, err = qb.Create(ctx, newPerformer)
|
||||
err = qb.Create(ctx, &newPerformer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(input.TagIds) > 0 {
|
||||
if err := r.updatePerformerTags(ctx, performer.ID, input.TagIds); err != nil {
|
||||
if err := r.updatePerformerTags(ctx, newPerformer.ID, input.TagIds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update image table
|
||||
if len(imageData) > 0 {
|
||||
if err := qb.UpdateImage(ctx, performer.ID, imageData); err != nil {
|
||||
if err := qb.UpdateImage(ctx, newPerformer.ID, imageData); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -163,7 +168,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
|
|||
// Save the stash_ids
|
||||
if input.StashIds != nil {
|
||||
stashIDJoins := stashIDPtrSliceToSlice(input.StashIds)
|
||||
if err := qb.UpdateStashIDs(ctx, performer.ID, stashIDJoins); err != nil {
|
||||
if err := qb.UpdateStashIDs(ctx, newPerformer.ID, stashIDJoins); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -173,17 +178,14 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
|
|||
return nil, err
|
||||
}
|
||||
|
||||
r.hookExecutor.ExecutePostHooks(ctx, performer.ID, plugin.PerformerCreatePost, input, nil)
|
||||
return r.getPerformer(ctx, performer.ID)
|
||||
r.hookExecutor.ExecutePostHooks(ctx, newPerformer.ID, plugin.PerformerCreatePost, input, nil)
|
||||
return r.getPerformer(ctx, newPerformer.ID)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerUpdateInput) (*models.Performer, error) {
|
||||
// Populate performer from the input
|
||||
performerID, _ := strconv.Atoi(input.ID)
|
||||
updatedPerformer := models.PerformerPartial{
|
||||
ID: performerID,
|
||||
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
|
||||
}
|
||||
updatedPerformer := models.NewPerformerPartial()
|
||||
|
||||
translator := changesetTranslator{
|
||||
inputMap: getUpdateInputMap(ctx),
|
||||
|
|
@ -203,54 +205,62 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU
|
|||
// generate checksum from performer name rather than image
|
||||
checksum := md5.FromString(*input.Name)
|
||||
|
||||
updatedPerformer.Name = &sql.NullString{String: *input.Name, Valid: true}
|
||||
updatedPerformer.Checksum = &checksum
|
||||
updatedPerformer.Name = models.NewOptionalString(*input.Name)
|
||||
updatedPerformer.Checksum = models.NewOptionalString(checksum)
|
||||
}
|
||||
|
||||
updatedPerformer.URL = translator.nullString(input.URL, "url")
|
||||
updatedPerformer.URL = translator.optionalString(input.URL, "url")
|
||||
|
||||
if translator.hasField("gender") {
|
||||
if input.Gender != nil {
|
||||
updatedPerformer.Gender = &sql.NullString{String: input.Gender.String(), Valid: true}
|
||||
updatedPerformer.Gender = models.NewOptionalString(input.Gender.String())
|
||||
} else {
|
||||
updatedPerformer.Gender = &sql.NullString{String: "", Valid: false}
|
||||
updatedPerformer.Gender = models.NewOptionalStringPtr(nil)
|
||||
}
|
||||
}
|
||||
|
||||
updatedPerformer.Birthdate = translator.sqliteDate(input.Birthdate, "birthdate")
|
||||
updatedPerformer.Country = translator.nullString(input.Country, "country")
|
||||
updatedPerformer.EyeColor = translator.nullString(input.EyeColor, "eye_color")
|
||||
updatedPerformer.Measurements = translator.nullString(input.Measurements, "measurements")
|
||||
updatedPerformer.Height = translator.nullString(input.Height, "height")
|
||||
updatedPerformer.Ethnicity = translator.nullString(input.Ethnicity, "ethnicity")
|
||||
updatedPerformer.FakeTits = translator.nullString(input.FakeTits, "fake_tits")
|
||||
updatedPerformer.CareerLength = translator.nullString(input.CareerLength, "career_length")
|
||||
updatedPerformer.Tattoos = translator.nullString(input.Tattoos, "tattoos")
|
||||
updatedPerformer.Piercings = translator.nullString(input.Piercings, "piercings")
|
||||
updatedPerformer.Aliases = translator.nullString(input.Aliases, "aliases")
|
||||
updatedPerformer.Twitter = translator.nullString(input.Twitter, "twitter")
|
||||
updatedPerformer.Instagram = translator.nullString(input.Instagram, "instagram")
|
||||
updatedPerformer.Favorite = translator.nullBool(input.Favorite, "favorite")
|
||||
updatedPerformer.Rating = translator.nullInt64(input.Rating, "rating")
|
||||
updatedPerformer.Details = translator.nullString(input.Details, "details")
|
||||
updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date")
|
||||
updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color")
|
||||
updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight")
|
||||
updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag
|
||||
updatedPerformer.Birthdate = translator.optionalDate(input.Birthdate, "birthdate")
|
||||
updatedPerformer.Country = translator.optionalString(input.Country, "country")
|
||||
updatedPerformer.EyeColor = translator.optionalString(input.EyeColor, "eye_color")
|
||||
updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements")
|
||||
// prefer height_cm over height
|
||||
if translator.hasField("height_cm") {
|
||||
updatedPerformer.Height = translator.optionalInt(input.HeightCm, "height_cm")
|
||||
} else if translator.hasField("height") {
|
||||
updatedPerformer.Height, err = translator.optionalIntFromString(input.Height, "height")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
|
||||
updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
|
||||
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
||||
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
||||
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
||||
updatedPerformer.Aliases = translator.optionalString(input.Aliases, "aliases")
|
||||
updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter")
|
||||
updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram")
|
||||
updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
||||
updatedPerformer.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100)
|
||||
updatedPerformer.Details = translator.optionalString(input.Details, "details")
|
||||
updatedPerformer.DeathDate = translator.optionalDate(input.DeathDate, "death_date")
|
||||
updatedPerformer.HairColor = translator.optionalString(input.HairColor, "hair_color")
|
||||
updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight")
|
||||
updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
||||
|
||||
// Start the transaction and save the p
|
||||
var p *models.Performer
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Performer
|
||||
|
||||
// need to get existing performer
|
||||
existing, err := qb.Find(ctx, updatedPerformer.ID)
|
||||
existing, err := qb.Find(ctx, performerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing == nil {
|
||||
return fmt.Errorf("performer with id %d not found", updatedPerformer.ID)
|
||||
return fmt.Errorf("performer with id %d not found", performerID)
|
||||
}
|
||||
|
||||
if err := performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate); err != nil {
|
||||
|
|
@ -259,26 +269,26 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU
|
|||
}
|
||||
}
|
||||
|
||||
p, err = qb.Update(ctx, updatedPerformer)
|
||||
_, err = qb.UpdatePartial(ctx, performerID, updatedPerformer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the tags
|
||||
if translator.hasField("tag_ids") {
|
||||
if err := r.updatePerformerTags(ctx, p.ID, input.TagIds); err != nil {
|
||||
if err := r.updatePerformerTags(ctx, performerID, input.TagIds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update image table
|
||||
if len(imageData) > 0 {
|
||||
if err := qb.UpdateImage(ctx, p.ID, imageData); err != nil {
|
||||
if err := qb.UpdateImage(ctx, performerID, imageData); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if imageIncluded {
|
||||
// must be unsetting
|
||||
if err := qb.DestroyImage(ctx, p.ID); err != nil {
|
||||
if err := qb.DestroyImage(ctx, performerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -296,8 +306,8 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU
|
|||
return nil, err
|
||||
}
|
||||
|
||||
r.hookExecutor.ExecutePostHooks(ctx, p.ID, plugin.PerformerUpdatePost, input, translator.getFields())
|
||||
return r.getPerformer(ctx, p.ID)
|
||||
r.hookExecutor.ExecutePostHooks(ctx, performerID, plugin.PerformerUpdatePost, input, translator.getFields())
|
||||
return r.getPerformer(ctx, performerID)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) updatePerformerTags(ctx context.Context, performerID int, tagsIDs []string) error {
|
||||
|
|
@ -315,43 +325,48 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
|||
}
|
||||
|
||||
// Populate performer from the input
|
||||
updatedTime := time.Now()
|
||||
|
||||
translator := changesetTranslator{
|
||||
inputMap: getUpdateInputMap(ctx),
|
||||
}
|
||||
|
||||
updatedPerformer := models.PerformerPartial{
|
||||
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
|
||||
updatedPerformer := models.NewPerformerPartial()
|
||||
|
||||
updatedPerformer.URL = translator.optionalString(input.URL, "url")
|
||||
updatedPerformer.Birthdate = translator.optionalDate(input.Birthdate, "birthdate")
|
||||
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
|
||||
updatedPerformer.Country = translator.optionalString(input.Country, "country")
|
||||
updatedPerformer.EyeColor = translator.optionalString(input.EyeColor, "eye_color")
|
||||
// prefer height_cm over height
|
||||
if translator.hasField("height_cm") {
|
||||
updatedPerformer.Height = translator.optionalInt(input.HeightCm, "height_cm")
|
||||
} else if translator.hasField("height") {
|
||||
updatedPerformer.Height, err = translator.optionalIntFromString(input.Height, "height")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
updatedPerformer.URL = translator.nullString(input.URL, "url")
|
||||
updatedPerformer.Birthdate = translator.sqliteDate(input.Birthdate, "birthdate")
|
||||
updatedPerformer.Ethnicity = translator.nullString(input.Ethnicity, "ethnicity")
|
||||
updatedPerformer.Country = translator.nullString(input.Country, "country")
|
||||
updatedPerformer.EyeColor = translator.nullString(input.EyeColor, "eye_color")
|
||||
updatedPerformer.Height = translator.nullString(input.Height, "height")
|
||||
updatedPerformer.Measurements = translator.nullString(input.Measurements, "measurements")
|
||||
updatedPerformer.FakeTits = translator.nullString(input.FakeTits, "fake_tits")
|
||||
updatedPerformer.CareerLength = translator.nullString(input.CareerLength, "career_length")
|
||||
updatedPerformer.Tattoos = translator.nullString(input.Tattoos, "tattoos")
|
||||
updatedPerformer.Piercings = translator.nullString(input.Piercings, "piercings")
|
||||
updatedPerformer.Aliases = translator.nullString(input.Aliases, "aliases")
|
||||
updatedPerformer.Twitter = translator.nullString(input.Twitter, "twitter")
|
||||
updatedPerformer.Instagram = translator.nullString(input.Instagram, "instagram")
|
||||
updatedPerformer.Favorite = translator.nullBool(input.Favorite, "favorite")
|
||||
updatedPerformer.Rating = translator.nullInt64(input.Rating, "rating")
|
||||
updatedPerformer.Details = translator.nullString(input.Details, "details")
|
||||
updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date")
|
||||
updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color")
|
||||
updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight")
|
||||
updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag
|
||||
updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements")
|
||||
updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
|
||||
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
||||
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
||||
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
||||
updatedPerformer.Aliases = translator.optionalString(input.Aliases, "aliases")
|
||||
updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter")
|
||||
updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram")
|
||||
updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
||||
updatedPerformer.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100)
|
||||
updatedPerformer.Details = translator.optionalString(input.Details, "details")
|
||||
updatedPerformer.DeathDate = translator.optionalDate(input.DeathDate, "death_date")
|
||||
updatedPerformer.HairColor = translator.optionalString(input.HairColor, "hair_color")
|
||||
updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight")
|
||||
updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
||||
|
||||
if translator.hasField("gender") {
|
||||
if input.Gender != nil {
|
||||
updatedPerformer.Gender = &sql.NullString{String: input.Gender.String(), Valid: true}
|
||||
updatedPerformer.Gender = models.NewOptionalString(input.Gender.String())
|
||||
} else {
|
||||
updatedPerformer.Gender = &sql.NullString{String: "", Valid: false}
|
||||
updatedPerformer.Gender = models.NewOptionalStringPtr(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -378,7 +393,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
|||
return err
|
||||
}
|
||||
|
||||
performer, err := qb.Update(ctx, updatedPerformer)
|
||||
performer, err := qb.UpdatePartial(ctx, performerID, updatedPerformer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
|
@ -30,6 +31,79 @@ func (r *mutationResolver) getScene(ctx context.Context, id int) (ret *models.Sc
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SceneCreate(ctx context.Context, input SceneCreateInput) (ret *models.Scene, err error) {
|
||||
translator := changesetTranslator{
|
||||
inputMap: getUpdateInputMap(ctx),
|
||||
}
|
||||
|
||||
performerIDs, err := stringslice.StringSliceToIntSlice(input.PerformerIds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting performer ids: %w", err)
|
||||
}
|
||||
tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||
}
|
||||
galleryIDs, err := stringslice.StringSliceToIntSlice(input.GalleryIds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
||||
}
|
||||
|
||||
moviesScenes, err := models.MoviesScenesFromInput(input.Movies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting movies scenes: %w", err)
|
||||
}
|
||||
|
||||
fileIDsInt, err := stringslice.StringSliceToIntSlice(input.FileIds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting file ids: %w", err)
|
||||
}
|
||||
|
||||
fileIDs := make([]file.ID, len(fileIDsInt))
|
||||
for i, v := range fileIDsInt {
|
||||
fileIDs[i] = file.ID(v)
|
||||
}
|
||||
|
||||
newScene := models.Scene{
|
||||
Title: translator.string(input.Title, "title"),
|
||||
Code: translator.string(input.Code, "code"),
|
||||
Details: translator.string(input.Details, "details"),
|
||||
Director: translator.string(input.Director, "director"),
|
||||
URL: translator.string(input.URL, "url"),
|
||||
Date: translator.datePtr(input.Date, "date"),
|
||||
Rating: translator.ratingConversionInt(input.Rating, input.Rating100),
|
||||
Organized: translator.bool(input.Organized, "organized"),
|
||||
PerformerIDs: models.NewRelatedIDs(performerIDs),
|
||||
TagIDs: models.NewRelatedIDs(tagIDs),
|
||||
GalleryIDs: models.NewRelatedIDs(galleryIDs),
|
||||
Movies: models.NewRelatedMovies(moviesScenes),
|
||||
StashIDs: models.NewRelatedStashIDs(stashIDPtrSliceToSlice(input.StashIds)),
|
||||
}
|
||||
|
||||
newScene.StudioID, err = translator.intPtrFromString(input.StudioID, "studio_id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||
}
|
||||
|
||||
var coverImageData []byte
|
||||
if input.CoverImage != nil && *input.CoverImage != "" {
|
||||
var err error
|
||||
coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.Resolver.sceneService.Create(ctx, &newScene, fileIDs, coverImageData)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUpdateInput) (ret *models.Scene, err error) {
|
||||
translator := changesetTranslator{
|
||||
inputMap: getUpdateInputMap(ctx),
|
||||
|
|
@ -90,32 +164,19 @@ func (r *mutationResolver) ScenesUpdate(ctx context.Context, input []*models.Sce
|
|||
return newRet, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUpdateInput, translator changesetTranslator) (*models.Scene, error) {
|
||||
// Populate scene from the input
|
||||
sceneID, err := strconv.Atoi(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qb := r.repository.Scene
|
||||
|
||||
s, err := qb.Find(ctx, sceneID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("scene with id %d not found", sceneID)
|
||||
}
|
||||
|
||||
var coverImageData []byte
|
||||
|
||||
func scenePartialFromInput(input models.SceneUpdateInput, translator changesetTranslator) (*models.ScenePartial, error) {
|
||||
updatedScene := models.NewScenePartial()
|
||||
updatedScene.Title = translator.optionalString(input.Title, "title")
|
||||
updatedScene.Code = translator.optionalString(input.Code, "code")
|
||||
updatedScene.Details = translator.optionalString(input.Details, "details")
|
||||
updatedScene.Director = translator.optionalString(input.Director, "director")
|
||||
updatedScene.URL = translator.optionalString(input.URL, "url")
|
||||
updatedScene.Date = translator.optionalDate(input.Date, "date")
|
||||
updatedScene.Rating = translator.optionalInt(input.Rating, "rating")
|
||||
updatedScene.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100)
|
||||
updatedScene.OCounter = translator.optionalInt(input.OCounter, "o_counter")
|
||||
updatedScene.PlayCount = translator.optionalInt(input.PlayCount, "play_count")
|
||||
updatedScene.PlayDuration = translator.optionalFloat64(input.PlayDuration, "play_duration")
|
||||
var err error
|
||||
updatedScene.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||
|
|
@ -131,36 +192,6 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp
|
|||
|
||||
converted := file.ID(primaryFileID)
|
||||
updatedScene.PrimaryFileID = &converted
|
||||
|
||||
// if file hash has changed, we should migrate generated files
|
||||
// after commit
|
||||
if err := s.LoadFiles(ctx, r.repository.Scene); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure that new primary file is associated with scene
|
||||
var f *file.VideoFile
|
||||
for _, ff := range s.Files.List() {
|
||||
if ff.ID == converted {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
return nil, fmt.Errorf("file with id %d not associated with scene", converted)
|
||||
}
|
||||
|
||||
fileNamingAlgorithm := config.GetInstance().GetVideoFileNamingAlgorithm()
|
||||
oldHash := scene.GetHash(s.Files.Primary(), fileNamingAlgorithm)
|
||||
newHash := scene.GetHash(f, fileNamingAlgorithm)
|
||||
|
||||
if oldHash != "" && newHash != "" && oldHash != newHash {
|
||||
// perform migration after commit
|
||||
txn.AddPostCommitHook(ctx, func(ctx context.Context) error {
|
||||
scene.MigrateHash(manager.GetInstance().Paths, oldHash, newHash)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if translator.hasField("performer_ids") {
|
||||
|
|
@ -200,39 +231,107 @@ func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUp
|
|||
}
|
||||
}
|
||||
|
||||
return &updatedScene, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUpdateInput, translator changesetTranslator) (*models.Scene, error) {
|
||||
// Populate scene from the input
|
||||
sceneID, err := strconv.Atoi(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qb := r.repository.Scene
|
||||
|
||||
s, err := qb.Find(ctx, sceneID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("scene with id %d not found", sceneID)
|
||||
}
|
||||
|
||||
var coverImageData []byte
|
||||
|
||||
updatedScene, err := scenePartialFromInput(input, translator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure that title is set where scene has no file
|
||||
if updatedScene.Title.Set && updatedScene.Title.Value == "" {
|
||||
if err := s.LoadFiles(ctx, r.repository.Scene); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(s.Files.List()) == 0 {
|
||||
return nil, errors.New("title must be set if scene has no files")
|
||||
}
|
||||
}
|
||||
|
||||
if updatedScene.PrimaryFileID != nil {
|
||||
newPrimaryFileID := *updatedScene.PrimaryFileID
|
||||
|
||||
// if file hash has changed, we should migrate generated files
|
||||
// after commit
|
||||
if err := s.LoadFiles(ctx, r.repository.Scene); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure that new primary file is associated with scene
|
||||
var f *file.VideoFile
|
||||
for _, ff := range s.Files.List() {
|
||||
if ff.ID == newPrimaryFileID {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
|
||||
if f == nil {
|
||||
return nil, fmt.Errorf("file with id %d not associated with scene", newPrimaryFileID)
|
||||
}
|
||||
}
|
||||
|
||||
if input.CoverImage != nil && *input.CoverImage != "" {
|
||||
var err error
|
||||
coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update the cover after updating the scene
|
||||
}
|
||||
|
||||
s, err = qb.UpdatePartial(ctx, sceneID, updatedScene)
|
||||
s, err = qb.UpdatePartial(ctx, sceneID, *updatedScene)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update cover table
|
||||
if len(coverImageData) > 0 {
|
||||
if err := qb.UpdateCover(ctx, sceneID, coverImageData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// only update the cover image if provided and everything else was successful
|
||||
if coverImageData != nil {
|
||||
err = scene.SetScreenshot(manager.GetInstance().Paths, s.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()), coverImageData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.sceneUpdateCoverImage(ctx, s, coverImageData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) sceneUpdateCoverImage(ctx context.Context, s *models.Scene, coverImageData []byte) error {
|
||||
if len(coverImageData) > 0 {
|
||||
qb := r.repository.Scene
|
||||
|
||||
// update cover table
|
||||
if err := qb.UpdateCover(ctx, s.ID, coverImageData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.Path != "" {
|
||||
// update the file-based screenshot after commit
|
||||
txn.AddPostCommitHook(ctx, func(ctx context.Context) error {
|
||||
return scene.SetScreenshot(manager.GetInstance().Paths, s.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()), coverImageData)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneUpdateInput) ([]*models.Scene, error) {
|
||||
sceneIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
|
||||
if err != nil {
|
||||
|
|
@ -246,10 +345,12 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneU
|
|||
|
||||
updatedScene := models.NewScenePartial()
|
||||
updatedScene.Title = translator.optionalString(input.Title, "title")
|
||||
updatedScene.Code = translator.optionalString(input.Code, "code")
|
||||
updatedScene.Details = translator.optionalString(input.Details, "details")
|
||||
updatedScene.Director = translator.optionalString(input.Director, "director")
|
||||
updatedScene.URL = translator.optionalString(input.URL, "url")
|
||||
updatedScene.Date = translator.optionalDate(input.Date, "date")
|
||||
updatedScene.Rating = translator.optionalInt(input.Rating, "rating")
|
||||
updatedScene.Rating = translator.ratingConversionOptional(input.Rating, input.Rating100)
|
||||
updatedScene.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||
|
|
@ -482,6 +583,84 @@ func (r *mutationResolver) ScenesDestroy(ctx context.Context, input models.Scene
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SceneAssignFile(ctx context.Context, input AssignSceneFileInput) (bool, error) {
|
||||
sceneID, err := strconv.Atoi(input.SceneID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("converting scene ID: %w", err)
|
||||
}
|
||||
|
||||
fileIDInt, err := strconv.Atoi(input.FileID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("converting file ID: %w", err)
|
||||
}
|
||||
|
||||
fileID := file.ID(fileIDInt)
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
return r.Resolver.sceneService.AssignFile(ctx, sceneID, fileID)
|
||||
}); err != nil {
|
||||
return false, fmt.Errorf("assigning file to scene: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SceneMerge(ctx context.Context, input SceneMergeInput) (*models.Scene, error) {
|
||||
srcIDs, err := stringslice.StringSliceToIntSlice(input.Source)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting source IDs: %w", err)
|
||||
}
|
||||
|
||||
destID, err := strconv.Atoi(input.Destination)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("converting destination ID %s: %w", input.Destination, err)
|
||||
}
|
||||
|
||||
var values *models.ScenePartial
|
||||
if input.Values != nil {
|
||||
translator := changesetTranslator{
|
||||
inputMap: getNamedUpdateInputMap(ctx, "input.values"),
|
||||
}
|
||||
|
||||
values, err = scenePartialFromInput(*input.Values, translator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
v := models.NewScenePartial()
|
||||
values = &v
|
||||
}
|
||||
|
||||
var coverImageData []byte
|
||||
|
||||
if input.Values.CoverImage != nil && *input.Values.CoverImage != "" {
|
||||
var err error
|
||||
coverImageData, err = utils.ProcessImageInput(ctx, *input.Values.CoverImage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var ret *models.Scene
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.Resolver.sceneService.Merge(ctx, srcIDs, destID, *values); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret, err = r.Resolver.repository.Scene.Find(ctx, destID)
|
||||
|
||||
if err == nil && ret != nil {
|
||||
err = r.sceneUpdateCoverImage(ctx, ret, coverImageData)
|
||||
}
|
||||
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) getSceneMarker(ctx context.Context, id int) (ret *models.SceneMarker, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.SceneMarker.Find(ctx, id)
|
||||
|
|
@ -679,6 +858,42 @@ func (r *mutationResolver) changeMarker(ctx context.Context, changeType int, cha
|
|||
return sceneMarker, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SceneSaveActivity(ctx context.Context, id string, resumeTime *float64, playDuration *float64) (ret bool, err error) {
|
||||
sceneID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Scene
|
||||
|
||||
ret, err = qb.SaveActivity(ctx, sceneID, resumeTime, playDuration)
|
||||
return err
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SceneIncrementPlayCount(ctx context.Context, id string) (ret int, err error) {
|
||||
sceneID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Scene
|
||||
|
||||
ret, err = qb.IncrementWatchCount(ctx, sceneID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) SceneIncrementO(ctx context.Context, id string) (ret int, err error) {
|
||||
sceneID, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input S
|
|||
}
|
||||
|
||||
var res *string
|
||||
err = r.withTxn(ctx, func(ctx context.Context) error {
|
||||
err = r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Scene
|
||||
scene, err := qb.Find(ctx, id)
|
||||
if err != nil {
|
||||
|
|
@ -82,7 +82,7 @@ func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, inp
|
|||
}
|
||||
|
||||
var res *string
|
||||
err = r.withTxn(ctx, func(ctx context.Context) error {
|
||||
err = r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Performer
|
||||
performer, err := qb.Find(ctx, id)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -58,11 +58,18 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input StudioCreateI
|
|||
newStudio.ParentID = sql.NullInt64{Int64: parentID, Valid: true}
|
||||
}
|
||||
|
||||
if input.Rating != nil {
|
||||
newStudio.Rating = sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
|
||||
} else {
|
||||
newStudio.Rating = sql.NullInt64{Valid: false}
|
||||
if input.Rating100 != nil {
|
||||
newStudio.Rating = sql.NullInt64{
|
||||
Int64: int64(*input.Rating100),
|
||||
Valid: true,
|
||||
}
|
||||
} else if input.Rating != nil {
|
||||
newStudio.Rating = sql.NullInt64{
|
||||
Int64: int64(models.Rating5To100(*input.Rating)),
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
if input.Details != nil {
|
||||
newStudio.Details = sql.NullString{String: *input.Details, Valid: true}
|
||||
}
|
||||
|
|
@ -150,7 +157,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input StudioUpdateI
|
|||
updatedStudio.URL = translator.nullString(input.URL, "url")
|
||||
updatedStudio.Details = translator.nullString(input.Details, "details")
|
||||
updatedStudio.ParentID = translator.nullInt64FromString(input.ParentID, "parent_id")
|
||||
updatedStudio.Rating = translator.nullInt64(input.Rating, "rating")
|
||||
updatedStudio.Rating = translator.ratingConversion(input.Rating, input.Rating100)
|
||||
updatedStudio.IgnoreAutoTag = input.IgnoreAutoTag
|
||||
|
||||
// Start the transaction and save the studio
|
||||
|
|
|
|||
|
|
@ -142,6 +142,8 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
|
|||
showStudioAsText := config.GetShowStudioAsText()
|
||||
css := config.GetCSS()
|
||||
cssEnabled := config.GetCSSEnabled()
|
||||
javascript := config.GetJavascript()
|
||||
javascriptEnabled := config.GetJavascriptEnabled()
|
||||
customLocales := config.GetCustomLocales()
|
||||
customLocalesEnabled := config.GetCustomLocalesEnabled()
|
||||
language := config.GetLanguage()
|
||||
|
|
@ -166,6 +168,8 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
|
|||
ContinuePlaylistDefault: &continuePlaylistDefault,
|
||||
CSS: &css,
|
||||
CSSEnabled: &cssEnabled,
|
||||
Javascript: &javascript,
|
||||
JavascriptEnabled: &javascriptEnabled,
|
||||
CustomLocales: &customLocales,
|
||||
CustomLocalesEnabled: &customLocalesEnabled,
|
||||
Language: &language,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func (r *queryResolver) FindGallery(ctx context.Context, id string) (ret *models
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Gallery.Find(ctx, idInt)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -24,7 +24,7 @@ func (r *queryResolver) FindGallery(ctx context.Context, id string) (ret *models
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindGalleries(ctx context.Context, galleryFilter *models.GalleryFilterType, filter *models.FindFilterType) (ret *FindGalleriesResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
galleries, total, err := r.repository.Gallery.Query(ctx, galleryFilter, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
func (r *queryResolver) FindImage(ctx context.Context, id *string, checksum *string) (*models.Image, error) {
|
||||
var image *models.Image
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Image
|
||||
var err error
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ func (r *queryResolver) FindImage(ctx context.Context, id *string, checksum *str
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindImages(ctx context.Context, imageFilter *models.ImageFilterType, imageIds []int, filter *models.FindFilterType) (ret *FindImagesResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Image
|
||||
|
||||
fields := graphql.CollectAllFields(ctx)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func (r *queryResolver) FindMovie(ctx context.Context, id string) (ret *models.M
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Movie.Find(ctx, idInt)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -24,7 +24,7 @@ func (r *queryResolver) FindMovie(ctx context.Context, id string) (ret *models.M
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.MovieFilterType, filter *models.FindFilterType) (ret *FindMoviesResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
movies, total, err := r.repository.Movie.Query(ctx, movieFilter, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -44,7 +44,7 @@ func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.Movi
|
|||
}
|
||||
|
||||
func (r *queryResolver) AllMovies(ctx context.Context) (ret []*models.Movie, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Movie.All(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func (r *queryResolver) FindPerformer(ctx context.Context, id string) (ret *mode
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Performer.Find(ctx, idInt)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -24,7 +24,7 @@ func (r *queryResolver) FindPerformer(ctx context.Context, id string) (ret *mode
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindPerformers(ctx context.Context, performerFilter *models.PerformerFilterType, filter *models.FindFilterType) (ret *FindPerformersResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
performers, total, err := r.repository.Performer.Query(ctx, performerFilter, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -43,7 +43,7 @@ func (r *queryResolver) FindPerformers(ctx context.Context, performerFilter *mod
|
|||
}
|
||||
|
||||
func (r *queryResolver) AllPerformers(ctx context.Context) (ret []*models.Performer, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Performer.All(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func (r *queryResolver) FindSavedFilter(ctx context.Context, id string) (ret *mo
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.SavedFilter.Find(ctx, idInt)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -23,7 +23,7 @@ func (r *queryResolver) FindSavedFilter(ctx context.Context, id string) (ret *mo
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindSavedFilters(ctx context.Context, mode *models.FilterMode) (ret []*models.SavedFilter, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
if mode != nil {
|
||||
ret, err = r.repository.SavedFilter.FindByMode(ctx, *mode)
|
||||
} else {
|
||||
|
|
@ -37,7 +37,7 @@ func (r *queryResolver) FindSavedFilters(ctx context.Context, mode *models.Filte
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindDefaultFilter(ctx context.Context, mode models.FilterMode) (ret *models.SavedFilter, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.SavedFilter.FindDefault(ctx, mode)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
func (r *queryResolver) FindScene(ctx context.Context, id *string, checksum *string) (*models.Scene, error) {
|
||||
var scene *models.Scene
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Scene
|
||||
var err error
|
||||
if id != nil {
|
||||
|
|
@ -43,7 +43,7 @@ func (r *queryResolver) FindScene(ctx context.Context, id *string, checksum *str
|
|||
func (r *queryResolver) FindSceneByHash(ctx context.Context, input SceneHashInput) (*models.Scene, error) {
|
||||
var scene *models.Scene
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
qb := r.repository.Scene
|
||||
if input.Checksum != nil {
|
||||
scenes, err := qb.FindByChecksum(ctx, *input.Checksum)
|
||||
|
|
@ -74,7 +74,7 @@ func (r *queryResolver) FindSceneByHash(ctx context.Context, input SceneHashInpu
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindScenes(ctx context.Context, sceneFilter *models.SceneFilterType, sceneIDs []int, filter *models.FindFilterType) (ret *FindScenesResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var scenes []*models.Scene
|
||||
var err error
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ func (r *queryResolver) FindScenes(ctx context.Context, sceneFilter *models.Scen
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *models.FindFilterType) (ret *FindScenesResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
|
||||
sceneFilter := &models.SceneFilterType{}
|
||||
|
||||
|
|
@ -192,7 +192,7 @@ func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *model
|
|||
func (r *queryResolver) ParseSceneFilenames(ctx context.Context, filter *models.FindFilterType, config manager.SceneParserInput) (ret *SceneParserResultType, err error) {
|
||||
parser := manager.NewSceneFilenameParser(filter, config)
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
result, count, err := parser.Parse(ctx, manager.SceneFilenameParserRepository{
|
||||
Scene: r.repository.Scene,
|
||||
Performer: r.repository.Performer,
|
||||
|
|
@ -223,7 +223,7 @@ func (r *queryResolver) FindDuplicateScenes(ctx context.Context, distance *int)
|
|||
if distance != nil {
|
||||
dist = *distance
|
||||
}
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Scene.FindDuplicates(ctx, dist)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func (r *queryResolver) FindSceneMarkers(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, filter *models.FindFilterType) (ret *FindSceneMarkersResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
sceneMarkers, total, err := r.repository.SceneMarker.Query(ctx, sceneMarkerFilter, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func (r *queryResolver) FindStudio(ctx context.Context, id string) (ret *models.
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
ret, err = r.repository.Studio.Find(ctx, idInt)
|
||||
return err
|
||||
|
|
@ -25,7 +25,7 @@ func (r *queryResolver) FindStudio(ctx context.Context, id string) (ret *models.
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindStudios(ctx context.Context, studioFilter *models.StudioFilterType, filter *models.FindFilterType) (ret *FindStudiosResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
studios, total, err := r.repository.Studio.Query(ctx, studioFilter, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -45,7 +45,7 @@ func (r *queryResolver) FindStudios(ctx context.Context, studioFilter *models.St
|
|||
}
|
||||
|
||||
func (r *queryResolver) AllStudios(ctx context.Context) (ret []*models.Studio, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Studio.All(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func (r *queryResolver) FindTag(ctx context.Context, id string) (ret *models.Tag
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Tag.Find(ctx, idInt)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
@ -24,7 +24,7 @@ func (r *queryResolver) FindTag(ctx context.Context, id string) (ret *models.Tag
|
|||
}
|
||||
|
||||
func (r *queryResolver) FindTags(ctx context.Context, tagFilter *models.TagFilterType, filter *models.FindFilterType) (ret *FindTagsResultType, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
tags, total, err := r.repository.Tag.Query(ctx, tagFilter, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -44,7 +44,7 @@ func (r *queryResolver) FindTags(ctx context.Context, tagFilter *models.TagFilte
|
|||
}
|
||||
|
||||
func (r *queryResolver) AllTags(ctx context.Context) (ret []*models.Tag, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
ret, err = r.repository.Tag.All(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
func (r *queryResolver) SceneStreams(ctx context.Context, id *string) ([]*manager.SceneStreamEndpoint, error) {
|
||||
// find the scene
|
||||
var scene *models.Scene
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||
idInt, _ := strconv.Atoi(*id)
|
||||
var err error
|
||||
scene, err = r.repository.Scene.Find(ctx, idInt)
|
||||
|
|
|
|||
35
internal/api/routes_custom.go
Normal file
35
internal/api/routes_custom.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stashapp/stash/internal/manager/config"
|
||||
)
|
||||
|
||||
type customRoutes struct {
|
||||
servedFolders config.URLMap
|
||||
}
|
||||
|
||||
func (rs customRoutes) Routes() chi.Router {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
r.URL.Path = strings.Replace(r.URL.Path, "/custom", "", 1)
|
||||
|
||||
// http.FileServer redirects to / if the path ends with index.html
|
||||
r.URL.Path = strings.TrimSuffix(r.URL.Path, "/index.html")
|
||||
|
||||
// map the path to the applicable filesystem location
|
||||
var dir string
|
||||
r.URL.Path, dir = rs.servedFolders.GetFilesystemLocation(r.URL.Path)
|
||||
if dir != "" {
|
||||
http.FileServer(http.Dir(dir)).ServeHTTP(w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ func (rs imageRoutes) ImageCtx(next http.Handler) http.Handler {
|
|||
imageID, _ := strconv.Atoi(imageIdentifierQueryParam)
|
||||
|
||||
var image *models.Image
|
||||
_ = txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
qb := rs.imageFinder
|
||||
if imageID == 0 {
|
||||
images, _ := qb.FindByChecksum(ctx, imageIdentifierQueryParam)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func (rs movieRoutes) FrontImage(w http.ResponseWriter, r *http.Request) {
|
|||
defaultParam := r.URL.Query().Get("default")
|
||||
var image []byte
|
||||
if defaultParam != "true" {
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
image, _ = rs.movieFinder.GetFrontImage(ctx, movie.ID)
|
||||
return nil
|
||||
})
|
||||
|
|
@ -67,7 +67,7 @@ func (rs movieRoutes) BackImage(w http.ResponseWriter, r *http.Request) {
|
|||
defaultParam := r.URL.Query().Get("default")
|
||||
var image []byte
|
||||
if defaultParam != "true" {
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
image, _ = rs.movieFinder.GetBackImage(ctx, movie.ID)
|
||||
return nil
|
||||
})
|
||||
|
|
@ -97,7 +97,7 @@ func (rs movieRoutes) MovieCtx(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
var movie *models.Movie
|
||||
_ = txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
movie, _ = rs.movieFinder.Find(ctx, movieID)
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var image []byte
|
||||
if defaultParam != "true" {
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
image, _ = rs.performerFinder.GetImage(ctx, performer.ID)
|
||||
return nil
|
||||
})
|
||||
|
|
@ -54,7 +54,7 @@ func (rs performerRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if len(image) == 0 || defaultParam == "true" {
|
||||
image, _ = getRandomPerformerImageUsingName(performer.Name.String, performer.Gender.String, config.GetInstance().GetCustomPerformerImageLocation())
|
||||
image, _ = getRandomPerformerImageUsingName(performer.Name, performer.Gender, config.GetInstance().GetCustomPerformerImageLocation())
|
||||
}
|
||||
|
||||
if err := utils.ServeImage(image, w, r); err != nil {
|
||||
|
|
@ -71,7 +71,7 @@ func (rs performerRoutes) PerformerCtx(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
var performer *models.Performer
|
||||
_ = txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
var err error
|
||||
performer, err = rs.performerFinder.Find(ctx, performerID)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ func (rs sceneRoutes) getChapterVttTitle(ctx context.Context, marker *models.Sce
|
|||
}
|
||||
|
||||
var title string
|
||||
if err := txn.WithTxn(ctx, rs.txnManager, func(ctx context.Context) error {
|
||||
if err := txn.WithReadTxn(ctx, rs.txnManager, func(ctx context.Context) error {
|
||||
qb := rs.tagFinder
|
||||
primaryTag, err := qb.Find(ctx, marker.PrimaryTagID)
|
||||
if err != nil {
|
||||
|
|
@ -293,7 +293,7 @@ func (rs sceneRoutes) getChapterVttTitle(ctx context.Context, marker *models.Sce
|
|||
func (rs sceneRoutes) ChapterVtt(w http.ResponseWriter, r *http.Request) {
|
||||
scene := r.Context().Value(sceneKey).(*models.Scene)
|
||||
var sceneMarkers []*models.SceneMarker
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
var err error
|
||||
sceneMarkers, err = rs.sceneMarkerFinder.FindBySceneID(ctx, scene.ID)
|
||||
return err
|
||||
|
|
@ -349,7 +349,7 @@ func (rs sceneRoutes) Caption(w http.ResponseWriter, r *http.Request, lang strin
|
|||
s := r.Context().Value(sceneKey).(*models.Scene)
|
||||
|
||||
var captions []*models.VideoCaption
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
var err error
|
||||
primaryFile := s.Files.Primary()
|
||||
if primaryFile == nil {
|
||||
|
|
@ -377,14 +377,14 @@ func (rs sceneRoutes) Caption(w http.ResponseWriter, r *http.Request, lang strin
|
|||
sub, err := video.ReadSubs(caption.Path(s.Path))
|
||||
if err != nil {
|
||||
logger.Warnf("error while reading subs: %v", err)
|
||||
http.Error(w, readTxnErr.Error(), http.StatusInternalServerError)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
err = sub.WriteToWebVTT(&b)
|
||||
if err != nil {
|
||||
http.Error(w, readTxnErr.Error(), http.StatusInternalServerError)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -423,7 +423,7 @@ func (rs sceneRoutes) SceneMarkerStream(w http.ResponseWriter, r *http.Request)
|
|||
scene := r.Context().Value(sceneKey).(*models.Scene)
|
||||
sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId"))
|
||||
var sceneMarker *models.SceneMarker
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
var err error
|
||||
sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID)
|
||||
return err
|
||||
|
|
@ -450,7 +450,7 @@ func (rs sceneRoutes) SceneMarkerPreview(w http.ResponseWriter, r *http.Request)
|
|||
scene := r.Context().Value(sceneKey).(*models.Scene)
|
||||
sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId"))
|
||||
var sceneMarker *models.SceneMarker
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
var err error
|
||||
sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID)
|
||||
return err
|
||||
|
|
@ -487,7 +487,7 @@ func (rs sceneRoutes) SceneMarkerScreenshot(w http.ResponseWriter, r *http.Reque
|
|||
scene := r.Context().Value(sceneKey).(*models.Scene)
|
||||
sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId"))
|
||||
var sceneMarker *models.SceneMarker
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
var err error
|
||||
sceneMarker, err = rs.sceneMarkerFinder.Find(ctx, sceneMarkerID)
|
||||
return err
|
||||
|
|
@ -528,7 +528,7 @@ func (rs sceneRoutes) SceneCtx(next http.Handler) http.Handler {
|
|||
sceneID, _ := strconv.Atoi(sceneIdentifierQueryParam)
|
||||
|
||||
var scene *models.Scene
|
||||
_ = txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
qb := rs.sceneFinder
|
||||
if sceneID == 0 {
|
||||
var scenes []*models.Scene
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func (rs studioRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var image []byte
|
||||
if defaultParam != "true" {
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
image, _ = rs.studioFinder.GetImage(ctx, studio.ID)
|
||||
return nil
|
||||
})
|
||||
|
|
@ -71,7 +71,7 @@ func (rs studioRoutes) StudioCtx(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
var studio *models.Studio
|
||||
_ = txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
var err error
|
||||
studio, err = rs.studioFinder.Find(ctx, studioID)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func (rs tagRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var image []byte
|
||||
if defaultParam != "true" {
|
||||
readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
image, _ = rs.tagFinder.GetImage(ctx, tag.ID)
|
||||
return nil
|
||||
})
|
||||
|
|
@ -71,7 +71,7 @@ func (rs tagRoutes) TagCtx(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
var tag *models.Tag
|
||||
_ = txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
_ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error {
|
||||
var err error
|
||||
tag, err = rs.tagFinder.Find(ctx, tagID)
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -181,6 +181,21 @@ func Start() error {
|
|||
|
||||
http.ServeFile(w, r, fn)
|
||||
})
|
||||
r.HandleFunc("/javascript", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/javascript")
|
||||
if !c.GetJavascriptEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
// search for custom.js in current directory, then $HOME/.stash
|
||||
fn := c.GetJavascriptPath()
|
||||
exists, _ := fsutil.FileExists(fn)
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, fn)
|
||||
})
|
||||
r.HandleFunc("/customlocales", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if c.GetCustomLocalesEnabled() {
|
||||
|
|
@ -216,18 +231,9 @@ func Start() error {
|
|||
// Serve static folders
|
||||
customServedFolders := c.GetCustomServedFolders()
|
||||
if customServedFolders != nil {
|
||||
r.HandleFunc("/custom/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
r.URL.Path = strings.Replace(r.URL.Path, "/custom", "", 1)
|
||||
|
||||
// map the path to the applicable filesystem location
|
||||
var dir string
|
||||
r.URL.Path, dir = customServedFolders.GetFilesystemLocation(r.URL.Path)
|
||||
if dir != "" {
|
||||
http.FileServer(http.Dir(dir)).ServeHTTP(w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
r.Mount("/custom", customRoutes{
|
||||
servedFolders: customServedFolders,
|
||||
}.Routes())
|
||||
}
|
||||
|
||||
customUILocation := c.GetCustomUILocation()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package urlbuilders
|
||||
|
||||
import (
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type PerformerURLBuilder struct {
|
||||
|
|
@ -15,7 +16,7 @@ func NewPerformerURLBuilder(baseURL string, performer *models.Performer) Perform
|
|||
return PerformerURLBuilder{
|
||||
BaseURL: baseURL,
|
||||
PerformerID: strconv.Itoa(performer.ID),
|
||||
UpdatedAt: strconv.FormatInt(performer.UpdatedAt.Timestamp.Unix(), 10),
|
||||
UpdatedAt: strconv.FormatInt(performer.UpdatedAt.Unix(), 10),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ func TestGalleryPerformers(t *testing.T) {
|
|||
const performerID = 2
|
||||
performer := models.Performer{
|
||||
ID: performerID,
|
||||
Name: models.NullString(performerName),
|
||||
Name: performerName,
|
||||
}
|
||||
|
||||
const reversedPerformerName = "name performer"
|
||||
const reversedPerformerID = 3
|
||||
reversedPerformer := models.Performer{
|
||||
ID: reversedPerformerID,
|
||||
Name: models.NullString(reversedPerformerName),
|
||||
Name: reversedPerformerName,
|
||||
}
|
||||
|
||||
testTables := generateTestTable(performerName, galleryExt)
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ func TestImagePerformers(t *testing.T) {
|
|||
const performerID = 2
|
||||
performer := models.Performer{
|
||||
ID: performerID,
|
||||
Name: models.NullString(performerName),
|
||||
Name: performerName,
|
||||
}
|
||||
|
||||
const reversedPerformerName = "name performer"
|
||||
const reversedPerformerID = 3
|
||||
reversedPerformer := models.Performer{
|
||||
ID: reversedPerformerID,
|
||||
Name: models.NullString(reversedPerformerName),
|
||||
Name: reversedPerformerName,
|
||||
}
|
||||
|
||||
testTables := generateTestTable(performerName, imageExt)
|
||||
|
|
|
|||
|
|
@ -87,11 +87,10 @@ func createPerformer(ctx context.Context, pqb models.PerformerWriter) error {
|
|||
// create the performer
|
||||
performer := models.Performer{
|
||||
Checksum: testName,
|
||||
Name: sql.NullString{Valid: true, String: testName},
|
||||
Favorite: sql.NullBool{Valid: true, Bool: false},
|
||||
Name: testName,
|
||||
}
|
||||
|
||||
_, err := pqb.Create(ctx, performer)
|
||||
err := pqb.Create(ctx, &performer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -480,6 +479,10 @@ func withTxn(f func(ctx context.Context) error) error {
|
|||
return txn.WithTxn(context.TODO(), db, f)
|
||||
}
|
||||
|
||||
func withDB(f func(ctx context.Context) error) error {
|
||||
return txn.WithDatabase(context.TODO(), db, f)
|
||||
}
|
||||
|
||||
func populateDB() error {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
err := createPerformer(ctx, r.Performer)
|
||||
|
|
@ -539,9 +542,13 @@ func TestParsePerformerScenes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, p := range performers {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
return PerformerScenes(ctx, p, nil, r.Scene, nil)
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
return tagger.PerformerScenes(ctx, p, nil, r.Scene)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
@ -586,14 +593,18 @@ func TestParseStudioScenes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range studios {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Studio.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return StudioScenes(ctx, s, nil, aliases, r.Scene, nil)
|
||||
return tagger.StudioScenes(ctx, s, nil, aliases, r.Scene)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
@ -642,14 +653,18 @@ func TestParseTagScenes(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range tags {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Tag.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return TagScenes(ctx, s, nil, aliases, r.Scene, nil)
|
||||
return tagger.TagScenes(ctx, s, nil, aliases, r.Scene)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
@ -694,9 +709,13 @@ func TestParsePerformerImages(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, p := range performers {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
return PerformerImages(ctx, p, nil, r.Image, nil)
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
return tagger.PerformerImages(ctx, p, nil, r.Image)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
@ -742,14 +761,18 @@ func TestParseStudioImages(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range studios {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Studio.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return StudioImages(ctx, s, nil, aliases, r.Image, nil)
|
||||
return tagger.StudioImages(ctx, s, nil, aliases, r.Image)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
@ -798,14 +821,18 @@ func TestParseTagImages(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range tags {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Tag.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return TagImages(ctx, s, nil, aliases, r.Image, nil)
|
||||
return tagger.TagImages(ctx, s, nil, aliases, r.Image)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
@ -851,9 +878,13 @@ func TestParsePerformerGalleries(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, p := range performers {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
return PerformerGalleries(ctx, p, nil, r.Gallery, nil)
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
return tagger.PerformerGalleries(ctx, p, nil, r.Gallery)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
@ -899,14 +930,18 @@ func TestParseStudioGalleries(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range studios {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Studio.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return StudioGalleries(ctx, s, nil, aliases, r.Gallery, nil)
|
||||
return tagger.StudioGalleries(ctx, s, nil, aliases, r.Gallery)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
@ -955,14 +990,18 @@ func TestParseTagGalleries(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
tagger := Tagger{
|
||||
TxnManager: db,
|
||||
}
|
||||
|
||||
for _, s := range tags {
|
||||
if err := withTxn(func(ctx context.Context) error {
|
||||
if err := withDB(func(ctx context.Context) error {
|
||||
aliases, err := r.Tag.GetAliases(ctx, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return TagGalleries(ctx, s, nil, aliases, r.Gallery, nil)
|
||||
return tagger.TagGalleries(ctx, s, nil, aliases, r.Gallery)
|
||||
}); err != nil {
|
||||
t.Errorf("Error auto-tagging performers: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
type SceneQueryPerformerUpdater interface {
|
||||
|
|
@ -33,14 +34,14 @@ func getPerformerTagger(p *models.Performer, cache *match.Cache) tagger {
|
|||
return tagger{
|
||||
ID: p.ID,
|
||||
Type: "performer",
|
||||
Name: p.Name.String,
|
||||
Name: p.Name,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// PerformerScenes searches for scenes whose path matches the provided performer name and tags the scene with the performer.
|
||||
func PerformerScenes(ctx context.Context, p *models.Performer, paths []string, rw SceneQueryPerformerUpdater, cache *match.Cache) error {
|
||||
t := getPerformerTagger(p, cache)
|
||||
func (tagger *Tagger) PerformerScenes(ctx context.Context, p *models.Performer, paths []string, rw SceneQueryPerformerUpdater) error {
|
||||
t := getPerformerTagger(p, tagger.Cache)
|
||||
|
||||
return t.tagScenes(ctx, paths, rw, func(o *models.Scene) (bool, error) {
|
||||
if err := o.LoadPerformerIDs(ctx, rw); err != nil {
|
||||
|
|
@ -52,7 +53,9 @@ func PerformerScenes(ctx context.Context, p *models.Performer, paths []string, r
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := scene.AddPerformer(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return scene.AddPerformer(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
@ -61,8 +64,8 @@ func PerformerScenes(ctx context.Context, p *models.Performer, paths []string, r
|
|||
}
|
||||
|
||||
// PerformerImages searches for images whose path matches the provided performer name and tags the image with the performer.
|
||||
func PerformerImages(ctx context.Context, p *models.Performer, paths []string, rw ImageQueryPerformerUpdater, cache *match.Cache) error {
|
||||
t := getPerformerTagger(p, cache)
|
||||
func (tagger *Tagger) PerformerImages(ctx context.Context, p *models.Performer, paths []string, rw ImageQueryPerformerUpdater) error {
|
||||
t := getPerformerTagger(p, tagger.Cache)
|
||||
|
||||
return t.tagImages(ctx, paths, rw, func(o *models.Image) (bool, error) {
|
||||
if err := o.LoadPerformerIDs(ctx, rw); err != nil {
|
||||
|
|
@ -74,7 +77,9 @@ func PerformerImages(ctx context.Context, p *models.Performer, paths []string, r
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := image.AddPerformer(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return image.AddPerformer(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
@ -83,8 +88,8 @@ func PerformerImages(ctx context.Context, p *models.Performer, paths []string, r
|
|||
}
|
||||
|
||||
// PerformerGalleries searches for galleries whose path matches the provided performer name and tags the gallery with the performer.
|
||||
func PerformerGalleries(ctx context.Context, p *models.Performer, paths []string, rw GalleryQueryPerformerUpdater, cache *match.Cache) error {
|
||||
t := getPerformerTagger(p, cache)
|
||||
func (tagger *Tagger) PerformerGalleries(ctx context.Context, p *models.Performer, paths []string, rw GalleryQueryPerformerUpdater) error {
|
||||
t := getPerformerTagger(p, tagger.Cache)
|
||||
|
||||
return t.tagGalleries(ctx, paths, rw, func(o *models.Gallery) (bool, error) {
|
||||
if err := o.LoadPerformerIDs(ctx, rw); err != nil {
|
||||
|
|
@ -96,7 +101,9 @@ func PerformerGalleries(ctx context.Context, p *models.Performer, paths []string
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := gallery.AddPerformer(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return gallery.AddPerformer(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func TestPerformerScenes(t *testing.T) {
|
||||
|
|
@ -60,11 +61,13 @@ func testPerformerScenes(t *testing.T, performerName, expectedRegex string) {
|
|||
|
||||
performer := models.Performer{
|
||||
ID: performerID,
|
||||
Name: models.NullString(performerName),
|
||||
Name: performerName,
|
||||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedSceneFilter := &models.SceneFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -75,15 +78,17 @@ func testPerformerScenes(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
mockSceneReader.On("Query", testCtx, scene.QueryOptions(expectedSceneFilter, expectedFindFilter, false)).
|
||||
mockSceneReader.On("Query", mock.Anything, scene.QueryOptions(expectedSceneFilter, expectedFindFilter, false)).
|
||||
Return(mocks.SceneQueryResult(scenes, len(scenes)), nil).Once()
|
||||
|
||||
for i := range matchingPaths {
|
||||
sceneID := i + 1
|
||||
mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{
|
||||
mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{
|
||||
PerformerIDs: &models.UpdateIDs{
|
||||
IDs: []int{performerID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
|
@ -91,7 +96,11 @@ func testPerformerScenes(t *testing.T, performerName, expectedRegex string) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := PerformerScenes(testCtx, &performer, nil, mockSceneReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.PerformerScenes(testCtx, &performer, nil, mockSceneReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
@ -140,11 +149,13 @@ func testPerformerImages(t *testing.T, performerName, expectedRegex string) {
|
|||
|
||||
performer := models.Performer{
|
||||
ID: performerID,
|
||||
Name: models.NullString(performerName),
|
||||
Name: performerName,
|
||||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedImageFilter := &models.ImageFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -155,15 +166,17 @@ func testPerformerImages(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
mockImageReader.On("Query", testCtx, image.QueryOptions(expectedImageFilter, expectedFindFilter, false)).
|
||||
mockImageReader.On("Query", mock.Anything, image.QueryOptions(expectedImageFilter, expectedFindFilter, false)).
|
||||
Return(mocks.ImageQueryResult(images, len(images)), nil).Once()
|
||||
|
||||
for i := range matchingPaths {
|
||||
imageID := i + 1
|
||||
mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{
|
||||
mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{
|
||||
PerformerIDs: &models.UpdateIDs{
|
||||
IDs: []int{performerID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
|
@ -171,7 +184,11 @@ func testPerformerImages(t *testing.T, performerName, expectedRegex string) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := PerformerImages(testCtx, &performer, nil, mockImageReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.PerformerImages(testCtx, &performer, nil, mockImageReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
@ -221,11 +238,13 @@ func testPerformerGalleries(t *testing.T, performerName, expectedRegex string) {
|
|||
|
||||
performer := models.Performer{
|
||||
ID: performerID,
|
||||
Name: models.NullString(performerName),
|
||||
Name: performerName,
|
||||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedGalleryFilter := &models.GalleryFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -236,14 +255,16 @@ func testPerformerGalleries(t *testing.T, performerName, expectedRegex string) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
mockGalleryReader.On("Query", testCtx, expectedGalleryFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
mockGalleryReader.On("Query", mock.Anything, expectedGalleryFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
|
||||
for i := range matchingPaths {
|
||||
galleryID := i + 1
|
||||
mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{
|
||||
mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{
|
||||
PerformerIDs: &models.UpdateIDs{
|
||||
IDs: []int{performerID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
|
@ -251,7 +272,11 @@ func testPerformerGalleries(t *testing.T, performerName, expectedRegex string) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := PerformerGalleries(testCtx, &performer, nil, mockGalleryReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.PerformerGalleries(testCtx, &performer, nil, mockGalleryReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -152,14 +152,14 @@ func TestScenePerformers(t *testing.T) {
|
|||
const performerID = 2
|
||||
performer := models.Performer{
|
||||
ID: performerID,
|
||||
Name: models.NullString(performerName),
|
||||
Name: performerName,
|
||||
}
|
||||
|
||||
const reversedPerformerName = "name performer"
|
||||
const reversedPerformerID = 3
|
||||
reversedPerformer := models.Performer{
|
||||
ID: reversedPerformerID,
|
||||
Name: models.NullString(reversedPerformerName),
|
||||
Name: reversedPerformerName,
|
||||
}
|
||||
|
||||
testTables := generateTestTable(performerName, sceneExt)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ import (
|
|||
"github.com/stashapp/stash/pkg/match"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
// the following functions aren't used in Tagger because they assume
|
||||
// use within a transaction
|
||||
|
||||
func addSceneStudio(ctx context.Context, sceneWriter scene.PartialUpdater, o *models.Scene, studioID int) (bool, error) {
|
||||
// don't set if already set
|
||||
if o.StudioID != nil {
|
||||
|
|
@ -86,12 +90,28 @@ type SceneFinderUpdater interface {
|
|||
}
|
||||
|
||||
// StudioScenes searches for scenes whose path matches the provided studio name and tags the scene with the studio, if studio is not already set on the scene.
|
||||
func StudioScenes(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw SceneFinderUpdater, cache *match.Cache) error {
|
||||
t := getStudioTagger(p, aliases, cache)
|
||||
func (tagger *Tagger) StudioScenes(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw SceneFinderUpdater) error {
|
||||
t := getStudioTagger(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagScenes(ctx, paths, rw, func(o *models.Scene) (bool, error) {
|
||||
return addSceneStudio(ctx, rw, o, p.ID)
|
||||
// don't set if already set
|
||||
if o.StudioID != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// set the studio id
|
||||
scenePartial := models.ScenePartial{
|
||||
StudioID: models.NewOptionalInt(p.ID),
|
||||
}
|
||||
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
_, err := rw.UpdatePartial(ctx, o.ID, scenePartial)
|
||||
return err
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -107,12 +127,28 @@ type ImageFinderUpdater interface {
|
|||
}
|
||||
|
||||
// StudioImages searches for images whose path matches the provided studio name and tags the image with the studio, if studio is not already set on the image.
|
||||
func StudioImages(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw ImageFinderUpdater, cache *match.Cache) error {
|
||||
t := getStudioTagger(p, aliases, cache)
|
||||
func (tagger *Tagger) StudioImages(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw ImageFinderUpdater) error {
|
||||
t := getStudioTagger(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagImages(ctx, paths, rw, func(i *models.Image) (bool, error) {
|
||||
return addImageStudio(ctx, rw, i, p.ID)
|
||||
// don't set if already set
|
||||
if i.StudioID != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// set the studio id
|
||||
imagePartial := models.ImagePartial{
|
||||
StudioID: models.NewOptionalInt(p.ID),
|
||||
}
|
||||
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
_, err := rw.UpdatePartial(ctx, i.ID, imagePartial)
|
||||
return err
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -128,12 +164,28 @@ type GalleryFinderUpdater interface {
|
|||
}
|
||||
|
||||
// StudioGalleries searches for galleries whose path matches the provided studio name and tags the gallery with the studio, if studio is not already set on the gallery.
|
||||
func StudioGalleries(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw GalleryFinderUpdater, cache *match.Cache) error {
|
||||
t := getStudioTagger(p, aliases, cache)
|
||||
func (tagger *Tagger) StudioGalleries(ctx context.Context, p *models.Studio, paths []string, aliases []string, rw GalleryFinderUpdater) error {
|
||||
t := getStudioTagger(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagGalleries(ctx, paths, rw, func(o *models.Gallery) (bool, error) {
|
||||
return addGalleryStudio(ctx, rw, o, p.ID)
|
||||
// don't set if already set
|
||||
if o.StudioID != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// set the studio id
|
||||
galleryPartial := models.GalleryPartial{
|
||||
StudioID: models.NewOptionalInt(p.ID),
|
||||
}
|
||||
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
_, err := rw.UpdatePartial(ctx, o.ID, galleryPartial)
|
||||
return err
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type testStudioCase struct {
|
||||
|
|
@ -110,7 +111,9 @@ func testStudioScenes(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedSceneFilter := &models.SceneFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -121,7 +124,9 @@ func testStudioScenes(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
|
|
@ -140,19 +145,23 @@ func testStudioScenes(t *testing.T, tc testStudioCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockSceneReader.On("Query", testCtx, scene.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
mockSceneReader.On("Query", mock.Anything, scene.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
Return(mocks.SceneQueryResult(scenes, len(scenes)), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
sceneID := i + 1
|
||||
expectedStudioID := studioID
|
||||
mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{
|
||||
mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{
|
||||
StudioID: models.NewOptionalInt(expectedStudioID),
|
||||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := StudioScenes(testCtx, &studio, nil, aliases, mockSceneReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.StudioScenes(testCtx, &studio, nil, aliases, mockSceneReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
@ -201,7 +210,9 @@ func testStudioImages(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedImageFilter := &models.ImageFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -212,11 +223,13 @@ func testStudioImages(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
onNameQuery := mockImageReader.On("Query", testCtx, image.QueryOptions(expectedImageFilter, expectedFindFilter, false))
|
||||
onNameQuery := mockImageReader.On("Query", mock.Anything, image.QueryOptions(expectedImageFilter, expectedFindFilter, false))
|
||||
if aliasName == "" {
|
||||
onNameQuery.Return(mocks.ImageQueryResult(images, len(images)), nil).Once()
|
||||
} else {
|
||||
|
|
@ -230,19 +243,23 @@ func testStudioImages(t *testing.T, tc testStudioCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockImageReader.On("Query", testCtx, image.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
mockImageReader.On("Query", mock.Anything, image.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
Return(mocks.ImageQueryResult(images, len(images)), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
imageID := i + 1
|
||||
expectedStudioID := studioID
|
||||
mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{
|
||||
mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{
|
||||
StudioID: models.NewOptionalInt(expectedStudioID),
|
||||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := StudioImages(testCtx, &studio, nil, aliases, mockImageReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.StudioImages(testCtx, &studio, nil, aliases, mockImageReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
@ -291,7 +308,9 @@ func testStudioGalleries(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedGalleryFilter := &models.GalleryFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -302,11 +321,13 @@ func testStudioGalleries(t *testing.T, tc testStudioCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
onNameQuery := mockGalleryReader.On("Query", testCtx, expectedGalleryFilter, expectedFindFilter)
|
||||
onNameQuery := mockGalleryReader.On("Query", mock.Anything, expectedGalleryFilter, expectedFindFilter)
|
||||
if aliasName == "" {
|
||||
onNameQuery.Return(galleries, len(galleries), nil).Once()
|
||||
} else {
|
||||
|
|
@ -320,18 +341,22 @@ func testStudioGalleries(t *testing.T, tc testStudioCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockGalleryReader.On("Query", testCtx, expectedAliasFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
mockGalleryReader.On("Query", mock.Anything, expectedAliasFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
galleryID := i + 1
|
||||
expectedStudioID := studioID
|
||||
mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{
|
||||
mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{
|
||||
StudioID: models.NewOptionalInt(expectedStudioID),
|
||||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := StudioGalleries(testCtx, &studio, nil, aliases, mockGalleryReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.StudioGalleries(testCtx, &studio, nil, aliases, mockGalleryReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
type SceneQueryTagUpdater interface {
|
||||
|
|
@ -50,8 +51,8 @@ func getTagTaggers(p *models.Tag, aliases []string, cache *match.Cache) []tagger
|
|||
}
|
||||
|
||||
// TagScenes searches for scenes whose path matches the provided tag name and tags the scene with the tag.
|
||||
func TagScenes(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw SceneQueryTagUpdater, cache *match.Cache) error {
|
||||
t := getTagTaggers(p, aliases, cache)
|
||||
func (tagger *Tagger) TagScenes(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw SceneQueryTagUpdater) error {
|
||||
t := getTagTaggers(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagScenes(ctx, paths, rw, func(o *models.Scene) (bool, error) {
|
||||
|
|
@ -64,7 +65,9 @@ func TagScenes(ctx context.Context, p *models.Tag, paths []string, aliases []str
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := scene.AddTag(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return scene.AddTag(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
@ -77,8 +80,8 @@ func TagScenes(ctx context.Context, p *models.Tag, paths []string, aliases []str
|
|||
}
|
||||
|
||||
// TagImages searches for images whose path matches the provided tag name and tags the image with the tag.
|
||||
func TagImages(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw ImageQueryTagUpdater, cache *match.Cache) error {
|
||||
t := getTagTaggers(p, aliases, cache)
|
||||
func (tagger *Tagger) TagImages(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw ImageQueryTagUpdater) error {
|
||||
t := getTagTaggers(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagImages(ctx, paths, rw, func(o *models.Image) (bool, error) {
|
||||
|
|
@ -91,7 +94,9 @@ func TagImages(ctx context.Context, p *models.Tag, paths []string, aliases []str
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := image.AddTag(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return image.AddTag(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
@ -104,8 +109,8 @@ func TagImages(ctx context.Context, p *models.Tag, paths []string, aliases []str
|
|||
}
|
||||
|
||||
// TagGalleries searches for galleries whose path matches the provided tag name and tags the gallery with the tag.
|
||||
func TagGalleries(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw GalleryQueryTagUpdater, cache *match.Cache) error {
|
||||
t := getTagTaggers(p, aliases, cache)
|
||||
func (tagger *Tagger) TagGalleries(ctx context.Context, p *models.Tag, paths []string, aliases []string, rw GalleryQueryTagUpdater) error {
|
||||
t := getTagTaggers(p, aliases, tagger.Cache)
|
||||
|
||||
for _, tt := range t {
|
||||
if err := tt.tagGalleries(ctx, paths, rw, func(o *models.Gallery) (bool, error) {
|
||||
|
|
@ -118,7 +123,9 @@ func TagGalleries(ctx context.Context, p *models.Tag, paths []string, aliases []
|
|||
return false, nil
|
||||
}
|
||||
|
||||
if err := gallery.AddTag(ctx, rw, o, p.ID); err != nil {
|
||||
if err := txn.WithTxn(ctx, tagger.TxnManager, func(ctx context.Context) error {
|
||||
return gallery.AddTag(ctx, rw, o, p.ID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type testTagCase struct {
|
||||
|
|
@ -111,7 +112,9 @@ func testTagScenes(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedSceneFilter := &models.SceneFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -122,7 +125,9 @@ func testTagScenes(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
|
|
@ -140,13 +145,13 @@ func testTagScenes(t *testing.T, tc testTagCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockSceneReader.On("Query", testCtx, scene.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
mockSceneReader.On("Query", mock.Anything, scene.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
Return(mocks.SceneQueryResult(scenes, len(scenes)), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
sceneID := i + 1
|
||||
mockSceneReader.On("UpdatePartial", testCtx, sceneID, models.ScenePartial{
|
||||
mockSceneReader.On("UpdatePartial", mock.Anything, sceneID, models.ScenePartial{
|
||||
TagIDs: &models.UpdateIDs{
|
||||
IDs: []int{tagID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
|
@ -154,7 +159,11 @@ func testTagScenes(t *testing.T, tc testTagCase) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := TagScenes(testCtx, &tag, nil, aliases, mockSceneReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.TagScenes(testCtx, &tag, nil, aliases, mockSceneReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
@ -204,7 +213,9 @@ func testTagImages(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedImageFilter := &models.ImageFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -215,7 +226,9 @@ func testTagImages(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
|
|
@ -233,14 +246,14 @@ func testTagImages(t *testing.T, tc testTagCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockImageReader.On("Query", testCtx, image.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
mockImageReader.On("Query", mock.Anything, image.QueryOptions(expectedAliasFilter, expectedFindFilter, false)).
|
||||
Return(mocks.ImageQueryResult(images, len(images)), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
imageID := i + 1
|
||||
|
||||
mockImageReader.On("UpdatePartial", testCtx, imageID, models.ImagePartial{
|
||||
mockImageReader.On("UpdatePartial", mock.Anything, imageID, models.ImagePartial{
|
||||
TagIDs: &models.UpdateIDs{
|
||||
IDs: []int{tagID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
|
@ -248,7 +261,11 @@ func testTagImages(t *testing.T, tc testTagCase) {
|
|||
}).Return(nil, nil).Once()
|
||||
}
|
||||
|
||||
err := TagImages(testCtx, &tag, nil, aliases, mockImageReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.TagImages(testCtx, &tag, nil, aliases, mockImageReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
@ -299,7 +316,9 @@ func testTagGalleries(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
organized := false
|
||||
perPage := models.PerPageAll
|
||||
perPage := 1000
|
||||
sort := "id"
|
||||
direction := models.SortDirectionEnumAsc
|
||||
|
||||
expectedGalleryFilter := &models.GalleryFilterType{
|
||||
Organized: &organized,
|
||||
|
|
@ -310,7 +329,9 @@ func testTagGalleries(t *testing.T, tc testTagCase) {
|
|||
}
|
||||
|
||||
expectedFindFilter := &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
PerPage: &perPage,
|
||||
Sort: &sort,
|
||||
Direction: &direction,
|
||||
}
|
||||
|
||||
// if alias provided, then don't find by name
|
||||
|
|
@ -328,13 +349,13 @@ func testTagGalleries(t *testing.T, tc testTagCase) {
|
|||
},
|
||||
}
|
||||
|
||||
mockGalleryReader.On("Query", testCtx, expectedAliasFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
mockGalleryReader.On("Query", mock.Anything, expectedAliasFilter, expectedFindFilter).Return(galleries, len(galleries), nil).Once()
|
||||
}
|
||||
|
||||
for i := range matchingPaths {
|
||||
galleryID := i + 1
|
||||
|
||||
mockGalleryReader.On("UpdatePartial", testCtx, galleryID, models.GalleryPartial{
|
||||
mockGalleryReader.On("UpdatePartial", mock.Anything, galleryID, models.GalleryPartial{
|
||||
TagIDs: &models.UpdateIDs{
|
||||
IDs: []int{tagID},
|
||||
Mode: models.RelationshipUpdateModeAdd,
|
||||
|
|
@ -343,7 +364,11 @@ func testTagGalleries(t *testing.T, tc testTagCase) {
|
|||
|
||||
}
|
||||
|
||||
err := TagGalleries(testCtx, &tag, nil, aliases, mockGalleryReader, nil)
|
||||
tagger := Tagger{
|
||||
TxnManager: &mocks.TxnManager{},
|
||||
}
|
||||
|
||||
err := tagger.TagGalleries(testCtx, &tag, nil, aliases, mockGalleryReader)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,14 @@ import (
|
|||
"github.com/stashapp/stash/pkg/match"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/txn"
|
||||
)
|
||||
|
||||
type Tagger struct {
|
||||
TxnManager txn.Manager
|
||||
Cache *match.Cache
|
||||
}
|
||||
|
||||
type tagger struct {
|
||||
ID int
|
||||
Type string
|
||||
|
|
@ -58,11 +64,11 @@ func (t *tagger) tagPerformers(ctx context.Context, performerReader match.Perfor
|
|||
added, err := addFunc(t.ID, p.ID)
|
||||
|
||||
if err != nil {
|
||||
return t.addError("performer", p.Name.String, err)
|
||||
return t.addError("performer", p.Name, err)
|
||||
}
|
||||
|
||||
if added {
|
||||
t.addLog("performer", p.Name.String)
|
||||
t.addLog("performer", p.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,12 +118,7 @@ func (t *tagger) tagTags(ctx context.Context, tagReader match.TagAutoTagQueryer,
|
|||
}
|
||||
|
||||
func (t *tagger) tagScenes(ctx context.Context, paths []string, sceneReader scene.Queryer, addFunc addSceneLinkFunc) error {
|
||||
others, err := match.PathToScenes(ctx, t.Name, paths, sceneReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range others {
|
||||
return match.PathToScenesFn(ctx, t.Name, paths, sceneReader, func(ctx context.Context, p *models.Scene) error {
|
||||
added, err := addFunc(p)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -127,18 +128,13 @@ func (t *tagger) tagScenes(ctx context.Context, paths []string, sceneReader scen
|
|||
if added {
|
||||
t.addLog("scene", p.DisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *tagger) tagImages(ctx context.Context, paths []string, imageReader image.Queryer, addFunc addImageLinkFunc) error {
|
||||
others, err := match.PathToImages(ctx, t.Name, paths, imageReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range others {
|
||||
return match.PathToImagesFn(ctx, t.Name, paths, imageReader, func(ctx context.Context, p *models.Image) error {
|
||||
added, err := addFunc(p)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -148,18 +144,13 @@ func (t *tagger) tagImages(ctx context.Context, paths []string, imageReader imag
|
|||
if added {
|
||||
t.addLog("image", p.DisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *tagger) tagGalleries(ctx context.Context, paths []string, galleryReader gallery.Queryer, addFunc addGalleryLinkFunc) error {
|
||||
others, err := match.PathToGalleries(ctx, t.Name, paths, galleryReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range others {
|
||||
return match.PathToGalleriesFn(ctx, t.Name, paths, galleryReader, func(ctx context.Context, p *models.Gallery) error {
|
||||
added, err := addFunc(p)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -169,7 +160,7 @@ func (t *tagger) tagGalleries(ctx context.Context, paths []string, galleryReader
|
|||
if added {
|
||||
t.addLog("gallery", p.DisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -360,7 +360,7 @@ func (me *contentDirectoryService) handleBrowseMetadata(obj object, host string)
|
|||
} else {
|
||||
var scene *models.Scene
|
||||
|
||||
if err := txn.WithTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
if err := txn.WithReadTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
scene, err = me.repository.SceneFinder.Find(ctx, sceneID)
|
||||
if scene != nil {
|
||||
err = scene.LoadPrimaryFile(ctx, me.repository.FileFinder)
|
||||
|
|
@ -443,7 +443,7 @@ func getRootObjects() []interface{} {
|
|||
func (me *contentDirectoryService) getVideos(sceneFilter *models.SceneFilterType, parentID string, host string) []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := txn.WithTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
if err := txn.WithReadTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
sort := "title"
|
||||
findFilter := &models.FindFilterType{
|
||||
PerPage: &pageSize,
|
||||
|
|
@ -486,7 +486,7 @@ func (me *contentDirectoryService) getVideos(sceneFilter *models.SceneFilterType
|
|||
func (me *contentDirectoryService) getPageVideos(sceneFilter *models.SceneFilterType, parentID string, page int, host string) []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := txn.WithTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
if err := txn.WithReadTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
pager := scenePager{
|
||||
sceneFilter: sceneFilter,
|
||||
parentID: parentID,
|
||||
|
|
@ -527,7 +527,7 @@ func (me *contentDirectoryService) getAllScenes(host string) []interface{} {
|
|||
func (me *contentDirectoryService) getStudios() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := txn.WithTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
if err := txn.WithReadTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
studios, err := me.repository.StudioFinder.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -566,7 +566,7 @@ func (me *contentDirectoryService) getStudioScenes(paths []string, host string)
|
|||
func (me *contentDirectoryService) getTags() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := txn.WithTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
if err := txn.WithReadTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
tags, err := me.repository.TagFinder.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -605,14 +605,14 @@ func (me *contentDirectoryService) getTagScenes(paths []string, host string) []i
|
|||
func (me *contentDirectoryService) getPerformers() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := txn.WithTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
if err := txn.WithReadTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
performers, err := me.repository.PerformerFinder.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range performers {
|
||||
objs = append(objs, makeStorageFolder("performers/"+strconv.Itoa(s.ID), s.Name.String, "performers"))
|
||||
objs = append(objs, makeStorageFolder("performers/"+strconv.Itoa(s.ID), s.Name, "performers"))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -644,7 +644,7 @@ func (me *contentDirectoryService) getPerformerScenes(paths []string, host strin
|
|||
func (me *contentDirectoryService) getMovies() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := txn.WithTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
if err := txn.WithReadTxn(context.TODO(), me.txnManager, func(ctx context.Context) error {
|
||||
movies, err := me.repository.MovieFinder.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ func (me *Server) serveIcon(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
var scene *models.Scene
|
||||
err := txn.WithTxn(r.Context(), me.txnManager, func(ctx context.Context) error {
|
||||
err := txn.WithReadTxn(r.Context(), me.txnManager, func(ctx context.Context) error {
|
||||
idInt, err := strconv.Atoi(sceneId)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
|
@ -579,7 +579,7 @@ func (me *Server) initMux(mux *http.ServeMux) {
|
|||
mux.HandleFunc(resPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
sceneId := r.URL.Query().Get("scene")
|
||||
var scene *models.Scene
|
||||
err := txn.WithTxn(r.Context(), me.txnManager, func(ctx context.Context) error {
|
||||
err := txn.WithReadTxn(r.Context(), me.txnManager, func(ctx context.Context) error {
|
||||
sceneIdInt, err := strconv.Atoi(sceneId)
|
||||
if err != nil {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -280,6 +280,16 @@ func getScenePartial(scene *models.Scene, scraped *scraper.ScrapedScene, fieldOp
|
|||
partial.URL = models.NewOptionalString(*scraped.URL)
|
||||
}
|
||||
}
|
||||
if scraped.Director != nil && (scene.Director != *scraped.Director) {
|
||||
if shouldSetSingleValueField(fieldOptions["director"], scene.Director != "") {
|
||||
partial.Director = models.NewOptionalString(*scraped.Director)
|
||||
}
|
||||
}
|
||||
if scraped.Code != nil && (scene.Code != *scraped.Code) {
|
||||
if shouldSetSingleValueField(fieldOptions["code"], scene.Code != "") {
|
||||
partial.Code = models.NewOptionalString(*scraped.Code)
|
||||
}
|
||||
}
|
||||
|
||||
if setOrganized && !scene.Organized {
|
||||
// just reuse the boolean since we know it's true
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package identify
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
|
@ -12,7 +11,7 @@ import (
|
|||
)
|
||||
|
||||
type PerformerCreator interface {
|
||||
Create(ctx context.Context, newPerformer models.Performer) (*models.Performer, error)
|
||||
Create(ctx context.Context, newPerformer *models.Performer) error
|
||||
UpdateStashIDs(ctx context.Context, performerID int, stashIDs []models.StashID) error
|
||||
}
|
||||
|
||||
|
|
@ -33,13 +32,14 @@ func getPerformerID(ctx context.Context, endpoint string, w PerformerCreator, p
|
|||
}
|
||||
|
||||
func createMissingPerformer(ctx context.Context, endpoint string, w PerformerCreator, p *models.ScrapedPerformer) (*int, error) {
|
||||
created, err := w.Create(ctx, scrapedToPerformerInput(p))
|
||||
performerInput := scrapedToPerformerInput(p)
|
||||
err := w.Create(ctx, &performerInput)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating performer: %w", err)
|
||||
}
|
||||
|
||||
if endpoint != "" && p.RemoteSiteID != nil {
|
||||
if err := w.UpdateStashIDs(ctx, created.ID, []models.StashID{
|
||||
if err := w.UpdateStashIDs(ctx, performerInput.ID, []models.StashID{
|
||||
{
|
||||
Endpoint: endpoint,
|
||||
StashID: *p.RemoteSiteID,
|
||||
|
|
@ -49,65 +49,75 @@ func createMissingPerformer(ctx context.Context, endpoint string, w PerformerCre
|
|||
}
|
||||
}
|
||||
|
||||
return &created.ID, nil
|
||||
return &performerInput.ID, nil
|
||||
}
|
||||
|
||||
func scrapedToPerformerInput(performer *models.ScrapedPerformer) models.Performer {
|
||||
currentTime := time.Now()
|
||||
ret := models.Performer{
|
||||
Name: sql.NullString{String: *performer.Name, Valid: true},
|
||||
Name: *performer.Name,
|
||||
Checksum: md5.FromString(*performer.Name),
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
Favorite: sql.NullBool{Bool: false, Valid: true},
|
||||
CreatedAt: currentTime,
|
||||
UpdatedAt: currentTime,
|
||||
}
|
||||
if performer.Birthdate != nil {
|
||||
ret.Birthdate = models.SQLiteDate{String: *performer.Birthdate, Valid: true}
|
||||
d := models.NewDate(*performer.Birthdate)
|
||||
ret.Birthdate = &d
|
||||
}
|
||||
if performer.DeathDate != nil {
|
||||
ret.DeathDate = models.SQLiteDate{String: *performer.DeathDate, Valid: true}
|
||||
d := models.NewDate(*performer.DeathDate)
|
||||
ret.DeathDate = &d
|
||||
}
|
||||
if performer.Gender != nil {
|
||||
ret.Gender = sql.NullString{String: *performer.Gender, Valid: true}
|
||||
ret.Gender = models.GenderEnum(*performer.Gender)
|
||||
}
|
||||
if performer.Ethnicity != nil {
|
||||
ret.Ethnicity = sql.NullString{String: *performer.Ethnicity, Valid: true}
|
||||
ret.Ethnicity = *performer.Ethnicity
|
||||
}
|
||||
if performer.Country != nil {
|
||||
ret.Country = sql.NullString{String: *performer.Country, Valid: true}
|
||||
ret.Country = *performer.Country
|
||||
}
|
||||
if performer.EyeColor != nil {
|
||||
ret.EyeColor = sql.NullString{String: *performer.EyeColor, Valid: true}
|
||||
ret.EyeColor = *performer.EyeColor
|
||||
}
|
||||
if performer.HairColor != nil {
|
||||
ret.HairColor = sql.NullString{String: *performer.HairColor, Valid: true}
|
||||
ret.HairColor = *performer.HairColor
|
||||
}
|
||||
if performer.Height != nil {
|
||||
ret.Height = sql.NullString{String: *performer.Height, Valid: true}
|
||||
h, err := strconv.Atoi(*performer.Height) // height is stored as an int
|
||||
if err == nil {
|
||||
ret.Height = &h
|
||||
}
|
||||
}
|
||||
if performer.Weight != nil {
|
||||
h, err := strconv.Atoi(*performer.Weight)
|
||||
if err == nil {
|
||||
ret.Weight = &h
|
||||
}
|
||||
}
|
||||
if performer.Measurements != nil {
|
||||
ret.Measurements = sql.NullString{String: *performer.Measurements, Valid: true}
|
||||
ret.Measurements = *performer.Measurements
|
||||
}
|
||||
if performer.FakeTits != nil {
|
||||
ret.FakeTits = sql.NullString{String: *performer.FakeTits, Valid: true}
|
||||
ret.FakeTits = *performer.FakeTits
|
||||
}
|
||||
if performer.CareerLength != nil {
|
||||
ret.CareerLength = sql.NullString{String: *performer.CareerLength, Valid: true}
|
||||
ret.CareerLength = *performer.CareerLength
|
||||
}
|
||||
if performer.Tattoos != nil {
|
||||
ret.Tattoos = sql.NullString{String: *performer.Tattoos, Valid: true}
|
||||
ret.Tattoos = *performer.Tattoos
|
||||
}
|
||||
if performer.Piercings != nil {
|
||||
ret.Piercings = sql.NullString{String: *performer.Piercings, Valid: true}
|
||||
ret.Piercings = *performer.Piercings
|
||||
}
|
||||
if performer.Aliases != nil {
|
||||
ret.Aliases = sql.NullString{String: *performer.Aliases, Valid: true}
|
||||
ret.Aliases = *performer.Aliases
|
||||
}
|
||||
if performer.Twitter != nil {
|
||||
ret.Twitter = sql.NullString{String: *performer.Twitter, Valid: true}
|
||||
ret.Twitter = *performer.Twitter
|
||||
}
|
||||
if performer.Instagram != nil {
|
||||
ret.Instagram = sql.NullString{String: *performer.Instagram, Valid: true}
|
||||
ret.Instagram = *performer.Instagram
|
||||
}
|
||||
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
package identify
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/models/mocks"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
|
|
@ -24,9 +25,10 @@ func Test_getPerformerID(t *testing.T) {
|
|||
name := "name"
|
||||
|
||||
mockPerformerReaderWriter := mocks.PerformerReaderWriter{}
|
||||
mockPerformerReaderWriter.On("Create", testCtx, mock.Anything).Return(&models.Performer{
|
||||
ID: validStoredID,
|
||||
}, nil)
|
||||
mockPerformerReaderWriter.On("Create", testCtx, mock.Anything).Run(func(args mock.Arguments) {
|
||||
p := args.Get(1).(*models.Performer)
|
||||
p.ID = validStoredID
|
||||
}).Return(nil)
|
||||
|
||||
type args struct {
|
||||
endpoint string
|
||||
|
|
@ -132,14 +134,16 @@ func Test_createMissingPerformer(t *testing.T) {
|
|||
performerID := 1
|
||||
|
||||
mockPerformerReaderWriter := mocks.PerformerReaderWriter{}
|
||||
mockPerformerReaderWriter.On("Create", testCtx, mock.MatchedBy(func(p models.Performer) bool {
|
||||
return p.Name.String == validName
|
||||
})).Return(&models.Performer{
|
||||
ID: performerID,
|
||||
}, nil)
|
||||
mockPerformerReaderWriter.On("Create", testCtx, mock.MatchedBy(func(p models.Performer) bool {
|
||||
return p.Name.String == invalidName
|
||||
})).Return(nil, errors.New("error creating performer"))
|
||||
mockPerformerReaderWriter.On("Create", testCtx, mock.MatchedBy(func(p *models.Performer) bool {
|
||||
return p.Name == validName
|
||||
})).Run(func(args mock.Arguments) {
|
||||
p := args.Get(1).(*models.Performer)
|
||||
p.ID = performerID
|
||||
}).Return(nil)
|
||||
|
||||
mockPerformerReaderWriter.On("Create", testCtx, mock.MatchedBy(func(p *models.Performer) bool {
|
||||
return p.Name == invalidName
|
||||
})).Return(errors.New("error creating performer"))
|
||||
|
||||
mockPerformerReaderWriter.On("UpdateStashIDs", testCtx, performerID, []models.StashID{
|
||||
{
|
||||
|
|
@ -230,7 +234,7 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
|||
md5 := "b068931cc450442b63f5b3d276ea4297"
|
||||
|
||||
var stringValues []string
|
||||
for i := 0; i < 16; i++ {
|
||||
for i := 0; i < 17; i++ {
|
||||
stringValues = append(stringValues, strconv.Itoa(i))
|
||||
}
|
||||
|
||||
|
|
@ -241,6 +245,16 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
|||
return &ret
|
||||
}
|
||||
|
||||
nextIntVal := func() *int {
|
||||
ret := upTo
|
||||
upTo = (upTo + 1) % len(stringValues)
|
||||
return &ret
|
||||
}
|
||||
|
||||
dateToDatePtr := func(d models.Date) *models.Date {
|
||||
return &d
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
performer *models.ScrapedPerformer
|
||||
|
|
@ -258,6 +272,7 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
|||
EyeColor: nextVal(),
|
||||
HairColor: nextVal(),
|
||||
Height: nextVal(),
|
||||
Weight: nextVal(),
|
||||
Measurements: nextVal(),
|
||||
FakeTits: nextVal(),
|
||||
CareerLength: nextVal(),
|
||||
|
|
@ -268,34 +283,25 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
|||
Instagram: nextVal(),
|
||||
},
|
||||
models.Performer{
|
||||
Name: models.NullString(name),
|
||||
Checksum: md5,
|
||||
Favorite: sql.NullBool{
|
||||
Bool: false,
|
||||
Valid: true,
|
||||
},
|
||||
Birthdate: models.SQLiteDate{
|
||||
String: *nextVal(),
|
||||
Valid: true,
|
||||
},
|
||||
DeathDate: models.SQLiteDate{
|
||||
String: *nextVal(),
|
||||
Valid: true,
|
||||
},
|
||||
Gender: models.NullString(*nextVal()),
|
||||
Ethnicity: models.NullString(*nextVal()),
|
||||
Country: models.NullString(*nextVal()),
|
||||
EyeColor: models.NullString(*nextVal()),
|
||||
HairColor: models.NullString(*nextVal()),
|
||||
Height: models.NullString(*nextVal()),
|
||||
Measurements: models.NullString(*nextVal()),
|
||||
FakeTits: models.NullString(*nextVal()),
|
||||
CareerLength: models.NullString(*nextVal()),
|
||||
Tattoos: models.NullString(*nextVal()),
|
||||
Piercings: models.NullString(*nextVal()),
|
||||
Aliases: models.NullString(*nextVal()),
|
||||
Twitter: models.NullString(*nextVal()),
|
||||
Instagram: models.NullString(*nextVal()),
|
||||
Name: name,
|
||||
Checksum: md5,
|
||||
Birthdate: dateToDatePtr(models.NewDate(*nextVal())),
|
||||
DeathDate: dateToDatePtr(models.NewDate(*nextVal())),
|
||||
Gender: models.GenderEnum(*nextVal()),
|
||||
Ethnicity: *nextVal(),
|
||||
Country: *nextVal(),
|
||||
EyeColor: *nextVal(),
|
||||
HairColor: *nextVal(),
|
||||
Height: nextIntVal(),
|
||||
Weight: nextIntVal(),
|
||||
Measurements: *nextVal(),
|
||||
FakeTits: *nextVal(),
|
||||
CareerLength: *nextVal(),
|
||||
Tattoos: *nextVal(),
|
||||
Piercings: *nextVal(),
|
||||
Aliases: *nextVal(),
|
||||
Twitter: *nextVal(),
|
||||
Instagram: *nextVal(),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -304,12 +310,8 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
|||
Name: &name,
|
||||
},
|
||||
models.Performer{
|
||||
Name: models.NullString(name),
|
||||
Name: name,
|
||||
Checksum: md5,
|
||||
Favorite: sql.NullBool{
|
||||
Bool: false,
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -318,12 +320,10 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
|||
got := scrapedToPerformerInput(tt.performer)
|
||||
|
||||
// clear created/updated dates
|
||||
got.CreatedAt = models.SQLiteTimestamp{}
|
||||
got.CreatedAt = time.Time{}
|
||||
got.UpdatedAt = got.CreatedAt
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("scrapedToPerformerInput() = %v, want %v", got, tt.want)
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ const (
|
|||
ContinuePlaylistDefault = "continue_playlist_default"
|
||||
ShowStudioAsText = "show_studio_as_text"
|
||||
CSSEnabled = "cssEnabled"
|
||||
JavascriptEnabled = "javascriptEnabled"
|
||||
CustomLocalesEnabled = "customLocalesEnabled"
|
||||
|
||||
ShowScrubber = "show_scrubber"
|
||||
|
|
@ -465,7 +466,15 @@ func (i *Instance) getStringMapString(key string) map[string]string {
|
|||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
|
||||
return i.viper(key).GetStringMapString(key)
|
||||
ret := i.viper(key).GetStringMapString(key)
|
||||
|
||||
// GetStringMapString returns an empty map regardless of whether the
|
||||
// key exists or not.
|
||||
if len(ret) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type StashConfig struct {
|
||||
|
|
@ -1077,6 +1086,49 @@ func (i *Instance) GetCSSEnabled() bool {
|
|||
return i.getBool(CSSEnabled)
|
||||
}
|
||||
|
||||
func (i *Instance) GetJavascriptPath() string {
|
||||
// use custom.js in the same directory as the config file
|
||||
configFileUsed := i.GetConfigFile()
|
||||
configDir := filepath.Dir(configFileUsed)
|
||||
|
||||
fn := filepath.Join(configDir, "custom.js")
|
||||
|
||||
return fn
|
||||
}
|
||||
|
||||
func (i *Instance) GetJavascript() string {
|
||||
fn := i.GetJavascriptPath()
|
||||
|
||||
exists, _ := fsutil.FileExists(fn)
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf, err := os.ReadFile(fn)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func (i *Instance) SetJavascript(javascript string) {
|
||||
fn := i.GetJavascriptPath()
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
buf := []byte(javascript)
|
||||
|
||||
if err := os.WriteFile(fn, buf, 0777); err != nil {
|
||||
logger.Warnf("error while writing %v bytes to %v: %v", len(buf), fn, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Instance) GetJavascriptEnabled() bool {
|
||||
return i.getBool(JavascriptEnabled)
|
||||
}
|
||||
|
||||
func (i *Instance) GetCustomLocalesPath() string {
|
||||
// use custom-locales.json in the same directory as the config file
|
||||
configFileUsed := i.GetConfigFile()
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ func TestConcurrentConfigAccess(t *testing.T) {
|
|||
i.Set(ImageLightboxSlideshowDelay, *i.GetImageLightboxOptions().SlideshowDelay)
|
||||
i.GetCSSPath()
|
||||
i.GetCSS()
|
||||
i.GetJavascriptPath()
|
||||
i.GetJavascript()
|
||||
i.GetCustomLocalesPath()
|
||||
i.GetCustomLocales()
|
||||
i.Set(CSSEnabled, i.GetCSSEnabled())
|
||||
|
|
|
|||
|
|
@ -32,15 +32,19 @@ func toSnakeCase(v string) string {
|
|||
|
||||
func fromSnakeCase(v string) string {
|
||||
var buf bytes.Buffer
|
||||
leadingUnderscore := true
|
||||
capvar := false
|
||||
for i, c := range v {
|
||||
switch {
|
||||
case c == '_' && i > 0:
|
||||
case c == '_' && !leadingUnderscore && i > 0:
|
||||
capvar = true
|
||||
case c == '_' && leadingUnderscore:
|
||||
buf.WriteRune(c)
|
||||
case capvar:
|
||||
buf.WriteRune(unicode.ToUpper(c))
|
||||
capvar = false
|
||||
default:
|
||||
leadingUnderscore = false
|
||||
buf.WriteRune(c)
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +58,13 @@ func toSnakeCaseMap(m map[string]interface{}) map[string]interface{} {
|
|||
|
||||
for key, val := range m {
|
||||
adjKey := toSnakeCase(key)
|
||||
nm[adjKey] = val
|
||||
|
||||
switch v := val.(type) {
|
||||
case map[string]interface{}:
|
||||
nm[adjKey] = toSnakeCaseMap(v)
|
||||
default:
|
||||
nm[adjKey] = val
|
||||
}
|
||||
}
|
||||
|
||||
return nm
|
||||
|
|
@ -68,13 +78,15 @@ func convertMapValue(val interface{}) interface{} {
|
|||
case map[interface{}]interface{}:
|
||||
ret := cast.ToStringMap(v)
|
||||
for k, vv := range ret {
|
||||
ret[k] = convertMapValue(vv)
|
||||
adjKey := fromSnakeCase(k)
|
||||
ret[adjKey] = convertMapValue(vv)
|
||||
}
|
||||
return ret
|
||||
case map[string]interface{}:
|
||||
ret := make(map[string]interface{})
|
||||
for k, vv := range v {
|
||||
ret[k] = convertMapValue(vv)
|
||||
adjKey := fromSnakeCase(k)
|
||||
ret[adjKey] = convertMapValue(vv)
|
||||
}
|
||||
return ret
|
||||
case []interface{}:
|
||||
|
|
|
|||
|
|
@ -26,10 +26,13 @@ type SceneParserInput struct {
|
|||
type SceneParserResult struct {
|
||||
Scene *models.Scene `json:"scene"`
|
||||
Title *string `json:"title"`
|
||||
Code *string `json:"code"`
|
||||
Details *string `json:"details"`
|
||||
Director *string `json:"director"`
|
||||
URL *string `json:"url"`
|
||||
Date *string `json:"date"`
|
||||
Rating *int `json:"rating"`
|
||||
Rating100 *int `json:"rating100"`
|
||||
StudioID *string `json:"studio_id"`
|
||||
GalleryIds []string `json:"gallery_ids"`
|
||||
PerformerIds []string `json:"performer_ids"`
|
||||
|
|
@ -111,6 +114,7 @@ func initParserFields() {
|
|||
|
||||
ret["d"] = newParserField("d", `(?:\.|-|_)`, false)
|
||||
ret["rating"] = newParserField("rating", `\d`, true)
|
||||
ret["rating100"] = newParserField("rating100", `\d`, true)
|
||||
ret["performer"] = newParserField("performer", ".*", true)
|
||||
ret["studio"] = newParserField("studio", ".*", true)
|
||||
ret["movie"] = newParserField("movie", ".*", true)
|
||||
|
|
@ -254,6 +258,10 @@ func validateRating(rating int) bool {
|
|||
return rating >= 1 && rating <= 5
|
||||
}
|
||||
|
||||
func validateRating100(rating100 int) bool {
|
||||
return rating100 >= 1 && rating100 <= 100
|
||||
}
|
||||
|
||||
func validateDate(dateStr string) bool {
|
||||
splits := strings.Split(dateStr, "-")
|
||||
if len(splits) != 3 {
|
||||
|
|
@ -345,6 +353,13 @@ func (h *sceneHolder) setField(field parserField, value interface{}) {
|
|||
case "rating":
|
||||
rating, _ := strconv.Atoi(value.(string))
|
||||
if validateRating(rating) {
|
||||
// convert to 1-100 scale
|
||||
rating = models.Rating5To100(rating)
|
||||
h.result.Rating = &rating
|
||||
}
|
||||
case "rating100":
|
||||
rating, _ := strconv.Atoi(value.(string))
|
||||
if validateRating100(rating) {
|
||||
h.result.Rating = &rating
|
||||
}
|
||||
case "performer":
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue