mirror of
https://github.com/stashapp/stash.git
synced 2026-06-11 07:41:08 -05:00
Compare commits
413 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4b783871a | ||
|
|
29cd627ed2 | ||
|
|
c31c7c3c99 | ||
|
|
04ca11e62e | ||
|
|
fb5f9162d0 | ||
|
|
47ae1be53c | ||
|
|
11fa8ce581 | ||
|
|
428c6442d5 | ||
|
|
1e89e9dd82 | ||
|
|
f1da6cb1b2 | ||
|
|
dcf58b99a6 | ||
|
|
a5ca8fc678 | ||
|
|
17f5642ebd | ||
|
|
7464454da5 | ||
|
|
52193586de | ||
|
|
ea7a4f8d33 | ||
|
|
73ea195668 | ||
|
|
cca156b5f8 | ||
|
|
ca0a8b00ec | ||
|
|
e3480531a7 | ||
|
|
dabf5acefe | ||
|
|
df2c9e9754 | ||
|
|
9e541956f2 | ||
|
|
bc6d04dc2a | ||
|
|
b94eecae24 | ||
|
|
6ce57a9a43 | ||
|
|
94d192b833 | ||
|
|
479bd438df | ||
|
|
b957a87a78 | ||
|
|
be94e52f21 | ||
|
|
62af723017 | ||
|
|
a2cce0ba77 | ||
|
|
1d04b550b9 | ||
|
|
5fdab995f5 | ||
|
|
f977d0e18a | ||
|
|
ba2a79700a | ||
|
|
2f664fe826 | ||
|
|
b14d5c5650 | ||
|
|
a9e2a590b2 | ||
|
|
9cb1eccadb | ||
|
|
1e8a8efe3e | ||
|
|
cceaff780b | ||
|
|
8d9eb7f1e4 | ||
|
|
4a0c4c4847 | ||
|
|
56111433a1 | ||
|
|
f292238e7f | ||
|
|
25182997f1 | ||
|
|
a4ed9515c7 | ||
|
|
f3c8407c40 | ||
|
|
3e526a49a4 | ||
|
|
b7c229dc70 | ||
|
|
87709fd018 | ||
|
|
af6232ec97 | ||
|
|
919249f851 | ||
|
|
13fda2ad85 | ||
|
|
82e4ad4130 | ||
|
|
b83ce29ac4 | ||
|
|
66f92c5dcc | ||
|
|
d29699fa30 | ||
|
|
501ed7c2c2 | ||
|
|
e0c910d9e8 | ||
|
|
f3119a6c38 | ||
|
|
23f852cd91 | ||
|
|
2274db16b7 | ||
|
|
f5e4e7742e | ||
|
|
3be23999ef | ||
|
|
612ecb72fc | ||
|
|
b78060d361 | ||
|
|
1a3a2f1f83 | ||
|
|
565064b441 | ||
|
|
13a289a4a8 | ||
|
|
04e5ac9c2f | ||
|
|
c91ffe1e58 | ||
|
|
82a41e17c7 | ||
|
|
4b00d24248 | ||
|
|
265d5f4c70 | ||
|
|
4545da9af0 | ||
|
|
e7f6cb22b7 | ||
|
|
d2a0a8fe4c | ||
|
|
b482fbc796 | ||
|
|
a3f38d8edf | ||
|
|
651d2e6373 | ||
|
|
b76283df08 | ||
|
|
489db34db2 | ||
|
|
b2b05fb332 | ||
|
|
7a468413da | ||
|
|
4625e1f955 | ||
|
|
04e146f290 | ||
|
|
ab10cf8251 | ||
|
|
a73c99a61d | ||
|
|
709d7ce1cc | ||
|
|
1774a3600c | ||
|
|
50cb6a9c79 | ||
|
|
45a9aabdaf | ||
|
|
50217f6318 | ||
|
|
2e83405841 | ||
|
|
e98302fcd0 | ||
|
|
3f888a0335 | ||
|
|
4c144db510 | ||
|
|
177339c14e | ||
|
|
cb6dab3c5f | ||
|
|
da8803925c | ||
|
|
28b092885c | ||
|
|
392fa3535c | ||
|
|
ac72d4db2b | ||
|
|
0d4ab7f6f3 | ||
|
|
7b3b2ae9ba | ||
|
|
9803684535 | ||
|
|
680af72dcf | ||
|
|
fc6cafa15f | ||
|
|
0fc5a06332 | ||
|
|
b6d15cc077 | ||
|
|
7cb3d05535 | ||
|
|
c29d8b547d | ||
|
|
59c6fe046d | ||
|
|
d4d45d5a06 | ||
|
|
53489106a6 | ||
|
|
c5e1a3ed72 | ||
|
|
915533b8c5 | ||
|
|
59c7dd622b | ||
|
|
d31b6841d0 | ||
|
|
dfd55346b2 | ||
|
|
404eaa32d2 | ||
|
|
3e78d642a2 | ||
|
|
9d641c64e3 | ||
|
|
ac41416cd7 | ||
|
|
518be8ee70 | ||
|
|
eaa23240f7 | ||
|
|
f52bfae8ac | ||
|
|
7287ad3a05 | ||
|
|
8a7577c9bf | ||
|
|
c7d2ddc5db | ||
|
|
8f3036b351 | ||
|
|
ede8cca631 | ||
|
|
723446842f | ||
|
|
4bdd759dae | ||
|
|
a13f43c13b | ||
|
|
1b20fd1ad6 | ||
|
|
aecab2d131 | ||
|
|
9591faf3d4 | ||
|
|
7b85df868d | ||
|
|
0c417ad439 | ||
|
|
73f99f019c | ||
|
|
4c05535a13 | ||
|
|
4c838daa12 | ||
|
|
fd3c9153d5 | ||
|
|
0589df51cd | ||
|
|
4e08e37d74 | ||
|
|
4e26633abb | ||
|
|
debf21e6b2 | ||
|
|
2fdf672015 | ||
|
|
4165e7779f | ||
|
|
5ecea3f69f | ||
|
|
be2fe1de26 | ||
|
|
ae3400a9b1 | ||
|
|
5fdfbaa7f1 | ||
|
|
df6e06aaf6 | ||
|
|
3d1b949f4c | ||
|
|
8e636545f7 | ||
|
|
d7439b4832 | ||
|
|
86bd993b60 | ||
|
|
dc7584d77e | ||
|
|
4fe4da6c01 | ||
|
|
45f4a5ba81 | ||
|
|
b55715775d | ||
|
|
3ae187e6f0 | ||
|
|
46bbede9a0 | ||
|
|
dde361f9f3 | ||
|
|
f1786ad871 | ||
|
|
f843359ba3 | ||
|
|
099b4ecc56 | ||
|
|
3e80dffe34 | ||
|
|
c53799c25b | ||
|
|
732cc57149 | ||
|
|
2469012008 | ||
|
|
ad0a9d0707 | ||
|
|
2ce4d9f0d8 | ||
|
|
7164bb28ac | ||
|
|
508f7b84f2 | ||
|
|
c98cc73f33 | ||
|
|
1bb5de12e3 | ||
|
|
975343d2e9 | ||
|
|
eec071f248 | ||
|
|
b5a26cec8b | ||
|
|
3e81d45ae9 | ||
|
|
c5fed1bbdc | ||
|
|
47ecb9f9b1 | ||
|
|
c70faa2a53 | ||
|
|
9b57fbbf50 | ||
|
|
cc5ec650ae | ||
|
|
d6ada23616 | ||
|
|
65baf46c40 | ||
|
|
872e0b531c | ||
|
|
fc9d70f702 | ||
|
|
3d93f7f0fe | ||
|
|
5c4351f338 | ||
|
|
d326d4380f | ||
|
|
3981a781b9 | ||
|
|
4bad988373 | ||
|
|
fad558a618 | ||
|
|
dc453c193d | ||
|
|
0472cd9996 | ||
|
|
c08e0c0f60 | ||
|
|
0e01374537 | ||
|
|
9aa2dfd96c | ||
|
|
547f6d79ad | ||
|
|
33999d3e93 | ||
|
|
586d146fdb | ||
|
|
ab24d0f625 | ||
|
|
76019af3e5 | ||
|
|
0f579076b6 | ||
|
|
81058a7807 | ||
|
|
22a2fc3177 | ||
|
|
3df7ee06eb | ||
|
|
aba2514534 | ||
|
|
ce20df343a | ||
|
|
9d138278c2 | ||
|
|
16fe21138f | ||
|
|
bc9aa02835 | ||
|
|
c73025c86d | ||
|
|
3acb21d4e1 | ||
|
|
e0623eb302 | ||
|
|
5a37e6cf52 | ||
|
|
3f97b3a1cb | ||
|
|
81cf3d3337 | ||
|
|
bdac352250 | ||
|
|
31981d4116 | ||
|
|
08c294414d | ||
|
|
2ab42e9cd3 | ||
|
|
896c3874af | ||
|
|
a3609079bb | ||
|
|
2c52fd711b | ||
|
|
d7a04ced00 | ||
|
|
3f0c965400 | ||
|
|
4a04dfe4a2 | ||
|
|
597576f5e6 | ||
|
|
502d99de1b | ||
|
|
4d13e8d7f7 | ||
|
|
210feb4034 | ||
|
|
fe0c5615a6 | ||
|
|
70b66d91a0 | ||
|
|
eefc628cf0 | ||
|
|
aedadc3857 | ||
|
|
2eb2d865dc | ||
|
|
f66010a367 | ||
|
|
7836a37d6e | ||
|
|
bf3f658091 | ||
|
|
1767390e0d | ||
|
|
79a180ba73 | ||
|
|
8705f78591 | ||
|
|
39512e1452 | ||
|
|
9200f167bf | ||
|
|
1759a99f65 | ||
|
|
cd0a9a1d62 | ||
|
|
e3fa8f7b24 | ||
|
|
a5e9e7abce | ||
|
|
d673c4ce03 | ||
|
|
cd6b6b74eb | ||
|
|
0b40017b09 | ||
|
|
e59018acfb | ||
|
|
ea54a67798 | ||
|
|
e6aaa196f3 | ||
|
|
34f114faff | ||
|
|
f443223d16 | ||
|
|
6a4421f8e1 | ||
|
|
f5dc654f6b | ||
|
|
f6ffda7504 | ||
|
|
c38660d209 | ||
|
|
a2582047ca | ||
|
|
6a0c73b3a1 | ||
|
|
d042ec42ee | ||
|
|
25311247ed | ||
|
|
60af076fff | ||
|
|
4462b3cc8e | ||
|
|
2edcdeaeb9 | ||
|
|
d8ba4a08c0 | ||
|
|
72b027a887 | ||
|
|
7671465334 | ||
|
|
2c1300cae0 | ||
|
|
35718ce59a | ||
|
|
1412b554a0 | ||
|
|
2c2e56d33a | ||
|
|
ccb96c3795 | ||
|
|
d5e9030768 | ||
|
|
496900df42 | ||
|
|
7acae34ed4 | ||
|
|
d30a68567e | ||
|
|
8a3d2e8e06 | ||
|
|
cad96b7872 | ||
|
|
de538be79c | ||
|
|
4299f113e0 | ||
|
|
b39fe3ed2b | ||
|
|
68d4a4fe42 | ||
|
|
73a8bad1bc | ||
|
|
960f843259 | ||
|
|
d93011a828 | ||
|
|
215737d6c5 | ||
|
|
6369a500b3 | ||
|
|
58243cded0 | ||
|
|
7e6127975d | ||
|
|
f7cd9cb00d | ||
|
|
ecac7a8013 | ||
|
|
a619b9dd48 | ||
|
|
b63e8ef929 | ||
|
|
23d85655a8 | ||
|
|
53cb9a1b7b | ||
|
|
a3a531d122 | ||
|
|
55aee21cff | ||
|
|
b3966b3c76 | ||
|
|
b647a75151 | ||
|
|
baeeb2d649 | ||
|
|
f794c6ae45 | ||
|
|
a0676d5c30 | ||
|
|
698e21a04f | ||
|
|
9d1b716f48 | ||
|
|
c2c06d8f8d | ||
|
|
e5c5cde974 | ||
|
|
16da483674 | ||
|
|
bde5d07afb | ||
|
|
1850a2b533 | ||
|
|
117e6326db | ||
|
|
fe990e00c1 | ||
|
|
4825de7d35 | ||
|
|
7cfff46d02 | ||
|
|
44ea777019 | ||
|
|
711496e9f4 | ||
|
|
01da28010d | ||
|
|
7e0db2aad4 | ||
|
|
144cd6e4f2 | ||
|
|
a9ac176e91 | ||
|
|
af6b21a428 | ||
|
|
8ec25ef161 | ||
|
|
777956f0ab | ||
|
|
acbdee76de | ||
|
|
14230d7b52 | ||
|
|
f7a8899d90 | ||
|
|
7fbb92d071 | ||
|
|
2ec595dedf | ||
|
|
f9b2a4be5e | ||
|
|
ac117c2934 | ||
|
|
dd03c63da2 | ||
|
|
3f9b395b89 | ||
|
|
deebeab9e8 | ||
|
|
647ae2d7f9 | ||
|
|
758eccc659 | ||
|
|
bbc34bd1bf | ||
|
|
7609969491 | ||
|
|
8d8a8530e8 | ||
|
|
bcbbd1474c | ||
|
|
984a0c9247 | ||
|
|
714ae541d4 | ||
|
|
9da11603c2 | ||
|
|
918b739b6c | ||
|
|
25b600f768 | ||
|
|
0dd2e269ee | ||
|
|
6d48cd1c97 | ||
|
|
4e9ebe055b | ||
|
|
e4d91a0226 | ||
|
|
4fd022a93b | ||
|
|
86bfb64a0d | ||
|
|
6114caa938 | ||
|
|
23d2668b38 | ||
|
|
89fcd6d775 | ||
|
|
df8675c2e7 | ||
|
|
203fc3e4b2 | ||
|
|
0d7663c13d | ||
|
|
d1274d559d | ||
|
|
088f32a116 | ||
|
|
3b41894dbd | ||
|
|
093b997eb1 | ||
|
|
1e04deb3d4 | ||
|
|
7bae990c67 | ||
|
|
defb23aaa2 | ||
|
|
aad4ddc46d | ||
|
|
8a3d940aa7 | ||
|
|
3d83fa449d | ||
|
|
0a123548a0 | ||
|
|
03a9d65cfe | ||
|
|
1882b44951 | ||
|
|
4fc0d47087 | ||
|
|
6b1d229a6d | ||
|
|
d84d48bc29 | ||
|
|
d50238cf41 | ||
|
|
5c10712aab | ||
|
|
232a69c518 | ||
|
|
c8bcaaf27d | ||
|
|
e84c92355e | ||
|
|
e883e5fe27 | ||
|
|
dd2086a912 | ||
|
|
aadbcaeec2 | ||
|
|
99bd7bc750 | ||
|
|
f264baa330 | ||
|
|
931d3a0974 | ||
|
|
4a08bd351a | ||
|
|
fad64ba126 | ||
|
|
938559ca11 | ||
|
|
86747acc78 | ||
|
|
6eea33aec9 | ||
|
|
c45780dc59 | ||
|
|
a96ab9ce6f | ||
|
|
aecbd236bc | ||
|
|
a7d333786f | ||
|
|
a45c1111be | ||
|
|
54c9f167ba | ||
|
|
1d910419d1 | ||
|
|
89277f1e25 | ||
|
|
7d37e3e564 | ||
|
|
df37ddcc2c | ||
|
|
794ea00d37 | ||
|
|
e3eb550a7d | ||
|
|
502e9297ad | ||
|
|
56b47ff9be | ||
|
|
93068e1ded |
@@ -15,16 +15,10 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Packr2 artifacts
|
||||
**/*-packr.go
|
||||
|
||||
# GraphQL generated output
|
||||
pkg/models/generated_*.go
|
||||
ui/v2.5/src/core/generated-*.tsx
|
||||
|
||||
# packr generated files
|
||||
*-packr.go
|
||||
|
||||
####
|
||||
# Jetbrains
|
||||
####
|
||||
@@ -60,4 +54,4 @@ ui/v2.5/build
|
||||
stash
|
||||
dist
|
||||
|
||||
docker
|
||||
docker
|
||||
|
||||
197
.github/workflows/build.yml
vendored
Normal file
197
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop, master ]
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COMPILER_IMAGE: stashapp/compiler:5
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Checkout
|
||||
run: git fetch --prune --unshallow --tags
|
||||
|
||||
- name: Pull compiler image
|
||||
run: docker pull $COMPILER_IMAGE
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node_modules
|
||||
with:
|
||||
path: ui/v2.5/node_modules
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock') }}
|
||||
|
||||
- name: Cache UI build
|
||||
uses: actions/cache@v2
|
||||
id: cache-ui
|
||||
env:
|
||||
cache-name: cache-ui
|
||||
with:
|
||||
path: ui/v2.5/build
|
||||
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
|
||||
env:
|
||||
cache-name: cache-go-cache
|
||||
with:
|
||||
path: .go-cache
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Start build container
|
||||
run: |
|
||||
mkdir -p .go-cache
|
||||
docker run -d --name build --mount type=bind,source="$(pwd)",target=/stash,consistency=delegated --mount type=bind,source="$(pwd)/.go-cache",target=/root/.cache/go-build,consistency=delegated -w /stash $COMPILER_IMAGE tail -f /dev/null
|
||||
|
||||
- name: Pre-install
|
||||
run: docker exec -t build /bin/bash -c "make pre-ui"
|
||||
|
||||
- name: Generate
|
||||
run: docker exec -t build /bin/bash -c "make generate"
|
||||
|
||||
- name: Validate UI
|
||||
# skip UI validation for pull requests if UI is unchanged
|
||||
if: ${{ github.event_name != 'pull_request' || steps.cache-ui.outputs.cache-hit != 'true' }}
|
||||
run: docker exec -t build /bin/bash -c "make validate-frontend"
|
||||
|
||||
# Static validation happens in the linter workflow in parallel to this workflow
|
||||
# Run Dynamic validation here, to make sure we pass all the projects integration tests
|
||||
#
|
||||
# create UI file so that the embed doesn't fail
|
||||
- name: Test Backend
|
||||
run: |
|
||||
mkdir -p ui/v2.5/build
|
||||
touch ui/v2.5/build/index.html
|
||||
docker exec -t build /bin/bash -c "make it"
|
||||
|
||||
- name: Build UI
|
||||
# skip UI build for pull requests if UI is unchanged (UI was cached)
|
||||
# this means that the build version/time may be incorrect if the UI is
|
||||
# not changed in a pull request
|
||||
if: ${{ github.event_name != 'pull_request' || steps.cache-ui.outputs.cache-hit != 'true' }}
|
||||
run: docker exec -t build /bin/bash -c "make ui"
|
||||
|
||||
- name: Compile for all supported platforms
|
||||
run: |
|
||||
docker exec -t build /bin/bash -c "make cross-compile-windows"
|
||||
docker exec -t build /bin/bash -c "make cross-compile-osx-intel"
|
||||
docker exec -t build /bin/bash -c "make cross-compile-osx-applesilicon"
|
||||
docker exec -t build /bin/bash -c "make cross-compile-linux"
|
||||
docker exec -t build /bin/bash -c "make cross-compile-linux-arm64v8"
|
||||
docker exec -t build /bin/bash -c "make cross-compile-linux-arm32v7"
|
||||
docker exec -t build /bin/bash -c "make cross-compile-pi"
|
||||
|
||||
- name: Cleanup build container
|
||||
run: docker rm -f -v build
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
git describe --tags --exclude latest_develop | tee CHECKSUMS_SHA1
|
||||
sha1sum dist/stash-* | sed 's/dist\///g' | tee -a CHECKSUMS_SHA1
|
||||
echo "STASH_VERSION=$(git describe --tags --exclude latest_develop)" >> $GITHUB_ENV
|
||||
echo "RELEASE_DATE=$(date +'%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Windows binary
|
||||
# only upload binaries for pull requests
|
||||
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: stash-win.exe
|
||||
path: dist/stash-win.exe
|
||||
|
||||
- name: Upload OSX binary
|
||||
# only upload binaries for pull requests
|
||||
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: stash-osx
|
||||
path: dist/stash-osx
|
||||
|
||||
- name: Upload Linux binary
|
||||
# only upload binaries for pull requests
|
||||
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: stash-linux
|
||||
path: dist/stash-linux
|
||||
|
||||
- name: Update latest_develop tag
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
run : git tag -f latest_develop; git push -f --tags
|
||||
|
||||
- name: Development Release
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
uses: marvinpinto/action-automatic-releases@v1.1.2
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: true
|
||||
automatic_release_tag: latest_develop
|
||||
title: "${{ env.STASH_VERSION }}: Latest development build"
|
||||
files: |
|
||||
dist/stash-osx
|
||||
dist/stash-osx-applesilicon
|
||||
dist/stash-win.exe
|
||||
dist/stash-linux
|
||||
dist/stash-linux-arm64v8
|
||||
dist/stash-linux-arm32v7
|
||||
dist/stash-pi
|
||||
CHECKSUMS_SHA1
|
||||
|
||||
- name: Master release
|
||||
if: ${{ github.event_name == 'release' && github.ref != 'refs/tags/latest_develop' }}
|
||||
uses: meeDamian/github-release@2.0
|
||||
with:
|
||||
token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
allow_override: true
|
||||
files: |
|
||||
dist/stash-osx
|
||||
dist/stash-osx-applesilicon
|
||||
dist/stash-win.exe
|
||||
dist/stash-linux
|
||||
dist/stash-linux-arm64v8
|
||||
dist/stash-linux-arm32v7
|
||||
dist/stash-pi
|
||||
CHECKSUMS_SHA1
|
||||
gzip: false
|
||||
|
||||
- name: Development Docker
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
env:
|
||||
DOCKER_CLI_EXPERIMENTAL: enabled
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
|
||||
docker info
|
||||
docker buildx create --name builder --use
|
||||
docker buildx inspect --bootstrap
|
||||
docker buildx ls
|
||||
bash ./docker/ci/x86_64/docker_push.sh development
|
||||
|
||||
- name: Release Docker
|
||||
if: ${{ github.event_name == 'release' && github.ref != 'refs/tags/latest_develop' }}
|
||||
env:
|
||||
DOCKER_CLI_EXPERIMENTAL: enabled
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
|
||||
docker info
|
||||
docker buildx create --name builder --use
|
||||
docker buildx inspect --bootstrap
|
||||
docker buildx ls
|
||||
bash ./docker/ci/x86_64/docker_push.sh latest "${{ github.event.release.tag_name }}"
|
||||
60
.github/workflows/golangci-lint.yml
vendored
Normal file
60
.github/workflows/golangci-lint.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Lint (golangci-lint)
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
COMPILER_IMAGE: stashapp/compiler:5
|
||||
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Checkout
|
||||
run: git fetch --prune --unshallow --tags
|
||||
|
||||
- name: Pull compiler image
|
||||
run: docker pull $COMPILER_IMAGE
|
||||
|
||||
- name: Start build container
|
||||
run: |
|
||||
mkdir -p .go-cache
|
||||
docker run -d --name build --mount type=bind,source="$(pwd)",target=/stash,consistency=delegated --mount type=bind,source="$(pwd)/.go-cache",target=/root/.cache/go-build,consistency=delegated -w /stash $COMPILER_IMAGE tail -f /dev/null
|
||||
|
||||
- name: Generate Backend
|
||||
run: docker exec -t build /bin/bash -c "make generate-backend"
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: v1.42.1
|
||||
|
||||
# Optional: working directory, useful for monorepos
|
||||
# working-directory: somedir
|
||||
|
||||
# Optional: golangci-lint command line arguments.
|
||||
args: --modules-download-mode=vendor --timeout=3m
|
||||
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
# only-new-issues: true
|
||||
|
||||
# Optional: if set to true then the action will use pre-installed Go.
|
||||
# skip-go-installation: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
|
||||
# skip-pkg-cache: true
|
||||
|
||||
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
|
||||
# skip-build-cache: true
|
||||
|
||||
- name: Cleanup build container
|
||||
run: docker rm -f -v build
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -15,16 +15,10 @@
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Packr2 artifacts
|
||||
**/*-packr.go
|
||||
|
||||
# GraphQL generated output
|
||||
pkg/models/generated_*.go
|
||||
ui/v2.5/src/core/generated-*.tsx
|
||||
|
||||
# packr generated files
|
||||
*-packr.go
|
||||
|
||||
####
|
||||
# Jetbrains
|
||||
####
|
||||
@@ -35,6 +29,7 @@ ui/v2.5/src/core/generated-*.tsx
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.vscode
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
82
.golangci.yml
Normal file
82
.golangci.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
# options for analysis running
|
||||
run:
|
||||
timeout: 3m
|
||||
modules-download-mode: vendor
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
# Default set of linters from golangci-lint
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
# Linters added by the stash project
|
||||
# - bodyclose
|
||||
- dogsled
|
||||
# - errorlint
|
||||
# - exhaustive
|
||||
- exportloopref
|
||||
# - goconst
|
||||
# - gocritic
|
||||
# - goerr113
|
||||
- gofmt
|
||||
# - gosec
|
||||
# - ifshort
|
||||
- misspell
|
||||
# - nakedret
|
||||
# - noctx
|
||||
# - paralleltest
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
|
||||
linters-settings:
|
||||
gofmt:
|
||||
simplify: false
|
||||
|
||||
revive:
|
||||
ignore-generated-header: true
|
||||
severity: error
|
||||
confidence: 0.8
|
||||
error-code: 1
|
||||
warning-code: 1
|
||||
rules:
|
||||
- name: blank-imports
|
||||
disabled: true
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: exported
|
||||
disabled: true
|
||||
- name: if-return
|
||||
disabled: true
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
disabled: true
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
disabled: true
|
||||
- name: indent-error-flow
|
||||
disabled: true
|
||||
- name: errorf
|
||||
- name: empty-block
|
||||
disabled: true
|
||||
- name: superfluous-else
|
||||
- name: unused-parameter
|
||||
disabled: true
|
||||
- name: unreachable-code
|
||||
- name: redefines-builtin-id
|
||||
@@ -29,6 +29,18 @@ builds:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- binary: stash-osx-applesilicon
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=oa64-clang
|
||||
- CXX=oa64-clang++
|
||||
flags:
|
||||
- -tags
|
||||
- extended
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- arm64
|
||||
- binary: stash-linux
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
|
||||
@@ -5,13 +5,28 @@ git:
|
||||
depth: false
|
||||
language: go
|
||||
go:
|
||||
- 1.13.x
|
||||
- 1.17.x
|
||||
services:
|
||||
- docker
|
||||
env:
|
||||
global:
|
||||
- GO111MODULE=on
|
||||
before_install:
|
||||
- set -e
|
||||
# Configure environment so changes are picked up when the Docker daemon is restarted after upgrading
|
||||
- echo '{"experimental":true}' | sudo tee /etc/docker/daemon.json
|
||||
- export DOCKER_CLI_EXPERIMENTAL=enabled
|
||||
# Upgrade to Docker CE 19.03 for BuildKit support
|
||||
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
||||
- sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||
- sudo apt-get update
|
||||
- sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
|
||||
# install binfmt docker container, this container uses qemu to run arm programs transparently allowng docker to build arm 6,7,8 containers.
|
||||
- docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
|
||||
# Show info to simplify debugging and create a builder that can build the platforms we need
|
||||
- docker info
|
||||
- docker buildx create --name builder --use
|
||||
- docker buildx inspect --bootstrap
|
||||
- docker buildx ls
|
||||
|
||||
install:
|
||||
- echo -e "machine github.com\n login $CI_USER_TOKEN" > ~/.netrc
|
||||
- nvm install 12
|
||||
- travis_retry make pre-ui
|
||||
@@ -23,8 +38,10 @@ script:
|
||||
#- make lint
|
||||
- make fmt-check vet it
|
||||
after_success:
|
||||
- docker pull stashapp/compiler:4
|
||||
- docker pull stashapp/compiler:5
|
||||
- sh ./scripts/cross-compile.sh
|
||||
- git describe --tags --exclude latest_develop | tee CHECKSUMS_SHA1
|
||||
- sha1sum dist/stash-* | sed 's/dist\///g' | tee -a CHECKSUMS_SHA1
|
||||
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then sh ./scripts/upload-pull-request.sh; fi'
|
||||
before_deploy:
|
||||
# push the latest tag when on the develop branch
|
||||
@@ -42,11 +59,13 @@ deploy:
|
||||
secure: tGJ2q62CfPdayid2qEtW2aGRhMgCl3lBXYYQqp3eH0vFgIIf6cs7IDX7YC/x3XKMEQ/iMLZmtCXZvSTqNrD6Sk7MSnt30GIs+4uxIZDnnd8mV5X3K4n4gjD+NAORc4DrQBvUGrYMKJsR5gtkH0nu6diWb1o1If7OiJEuCPRhrmQYcza7NUdABnA9Z2wn2RNUV9Ga33WUCqLMEU5GtNBlfQPiP/khCQrqn/ocR6wUjYut3J6YagzqH4wsfJi3glHyWtowcNIw1LZi5zFxHD/bRBT4Tln7yypkjWNq9eQILA6i6kRUGf7ggyTx26/k8n4tnu+QD0vVh4EcjlThpU/LGyUXzKrrxjRwaDZnM0oYxg5AfHcBuAiAdo0eWnV3lEWRfTJMIVb9MPf4qDmzR4RREfB5OXOxwq3ODeCcJE8sTIMD/wBPZrlqS/QrRpND2gn2X4snkVukN9t9F4CMTFMtVSzFV7TDJW5E5Lq6VEExulteQhs6kcK9NRPNAaLgRQAw7X9kVWfDtiGUP+fE2i8F9Bo8bm7sOT5O5VPMPykx3EgeNg1IqIgMTCsMlhMJT4xBJoQUgmd2wWyf3Ryw+P+sFgdb5Sd7+lFgJBjMUUoOxMxAOiEgdFvCXcr+/Udyz2RdtetU1/6VzXzLPcKOw0wubZeBkISqu7o9gpfdMP9Eq00=
|
||||
file:
|
||||
- dist/stash-osx
|
||||
- dist/stash-osx-applesilicon
|
||||
- dist/stash-win.exe
|
||||
- dist/stash-linux
|
||||
- dist/stash-linux-arm64v8
|
||||
- dist/stash-linux-arm32v7
|
||||
- dist/stash-pi
|
||||
- CHECKSUMS_SHA1
|
||||
skip_cleanup: true
|
||||
overwrite: true
|
||||
name: "${STASH_VERSION}: Latest development build"
|
||||
@@ -58,7 +77,7 @@ deploy:
|
||||
# docker image build for develop release
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: bash ./docker/ci/x86_64/docker_push.sh development-x86_64
|
||||
script: bash ./docker/ci/x86_64/docker_push.sh development
|
||||
on:
|
||||
repo: stashapp/stash
|
||||
branch: develop
|
||||
@@ -68,11 +87,13 @@ deploy:
|
||||
secure: tGJ2q62CfPdayid2qEtW2aGRhMgCl3lBXYYQqp3eH0vFgIIf6cs7IDX7YC/x3XKMEQ/iMLZmtCXZvSTqNrD6Sk7MSnt30GIs+4uxIZDnnd8mV5X3K4n4gjD+NAORc4DrQBvUGrYMKJsR5gtkH0nu6diWb1o1If7OiJEuCPRhrmQYcza7NUdABnA9Z2wn2RNUV9Ga33WUCqLMEU5GtNBlfQPiP/khCQrqn/ocR6wUjYut3J6YagzqH4wsfJi3glHyWtowcNIw1LZi5zFxHD/bRBT4Tln7yypkjWNq9eQILA6i6kRUGf7ggyTx26/k8n4tnu+QD0vVh4EcjlThpU/LGyUXzKrrxjRwaDZnM0oYxg5AfHcBuAiAdo0eWnV3lEWRfTJMIVb9MPf4qDmzR4RREfB5OXOxwq3ODeCcJE8sTIMD/wBPZrlqS/QrRpND2gn2X4snkVukN9t9F4CMTFMtVSzFV7TDJW5E5Lq6VEExulteQhs6kcK9NRPNAaLgRQAw7X9kVWfDtiGUP+fE2i8F9Bo8bm7sOT5O5VPMPykx3EgeNg1IqIgMTCsMlhMJT4xBJoQUgmd2wWyf3Ryw+P+sFgdb5Sd7+lFgJBjMUUoOxMxAOiEgdFvCXcr+/Udyz2RdtetU1/6VzXzLPcKOw0wubZeBkISqu7o9gpfdMP9Eq00=
|
||||
file:
|
||||
- dist/stash-osx
|
||||
- dist/stash-osx-applesilicon
|
||||
- dist/stash-win.exe
|
||||
- dist/stash-linux
|
||||
- dist/stash-linux-arm64v8
|
||||
- dist/stash-linux-arm32v7
|
||||
- dist/stash-pi
|
||||
- CHECKSUMS_SHA1
|
||||
# make the release a draft so the maintainers can confirm before releasing
|
||||
draft: true
|
||||
skip_cleanup: true
|
||||
127
Makefile
127
Makefile
@@ -9,7 +9,7 @@ endif
|
||||
ifdef IS_WIN
|
||||
SEPARATOR := &&
|
||||
SET := set
|
||||
else
|
||||
else
|
||||
SEPARATOR := ;
|
||||
SET := export
|
||||
endif
|
||||
@@ -22,7 +22,9 @@ ifdef OUTPUT
|
||||
OUTPUT := -o $(OUTPUT)
|
||||
endif
|
||||
|
||||
.PHONY: release pre-build install clean
|
||||
export CGO_ENABLED = 1
|
||||
|
||||
.PHONY: release pre-build
|
||||
|
||||
release: generate ui build-release
|
||||
|
||||
@@ -41,28 +43,86 @@ endif
|
||||
|
||||
build: pre-build
|
||||
$(eval LDFLAGS := $(LDFLAGS) -X 'github.com/stashapp/stash/pkg/api.version=$(STASH_VERSION)' -X 'github.com/stashapp/stash/pkg/api.buildstamp=$(BUILD_DATE)' -X 'github.com/stashapp/stash/pkg/api.githash=$(GITHASH)')
|
||||
$(SET) CGO_ENABLED=1 $(SEPARATOR) go build $(OUTPUT) -mod=vendor -v -tags "sqlite_omit_load_extension osusergo netgo" -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS)"
|
||||
go build $(OUTPUT) -mod=vendor -v -tags "sqlite_omit_load_extension osusergo netgo" $(GO_BUILD_FLAGS) -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS)"
|
||||
|
||||
# strips debug symbols from the release build
|
||||
# consider -trimpath in go build if we move to go 1.13+
|
||||
build-release: EXTRA_LDFLAGS := -s -w
|
||||
build-release: GO_BUILD_FLAGS := -trimpath
|
||||
build-release: build
|
||||
|
||||
build-release-static: EXTRA_LDFLAGS := -extldflags=-static -s -w
|
||||
build-release-static: GO_BUILD_FLAGS := -trimpath
|
||||
build-release-static: build
|
||||
|
||||
install:
|
||||
packr2 install
|
||||
# cross-compile- targets should be run within the compiler docker container
|
||||
cross-compile-windows: export GOOS := windows
|
||||
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: build-release-static
|
||||
|
||||
clean:
|
||||
packr2 clean
|
||||
cross-compile-osx-intel: export GOOS := darwin
|
||||
cross-compile-osx-intel: export GOARCH := amd64
|
||||
cross-compile-osx-intel: export CC := o64-clang
|
||||
cross-compile-osx-intel: export CXX := o64-clang++
|
||||
cross-compile-osx-intel: OUTPUT := -o dist/stash-osx
|
||||
# can't use static build for OSX
|
||||
cross-compile-osx-intel: build-release
|
||||
|
||||
cross-compile-osx-applesilicon: export GOOS := darwin
|
||||
cross-compile-osx-applesilicon: export GOARCH := arm64
|
||||
cross-compile-osx-applesilicon: export CC := oa64e-clang
|
||||
cross-compile-osx-applesilicon: export CXX := oa64e-clang++
|
||||
cross-compile-osx-applesilicon: OUTPUT := -o dist/stash-osx-applesilicon
|
||||
# can't use static build for OSX
|
||||
cross-compile-osx-applesilicon: build-release
|
||||
|
||||
cross-compile-linux: export GOOS := linux
|
||||
cross-compile-linux: export GOARCH := amd64
|
||||
cross-compile-linux: OUTPUT := -o dist/stash-linux
|
||||
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: build-release-static
|
||||
|
||||
cross-compile-linux-arm32v7: export GOOS := linux
|
||||
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: build-release-static
|
||||
|
||||
cross-compile-pi: export GOOS := linux
|
||||
cross-compile-pi: export GOARCH := arm
|
||||
cross-compile-pi: export GOARM := 6
|
||||
cross-compile-pi: export CC := arm-linux-gnueabi-gcc
|
||||
cross-compile-pi: OUTPUT := -o dist/stash-pi
|
||||
cross-compile-pi: build-release-static
|
||||
|
||||
cross-compile-all:
|
||||
make cross-compile-windows
|
||||
make cross-compile-osx-intel
|
||||
make cross-compile-osx-applesilicon
|
||||
make cross-compile-linux
|
||||
make cross-compile-linux-arm64v8
|
||||
make cross-compile-linux-arm32v7
|
||||
make cross-compile-pi
|
||||
|
||||
# Regenerates GraphQL files
|
||||
.PHONY: generate
|
||||
generate:
|
||||
go generate -mod=vendor
|
||||
generate: generate-backend generate-frontend
|
||||
|
||||
.PHONY: generate-frontend
|
||||
generate-frontend:
|
||||
cd ui/v2.5 && yarn run gqlgen
|
||||
|
||||
.PHONY: generate-backend
|
||||
generate-backend:
|
||||
go generate -mod=vendor
|
||||
|
||||
# Regenerates stash-box client files
|
||||
.PHONY: generate-stash-box-client
|
||||
generate-stash-box-client:
|
||||
@@ -73,23 +133,13 @@ generate-stash-box-client:
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
# Ensures that changed files have had gofmt run on them
|
||||
.PHONY: fmt-check
|
||||
fmt-check:
|
||||
sh ./scripts/check-gofmt.sh
|
||||
|
||||
# Runs go vet on the project's source code.
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go vet -mod=vendor ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
revive -config revive.toml -exclude ./vendor/... ./...
|
||||
golangci-lint run
|
||||
|
||||
# runs unit tests - excluding integration tests
|
||||
.PHONY: test
|
||||
test:
|
||||
test:
|
||||
go test -mod=vendor ./...
|
||||
|
||||
# runs all tests - including integration tests
|
||||
@@ -102,23 +152,19 @@ it:
|
||||
generate-test-mocks:
|
||||
go run -mod=vendor github.com/vektra/mockery/v2 --dir ./pkg/models --name '.*ReaderWriter' --outpkg mocks --output ./pkg/models/mocks
|
||||
|
||||
# installs UI dependencies. Run when first cloning repository, or if UI
|
||||
# installs UI dependencies. Run when first cloning repository, or if UI
|
||||
# dependencies have changed
|
||||
.PHONY: pre-ui
|
||||
pre-ui:
|
||||
cd ui/v2.5 && yarn install --frozen-lockfile
|
||||
|
||||
.PHONY: ui-only
|
||||
ui-only: pre-build
|
||||
.PHONY: ui
|
||||
ui: pre-build
|
||||
$(SET) REACT_APP_DATE="$(BUILD_DATE)" $(SEPARATOR) \
|
||||
$(SET) REACT_APP_GITHASH=$(GITHASH) $(SEPARATOR) \
|
||||
$(SET) REACT_APP_STASH_VERSION=$(STASH_VERSION) $(SEPARATOR) \
|
||||
cd ui/v2.5 && yarn build
|
||||
|
||||
.PHONY: ui
|
||||
ui: ui-only
|
||||
packr2
|
||||
|
||||
.PHONY: ui-start
|
||||
ui-start: pre-build
|
||||
$(SET) REACT_APP_DATE="$(BUILD_DATE)" $(SEPARATOR) \
|
||||
@@ -135,12 +181,19 @@ fmt-ui:
|
||||
ui-validate:
|
||||
cd ui/v2.5 && yarn run validate
|
||||
|
||||
# just repacks the packr files - use when updating migrations and packed files without
|
||||
# rebuilding the UI
|
||||
.PHONY: packr
|
||||
packr:
|
||||
packr2
|
||||
|
||||
# runs all of the tests and checks required for a PR to be accepted
|
||||
.PHONY: validate
|
||||
validate: ui-validate fmt-check vet lint it
|
||||
validate: validate-frontend validate-backend
|
||||
|
||||
# runs all of the frontend PR-acceptance steps
|
||||
.PHONY: validate-frontend
|
||||
validate-frontend: ui-validate
|
||||
|
||||
# runs all of the backend PR-acceptance steps
|
||||
.PHONY: validate-backend
|
||||
validate-backend: lint it
|
||||
|
||||
# locally builds and tags a 'stash/build' docker image
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
docker build -t stash/build -f docker/build/x86_64/Dockerfile .
|
||||
|
||||
106
README.md
106
README.md
@@ -1,79 +1,102 @@
|
||||
# Stash
|
||||
|
||||
[](https://travis-ci.org/stashapp/stash)
|
||||
[](https://goreportcard.com/report/github.com/stashapp/stash)
|
||||
[](https://discord.gg/2TsNFKt)
|
||||
|
||||
https://stashapp.cc
|
||||
|
||||
**Stash is a Go app which organizes and serves your porn.**
|
||||
**Stash is a locally hosted web-based app written in Go which organizes and serves your porn.**
|
||||
|
||||
See a demo [here](https://vimeo.com/275537038) (password is stashapp).
|
||||
* It can gather information about videos in your collection from the internet, and is extensible through the use of community-built plugins for a large number of content producers.
|
||||
* It supports a wide variety of both video and image formats.
|
||||
* You can tag videos and find them later.
|
||||
* It provides statistics about performers, tags, studios and other things.
|
||||
|
||||
An in-app manual is available, and the manual pages can be viewed [here](https://github.com/stashapp/stash/tree/develop/ui/v2.5/src/docs/en).
|
||||
You can [watch a SFW demo video](https://vimeo.com/545323354) to see it in action.
|
||||
|
||||
# Docker install
|
||||
For further information you can [read the in-app manual](ui/v2.5/src/docs/en).
|
||||
|
||||
# Installing stash
|
||||
|
||||
## via Docker
|
||||
|
||||
Follow [this README.md in the docker directory.](docker/production/README.md)
|
||||
|
||||
# Bare-metal Install
|
||||
## Pre-Compiled Binaries
|
||||
|
||||
Stash supports macOS, Windows, and Linux. Download the [latest release here](https://github.com/stashapp/stash/releases).
|
||||
The Stash server runs on macOS, Windows, and Linux. Download the [latest release here](https://github.com/stashapp/stash/releases).
|
||||
|
||||
Run the executable (double click the exe on windows or run `./stash-osx` / `./stash-linux` from the terminal on macOS / Linux) and navigate to either https://localhost:9999 or http://localhost:9999 to get started.
|
||||
|
||||
*Note for Windows users:* Running the app might present a security prompt since the binary isn't signed yet. Just click more info and then the "run anyway" button.
|
||||
*Note for Windows users:* Running the app might present a security prompt since the binary isn't yet signed. Bypass this by clicking "more info" and then the "run anyway" button.
|
||||
|
||||
#### FFMPEG
|
||||
|
||||
If stash is unable to find or download FFMPEG then download it yourself from the link for your platform:
|
||||
|
||||
* [macOS](https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-4.0-macos64-static.zip)
|
||||
* [Windows](https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.0-win64-static.zip)
|
||||
* [Linux](https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz)
|
||||
* [macOS ffmpeg](https://evermeet.cx/ffmpeg/ffmpeg-4.3.1.zip), [macOS ffprobe](https://evermeet.cx/ffmpeg/ffprobe-4.3.1.zip)
|
||||
* [Windows](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip)
|
||||
* [Linux](https://www.johnvansickle.com/ffmpeg/)
|
||||
|
||||
The `ffmpeg(.exe)` and `ffprobe(.exe)` files should be placed in `~/.stash` on macOS / Linux or `C:\Users\YourUsername\.stash` on Windows.
|
||||
|
||||
# Usage
|
||||
|
||||
## Quickstart Guide
|
||||
1) Download and install Stash and its dependencies
|
||||
2) Run Stash. It will prompt you for some configuration options and a directory to index (you can also do this step afterward)
|
||||
3) After configuration, launch your web browser and navigate to the URL shown within the Stash app.
|
||||
|
||||
**Note that Stash does not currently retrieve and organize information about your entire library automatically.** You will need to help it along through the use of [scrapers](blob/develop/ui/v2.5/src/docs/en/Scraping.md). The Stash community has developed scrapers for many popular data sources which can be downloaded and installed from [this repository](https://github.com/stashapp/CommunityScrapers).
|
||||
|
||||
The simplest way to tag a large number of files is by using the [Tagger](https://github.com/stashapp/stash/blob/develop/ui/v2.5/src/docs/en/Tagger.md) which uses filename keywords to help identify the file and pull in scene and performer information from our stash-box database. Note that this data source is not comprehensive and you may need to use the scrapers to identify some of your media.
|
||||
|
||||
## CLI
|
||||
|
||||
Stash provides some command line options. See what is currently available by running `stash --help`.
|
||||
Stash runs as a command-line app and local web server. There are some command-line options available, which you can see by running `stash --help`.
|
||||
|
||||
For example, to run stash locally on port 80 run it like this (OSX / Linux) `stash --host 127.0.0.1 --port 80`
|
||||
|
||||
## SSL (HTTPS)
|
||||
|
||||
Stash supports HTTPS with some additional work. First you must generate a SSL certificate and key combo. Here is an example using openssl:
|
||||
Stash can run over HTTPS with some additional work. First you must generate a SSL certificate and key combo. Here is an example using openssl:
|
||||
|
||||
`openssl req -x509 -newkey rsa:4096 -sha256 -days 7300 -nodes -keyout stash.key -out stash.crt -extensions san -config <(echo "[req]"; echo distinguished_name=req; echo "[san]"; echo subjectAltName=DNS:stash.server,IP:127.0.0.1) -subj /CN=stash.server`
|
||||
|
||||
This command would need customizing for your environment. [This link](https://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl) might be useful.
|
||||
|
||||
Once you have a certificate and key file name them `stash.crt` and `stash.key` and place them in the `~/.stash` directory. Stash detects these and starts up using HTTPS rather than HTTP.
|
||||
Once you have a certificate and key file name them `stash.crt` and `stash.key` and place them in the same directory as the `config.yml` file, or the `~/.stash` directory. Stash detects these and starts up using HTTPS rather than HTTP.
|
||||
|
||||
# FAQ
|
||||
## Basepath rewriting
|
||||
|
||||
> I'm unable to run the app on OSX or Linux
|
||||
The basepath defaults to `/`. When running stash via a reverse proxy in a subpath, the basepath can be changed by having the reverse proxy pass `X-Forwarded-Prefix` (and optionally `X-Forwarded-Port`) headers. When detects these headers, it alters the basepath URL of the UI.
|
||||
|
||||
Try running `chmod u+x stash-osx` or `chmod u+x stash-linux` to make the file executable.
|
||||
# Customization
|
||||
|
||||
> I have a question not answered here.
|
||||
## Themes and CSS Customization
|
||||
There is a [directory of community-created themes](https://github.com/stashapp/stash/wiki/Themes) on our Wiki, along with instructions on how to install them.
|
||||
|
||||
Join the [Discord server](https://discord.gg/2TsNFKt).
|
||||
You can also make Stash interface fit your desired style with [Custom CSS snippets](https://github.com/stashapp/stash/wiki/Custom-CSS-snippets) and [CSS Tweaks](https://github.com/stashapp/stash/wiki/CSS-Tweaks).
|
||||
|
||||
# Development
|
||||
# Support (FAQ)
|
||||
|
||||
## Install
|
||||
Answers to other Frequently Asked Questions can be found [on our Wiki](https://github.com/stashapp/stash/wiki/FAQ)
|
||||
|
||||
For issues not addressed there, there are a few options.
|
||||
|
||||
* Read the [Wiki](https://github.com/stashapp/stash/wiki)
|
||||
* Check the in-app documentation (also available [here](https://github.com/stashapp/stash/tree/develop/ui/v2.5/src/docs/en)
|
||||
* Join the [Discord server](https://discord.gg/2TsNFKt), where the community can offer support.
|
||||
|
||||
# Compiling From Source Code
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
* [Go](https://golang.org/dl/)
|
||||
* [Revive](https://github.com/mgechev/revive) - Configurable linter
|
||||
* Go Install: `go get github.com/mgechev/revive`
|
||||
* [Packr2](https://github.com/gobuffalo/packr/tree/v2.0.2/v2) - Static asset bundler
|
||||
* Go Install: `go get github.com/gobuffalo/packr/v2/packr2@v2.0.2`
|
||||
* [Binary Download](https://github.com/gobuffalo/packr/releases)
|
||||
* [GolangCI](https://golangci-lint.run/) - A meta-linter which runs several linters in parallel
|
||||
* To install, follow the [local installation instructions](https://golangci-lint.run/usage/install/#local-installation)
|
||||
* [Yarn](https://yarnpkg.com/en/docs/install) - Yarn package manager
|
||||
* Run `yarn install --frozen-lockfile` in the `stash/ui/v2.5` folder (before running make generate for first time).
|
||||
* Run `yarn install --frozen-lockfile` in the `stash/ui/v2.5` folder (before running make generate for first time).
|
||||
|
||||
NOTE: You may need to run the `go get` commands outside the project directory to avoid modifying the projects module file.
|
||||
|
||||
@@ -89,8 +112,7 @@ TODO
|
||||
2. Download and install [MingW](https://sourceforge.net/projects/mingw-w64/)
|
||||
3. Search for "advanced system settings" and open the system properties dialog.
|
||||
1. Click the `Environment Variables` button
|
||||
2. Add `GO111MODULE=on`
|
||||
3. Under system variables find the `Path`. Edit and add `C:\Program Files\mingw-w64\*\mingw64\bin` (replace * with the correct path).
|
||||
2. Under system variables find the `Path`. Edit and add `C:\Program Files\mingw-w64\*\mingw64\bin` (replace * with the correct path).
|
||||
|
||||
NOTE: The `make` command in Windows will be `mingw32-make` with MingW.
|
||||
|
||||
@@ -98,34 +120,38 @@ NOTE: The `make` command in Windows will be `mingw32-make` with MingW.
|
||||
|
||||
* `make generate` - Generate Go and UI GraphQL files
|
||||
* `make build` - Builds the binary (make sure to build the UI as well... see below)
|
||||
* `make docker-build` - Locally builds and tags a complete 'stash/build' docker image
|
||||
* `make pre-ui` - Installs the UI dependencies. Only needs to be run once before building the UI for the first time, or if the dependencies are updated
|
||||
* `make fmt-ui` - Formats the UI source code.
|
||||
* `make ui` - Builds the frontend and the packr2 files
|
||||
* `make packr` - Generate packr2 files (sub-target of `ui`. Use to regenerate packr2 files without rebuilding UI)
|
||||
* `make vet` - Run `go vet`
|
||||
* `make lint` - Run the linter
|
||||
* `make fmt-ui` - Formats the UI source code
|
||||
* `make ui` - Builds the frontend
|
||||
* `make lint` - Run the linter on the backend
|
||||
* `make fmt` - Run `go fmt`
|
||||
* `make fmt-check` - Ensure changed files are formatted correctly
|
||||
* `make it` - Run the unit and integration tests
|
||||
* `make validate` - Run all of the tests and checks required to submit a PR
|
||||
* `make ui-start` - Runs the UI in development mode. Requires a running stash server to connect to. Stash port can be changed from the default of `9999` with environment variable `REACT_APP_PLATFORM_PORT`.
|
||||
|
||||
## Building a release
|
||||
|
||||
1. Run `make generate` to create generated files
|
||||
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
|
||||
|
||||
## Cross compiling
|
||||
|
||||
This project uses a modification of [this](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) docker container to create an environment
|
||||
This project uses a modification of the [CI-GoReleaser](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) docker container to create an environment
|
||||
where the app can be cross-compiled. This process is kicked off by CI via the `scripts/cross-compile.sh` script. Run the following
|
||||
command to open a bash shell to the container to poke around:
|
||||
|
||||
`docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -i -t stashappdev/compiler:latest /bin/bash`
|
||||
|
||||
## Customization
|
||||
## Profiling
|
||||
|
||||
You can make Stash interface fit your desired style with [Custom CSS snippets](https://github.com/stashapp/stash/wiki/Custom-CSS-snippets) and [CSS Tweaks](https://github.com/stashapp/stash/wiki/CSS-Tweaks).
|
||||
Stash can be profiled using the `--cpuprofile <output profile filename>` command line flag.
|
||||
|
||||
[Stash Plex Theme](https://github.com/stashapp/stash/wiki/Stash-Plex-Theme) is a community created theme inspired by popular Plex Interface.
|
||||
The resulting file can then be used with pprof as follows:
|
||||
|
||||
`go tool pprof <path to binary> <path to profile filename>`
|
||||
|
||||
With `graphviz` installed and in the path, a call graph can be generated with:
|
||||
|
||||
`go tool pprof -svg <path to binary> <path to profile filename> > <output svg file>`
|
||||
|
||||
@@ -1,59 +1,43 @@
|
||||
# this dockerfile must be built from the top-level stash directory
|
||||
# ie from top=level stash:
|
||||
# This dockerfile must be built from the top-level stash directory
|
||||
# ie from top-level stash:
|
||||
# docker build -t stash/build -f docker/build/x86_64/Dockerfile .
|
||||
|
||||
FROM golang:1.13.15 as compiler
|
||||
|
||||
RUN apt-get update && apt-get install -y apt-transport-https
|
||||
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
|
||||
|
||||
# prevent caching of the key
|
||||
ADD https://dl.yarnpkg.com/debian/pubkey.gpg yarn.gpg
|
||||
RUN cat yarn.gpg | apt-key add - && \
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
||||
rm yarn.gpg
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y nodejs yarn xz-utils --no-install-recommends || exit 1; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
ENV PACKR2_VERSION=2.0.2
|
||||
ENV PACKR2_SHA=f95ff4c96d7a28813220df030ad91700b8464fe292ab3e1dc9582305c2a338d2
|
||||
ENV PACKR2_DOWNLOAD_FILE=packr_${PACKR2_VERSION}_linux_amd64.tar.gz
|
||||
ENV PACKR2_DOWNLOAD_URL=https://github.com/gobuffalo/packr/releases/download/v${PACKR2_VERSION}/${PACKR2_DOWNLOAD_FILE}
|
||||
# Build Frontend
|
||||
FROM node:alpine as frontend
|
||||
RUN apk add --no-cache make git
|
||||
## cache node_modules separately
|
||||
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
|
||||
WORKDIR /stash
|
||||
RUN yarn --cwd ui/v2.5 install --frozen-lockfile.
|
||||
COPY Makefile /stash/
|
||||
COPY ./.git /stash/.git
|
||||
COPY ./graphql /stash/graphql/
|
||||
COPY ./ui /stash/ui/
|
||||
RUN make generate-frontend
|
||||
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
|
||||
|
||||
# Build Backend
|
||||
FROM golang:1.17-alpine as backend
|
||||
RUN apk add --no-cache xz make alpine-sdk
|
||||
## install ffmpeg
|
||||
WORKDIR /
|
||||
RUN wget ${PACKR2_DOWNLOAD_URL}; \
|
||||
echo "$PACKR2_SHA $PACKR2_DOWNLOAD_FILE" | sha256sum -c - || exit 1; \
|
||||
tar -xzf $PACKR2_DOWNLOAD_FILE -C /usr/bin/ packr2; \
|
||||
rm $PACKR2_DOWNLOAD_FILE;
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
RUN wget -O /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
|
||||
tar xf /ffmpeg.tar.xz && \
|
||||
rm ffmpeg.tar.xz && \
|
||||
mv /ffmpeg*/ /ffmpeg/
|
||||
|
||||
# copy the ui yarn stuff so that it doesn't get rebuilt every time
|
||||
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
|
||||
|
||||
WORKDIR /stash
|
||||
RUN yarn --cwd ui/v2.5 install --frozen-lockfile
|
||||
|
||||
COPY . /stash/
|
||||
ENV GO111MODULE=on
|
||||
|
||||
RUN make generate
|
||||
RUN make ui
|
||||
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
|
||||
COPY ./scripts /stash/scripts/
|
||||
COPY ./vendor /stash/vendor/
|
||||
COPY ./pkg /stash/pkg/
|
||||
COPY --from=frontend /stash /stash/
|
||||
RUN make generate-backend
|
||||
RUN make build
|
||||
|
||||
FROM ubuntu:20.04 as app
|
||||
|
||||
RUN apt-get update && apt-get -y install ca-certificates
|
||||
COPY --from=compiler /stash/stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
|
||||
|
||||
# Final Runnable Image
|
||||
FROM alpine:latest
|
||||
RUN apk add --no-cache ca-certificates vips-tools
|
||||
COPY --from=backend /stash/stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
|
||||
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
|
||||
EXPOSE 9999
|
||||
CMD ["stash"]
|
||||
|
||||
|
||||
ENTRYPOINT ["stash"]
|
||||
@@ -1,25 +1,22 @@
|
||||
|
||||
# must be built from /dist directory
|
||||
|
||||
FROM ubuntu:20.04 as prep
|
||||
LABEL MAINTAINER="https://discord.gg/Uz29ny"
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install curl xz-utils && \
|
||||
apt-get autoclean -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
FROM --platform=$BUILDPLATFORM ubuntu:20.04 AS prep
|
||||
ARG TARGETPLATFORM
|
||||
WORKDIR /
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
|
||||
tar xf /ffmpeg.tar.xz && \
|
||||
rm ffmpeg.tar.xz && \
|
||||
mv /ffmpeg*/ /ffmpeg/
|
||||
|
||||
COPY stash-* /
|
||||
RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then BIN=stash-pi; \
|
||||
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then BIN=stash-linux-arm32v7; \
|
||||
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then BIN=stash-linux-arm64v8; \
|
||||
elif [ "$TARGETPLATFORM" = "linux/amd64" ]; then BIN=stash-linux; \
|
||||
fi; \
|
||||
mv $BIN /stash
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt install -y python3 python-is-python3 python3-requests python3-requests-toolbelt python3-lxml python3-pip && pip3 install cloudscraper
|
||||
FROM ubuntu:20.04 as app
|
||||
RUN apt-get update && apt-get -y install ca-certificates
|
||||
COPY --from=prep /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
|
||||
COPY /stash-linux /usr/bin/stash
|
||||
run apt update && apt install -y python3 python-is-python3 python3-requests python3-requests-toolbelt python3-lxml python3-mechanicalsoup ffmpeg libvips-tools && rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=prep /stash /usr/bin/
|
||||
COPY --from=prep /usr/local/lib/python3.8/dist-packages /usr/local/lib/python3.8/dist-packages
|
||||
|
||||
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
|
||||
|
||||
EXPOSE 9999
|
||||
CMD ["stash"]
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
DOCKER_TAG=$1
|
||||
DOCKER_TAGS=""
|
||||
|
||||
# must build the image from dist directory
|
||||
docker build -t stashapp/stash:$DOCKER_TAG -f ./docker/ci/x86_64/Dockerfile ./dist
|
||||
for TAG in "$@"
|
||||
do
|
||||
DOCKER_TAGS="$DOCKER_TAGS -t stashapp/stash:$TAG"
|
||||
done
|
||||
|
||||
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
docker push stashapp/stash:$DOCKER_TAG
|
||||
|
||||
# must build the image from dist directory
|
||||
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 --push $DOCKER_TAGS -f docker/ci/x86_64/Dockerfile dist/
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
FROM golang:1.13.15
|
||||
FROM golang:1.17
|
||||
|
||||
LABEL maintainer="stashappdev@gmail.com"
|
||||
|
||||
ENV PACKR2_VERSION=2.0.2
|
||||
ENV PACKR2_SHA=f95ff4c96d7a28813220df030ad91700b8464fe292ab3e1dc9582305c2a338d2
|
||||
ENV PACKR2_DOWNLOAD_FILE=packr_${PACKR2_VERSION}_linux_amd64.tar.gz
|
||||
ENV PACKR2_DOWNLOAD_URL=https://github.com/gobuffalo/packr/releases/download/v${PACKR2_VERSION}/${PACKR2_DOWNLOAD_FILE}
|
||||
LABEL maintainer="https://discord.gg/2TsNFKt"
|
||||
|
||||
# Install tools
|
||||
RUN apt-get update && apt-get install -y apt-transport-https
|
||||
@@ -18,10 +13,10 @@ RUN cat yarn.gpg | apt-key add - && \
|
||||
rm yarn.gpg
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y automake autogen \
|
||||
apt-get install -y automake autogen cmake \
|
||||
libtool libxml2-dev uuid-dev libssl-dev bash \
|
||||
patch make tar xz-utils bzip2 gzip sed cpio \
|
||||
gcc-8-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \
|
||||
patch make tar xz-utils bzip2 gzip zlib1g-dev sed cpio \
|
||||
gcc-10-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \
|
||||
gcc-arm-linux-gnueabi libc-dev-armel-cross linux-libc-dev-armel-cross \
|
||||
gcc-arm-linux-gnueabihf libc-dev-armhf-cross \
|
||||
gcc-aarch64-linux-gnu libc-dev-arm64-cross \
|
||||
@@ -29,21 +24,22 @@ RUN apt-get update && \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
# Cross compile setup
|
||||
ENV OSX_SDK_VERSION 10.11
|
||||
ENV OSX_SDK_VERSION 11.3
|
||||
ENV OSX_SDK_DOWNLOAD_FILE=MacOSX${OSX_SDK_VERSION}.sdk.tar.xz
|
||||
ENV OSX_SDK_DOWNLOAD_URL=https://github.com/ndeloof/golang-cross/raw/113fix/${OSX_SDK_DOWNLOAD_FILE}
|
||||
ENV OSX_SDK_SHA=98cdd56e0f6c1f9e1af25e11dd93d2e7d306a4aa50430a2bc6bc083ac67efbb8
|
||||
ENV OSX_SDK_DOWNLOAD_URL=https://github.com/phracker/MacOSX-SDKs/releases/download/${OSX_SDK_VERSION}/${OSX_SDK_DOWNLOAD_FILE}
|
||||
ENV OSX_SDK_SHA=cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4
|
||||
ENV OSX_SDK MacOSX$OSX_SDK_VERSION.sdk
|
||||
ENV OSX_NDK_X86 /usr/local/osx-ndk-x86
|
||||
|
||||
RUN wget ${OSX_SDK_DOWNLOAD_URL}
|
||||
RUN echo "$OSX_SDK_SHA $OSX_SDK_DOWNLOAD_FILE" | sha256sum -c - || exit 1; \
|
||||
git clone https://github.com/tpoechtrager/osxcross.git && \
|
||||
git -C osxcross checkout a9317c18a3a457ca0a657f08cc4d0d43c6cf8953 || exit 1; \
|
||||
mv $OSX_SDK_DOWNLOAD_FILE osxcross/tarballs/ && \
|
||||
UNATTENDED=yes SDK_VERSION=${OSX_SDK_VERSION} OSX_VERSION_MIN=10.9 osxcross/build.sh || exit 1; \
|
||||
mv osxcross/target $OSX_NDK_X86; \
|
||||
rm -rf osxcross;
|
||||
git clone https://github.com/tpoechtrager/osxcross.git; \
|
||||
mv $OSX_SDK_DOWNLOAD_FILE osxcross/tarballs/
|
||||
|
||||
RUN UNATTENDED=yes SDK_VERSION=${OSX_SDK_VERSION} OSX_VERSION_MIN=10.10 osxcross/build.sh || exit 1;
|
||||
RUN cp osxcross/target/lib/* /usr/lib/ ; \
|
||||
mv osxcross/target $OSX_NDK_X86; \
|
||||
rm -rf osxcross;
|
||||
|
||||
ENV PATH $OSX_NDK_X86/bin:$PATH
|
||||
|
||||
@@ -51,14 +47,6 @@ RUN mkdir -p /root/.ssh; \
|
||||
chmod 0700 /root/.ssh; \
|
||||
ssh-keyscan github.com > /root/.ssh/known_hosts;
|
||||
|
||||
RUN wget ${PACKR2_DOWNLOAD_URL}; \
|
||||
echo "$PACKR2_SHA $PACKR2_DOWNLOAD_FILE" | sha256sum -c - || exit 1; \
|
||||
tar -xzf $PACKR2_DOWNLOAD_FILE -C /usr/bin/ packr2; \
|
||||
rm $PACKR2_DOWNLOAD_FILE;
|
||||
|
||||
CMD ["packr2", "version"]
|
||||
|
||||
|
||||
# Notes for self:
|
||||
# Windows:
|
||||
# GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags "-extldflags '-static'" -tags extended
|
||||
@@ -66,4 +54,4 @@ CMD ["packr2", "version"]
|
||||
|
||||
# Darwin
|
||||
# CC=o64-clang CXX=o64-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -tags extended
|
||||
# env GO111MODULE=on goreleaser --config=goreleaser-extended.yml --skip-publish --skip-validate --rm-dist --release-notes=temp/0.48-relnotes-ready.md
|
||||
# env goreleaser --config=goreleaser-extended.yml --skip-publish --skip-validate --rm-dist --release-notes=temp/0.48-relnotes-ready.md
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
user=stashapp
|
||||
repo=compiler
|
||||
version=4
|
||||
version=5
|
||||
|
||||
latest:
|
||||
docker build -t ${user}/${repo}:latest .
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
Modified from https://github.com/bep/dockerfiles/tree/master/ci-goreleaser
|
||||
|
||||
When the dockerfile is changed, the version number should be incremented in the Makefile and the new version tag should be pushed to docker hub. The `scripts/cross-compile.sh` script should also be updated to use the new version number tag, and `.travis.yml` needs to be updated to pull the correct image tag.
|
||||
When the dockerfile is changed, the version number should be incremented in the Makefile and the new version tag should be pushed to docker hub. The `scripts/cross-compile.sh` script should also be updated to use the new version number tag, and `.travis.yml` needs to be updated to pull the correct image tag.
|
||||
|
||||
A MacOS univeral binary can be created using `lipo -create -output stash-osx-universal stash-osx stash-osx-applesilicon`, available in the image.
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TMP=$(mktemp -d /tmp/XXXXXXXXXXX)
|
||||
SDK="MacOSX10.11.sdk"
|
||||
|
||||
mkdir -p $TMP/$SDK/usr/include/c++
|
||||
|
||||
cp -rf /Applications/Xcode7.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/$SDK $TMP &>/dev/null || true
|
||||
cp -rf /Applications/Xcode7.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1 $TMP/$SDK/usr/include/c++ || exit -1
|
||||
|
||||
tar -C $TMP -czf $SDK.tar.gz $SDK
|
||||
@@ -1,24 +0,0 @@
|
||||
FROM ubuntu:20.04 as prep
|
||||
LABEL MAINTAINER="https://discord.gg/Uz29ny"
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install curl xz-utils && \
|
||||
apt-get autoclean -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# added " to end of stash-linux clause so that it doesn't pick up the arm builds
|
||||
RUN curl -L -o /stash $(curl -s https://api.github.com/repos/stashapp/stash/releases/tags/latest_develop | awk '/browser_download_url/ && /stash-linux"/' | sed -e 's/.*: "\(.*\)"/\1/') && \
|
||||
chmod +x /stash
|
||||
|
||||
RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
|
||||
tar xf /ffmpeg.tar.xz && \
|
||||
rm ffmpeg.tar.xz && \
|
||||
mv /ffmpeg*/ /ffmpeg/
|
||||
|
||||
FROM ubuntu:20.04 as app
|
||||
RUN apt-get update && apt-get -y install ca-certificates
|
||||
COPY --from=prep /stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
|
||||
EXPOSE 9999
|
||||
CMD ["stash"]
|
||||
@@ -1,53 +1,37 @@
|
||||
# Docker install on Ubuntu 18.04
|
||||
Installing StashApp can likely work on others if your OS either has it's own package manager or comes shipped with Docker and docker-compose.
|
||||
# Docker Installation (for most 64-bit GNU/Linux systems)
|
||||
StashApp is supported on most systems that support Docker and docker-compose. Your OS likely ships with or makes available the necessary packages.
|
||||
|
||||
## Dependencies
|
||||
The goal is to avoid as many dependencies as possible so for now the only pre-requisites you are required to have are `curl`, `docker`, and `docker-compose` for the most part your understanding of the technologies can be superficial so long as you can follow commands and are open to reading a bit you should be fine.
|
||||
Only `docker` and `docker-compose` are required. For the most part your understanding of the technologies can be superficial. So long as you can follow commands and are open to reading a bit, you should be fine.
|
||||
|
||||
Installation instructions are available below, and if your distrobution's repository ships a current version of docker, you may use that.
|
||||
https://docs.docker.com/engine/install/
|
||||
|
||||
### Docker
|
||||
Docker is effectively a cross-platform software package repository. It allows you to ship an entire environment in what's referred to as a container. Containers are intended to hold everything that is needed to run an application from one place to another, making it easy for everyone along the way to reproduce the environment.
|
||||
|
||||
Docker is effectively the cross-platform software package repository it allows you to ship an entire environment in what's referred to as a container. Containers are intended to hold everything that is needed to ship what's required to run an application from one place to another with a degree of a standard that makes it easy for everyone along the way to reproduce the environment for their step in the chain.
|
||||
The StashApp docker container ships with everything you need to automatically build and run stash, including ffmpeg.
|
||||
|
||||
The other side of docker is it brings everything that we would typically have to teach you about the individual components of your soon to be installed StashApp and ffmpeg, docker-compose wraps it up nicely in a handful of easy to follow steps that should result in the same environment on everyone's host.
|
||||
### docker-compose
|
||||
Docker Compose lets you specify how and where to run your containers, and to manage their environment. The docker-compose.yml file in this folder gets you a fully working instance of StashApp exactly as you would need it to have a reasonable instance for testing / developing on. If you are deploying a live instance for production, a reverse proxy (such as NGINX or Traefik) is recommended, but not required.
|
||||
|
||||
The installation method we recommend is via the `docker.com` website however if your specific operating system's repository versions are at the latest along with docker you should be good to launch with you using whatever instructions you wish. The version of Docker we used in our deployment for testing this process was `Docker version 17.05.0-ce, build 89658be` however any versions later than this will be sufficient. At the writing of this tutorial, this was not the latest version of Docker.
|
||||
|
||||
#### Just the link to installation instructions, please
|
||||
Instructions for installing on Ubuntu are at the link that follows:
|
||||
https://docs.docker.com/install/linux/docker-ce/ubuntu/
|
||||
|
||||
If you plan on using other versions of OS you should at least aim to be a Linux base with an x86_64 CPU and the appropriate minimum version of the dependencies.
|
||||
|
||||
### Docker-compose
|
||||
Docker Compose's role in this deployment is to get you a fully working instance of StashApp exactly as you would need it to have a reasonable instance for testing / developing on, you could technically deploy a live instance with this, but without a reverse proxy, is not recommended. You are encouraged to learn how to use the Docker-Compose format, but it's not a required prerequisite for getting this running you need to have it installed successfully.
|
||||
|
||||
Install Docker Compose via this guide below, and it is essential if you're using an older version of Linux to use the official documentation from Docker.com because you require the more recent version of docker-compose at least version 3.4 aka 1.22.0 or newer.
|
||||
|
||||
#### Just the link to installation instructions, please
|
||||
https://docs.docker.com/compose/install/
|
||||
|
||||
### Install curl
|
||||
This one's easy, copy paste.
|
||||
|
||||
```
|
||||
apt update -y && \
|
||||
apt install -f curl
|
||||
```
|
||||
The latest version is always recommended.
|
||||
|
||||
### Get the docker-compose.yml file
|
||||
|
||||
Now you can either navigate to the [docker-compose.yml](https://raw.githubusercontent.com/stashapp/stash/master/docker/production/docker-compose.yml) in the repository, OR you can make your Linux console do it for you with this.
|
||||
Now you can either navigate to the [docker-compose.yml](https://raw.githubusercontent.com/stashapp/stash/master/docker/production/docker-compose.yml) in the repository, or if you have curl, you can make your Linux console do it for you:
|
||||
|
||||
```
|
||||
curl -o ~/docker-compose.yml https://raw.githubusercontent.com/stashapp/stash/master/docker/production/docker-compose.yml
|
||||
mkdir stashapp && cd stashapp
|
||||
curl -o docker-compose.yml https://raw.githubusercontent.com/stashapp/stash/master/docker/production/docker-compose.yml
|
||||
```
|
||||
|
||||
Once you have that file where you want it, you can either modify the settings as you please OR you can run the following to get it up and running instantly.
|
||||
Once you have that file where you want it, modify the settings as you please, and then run:
|
||||
|
||||
```
|
||||
cd ~ && docker-compose up -d
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Installing StashApp this way will by default bind stash to port 9999 or in web browser terms. http://YOURIP:9999 or if you're doing this on your machine locally which is the only recommended production version of this container as is with no security configurations set at all is http://localhost:9999
|
||||
Installing StashApp this way will by default bind stash to port 9999. This is available in your web browser locally at http://localhost:9999 or on your network as http://YOUR-LOCAL-IP:9999
|
||||
|
||||
Good luck and have fun!
|
||||
|
||||
@@ -4,9 +4,13 @@ version: '3.4'
|
||||
services:
|
||||
stash:
|
||||
image: stashapp/stash:latest
|
||||
container_name: stash
|
||||
restart: unless-stopped
|
||||
## the container's port must be the same with the STASH_PORT in the environment section
|
||||
ports:
|
||||
- "9999:9999"
|
||||
## If you intend to use stash's DLNA functionality uncomment the below network mode and comment out the above ports section
|
||||
# network_mode: host
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
@@ -17,12 +21,14 @@ services:
|
||||
- STASH_GENERATED=/generated/
|
||||
- STASH_METADATA=/metadata/
|
||||
- STASH_CACHE=/cache/
|
||||
## Adjust below to change default port (9999)
|
||||
- STASH_PORT=9999
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
## Adjust below paths (the left part) to your liking.
|
||||
## E.g. you can change ./config:/root/.stash to ./stash:/root/.stash
|
||||
|
||||
## Keep configs here.
|
||||
## Keep configs, scrapers, and plugins here.
|
||||
- ./config:/root/.stash
|
||||
## Point this at your collection.
|
||||
- ./data:/data
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM ubuntu:20.04 as prep
|
||||
LABEL MAINTAINER="leopere [at] nixc [dot] us"
|
||||
LABEL MAINTAINER="https://discord.gg/2TsNFKt"
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y install curl xz-utils && \
|
||||
@@ -20,5 +20,8 @@ RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/f
|
||||
FROM ubuntu:20.04 as app
|
||||
RUN apt-get update && apt-get -y install ca-certificates
|
||||
COPY --from=prep /stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
|
||||
|
||||
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
|
||||
|
||||
EXPOSE 9999
|
||||
CMD ["stash"]
|
||||
|
||||
88
go.mod
88
go.mod
@@ -3,39 +3,95 @@ module github.com/stashapp/stash
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.12.2
|
||||
github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953
|
||||
github.com/anacrolix/dms v1.2.2
|
||||
github.com/antchfx/htmlquery v1.2.3
|
||||
github.com/bmatcuk/doublestar/v2 v2.0.1
|
||||
github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c
|
||||
github.com/chromedp/chromedp v0.5.3
|
||||
github.com/chromedp/cdproto v0.0.0-20210622022015-fe1827b46b84
|
||||
github.com/chromedp/chromedp v0.7.3
|
||||
github.com/corona10/goimagehash v1.0.3
|
||||
github.com/disintegration/imaging v1.6.0
|
||||
github.com/fvbommel/sortorder v1.0.2
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/gobuffalo/packr/v2 v2.0.2
|
||||
github.com/golang-migrate/migrate/v4 v4.3.1
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0
|
||||
github.com/golang-migrate/migrate/v4 v4.15.0-beta.1
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/gorilla/sessions v1.2.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/h2non/filetype v1.0.8
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/jmoiron/sqlx v1.3.1
|
||||
github.com/json-iterator/go v1.1.9
|
||||
github.com/mattn/go-sqlite3 v1.13.0
|
||||
github.com/mattn/go-sqlite3 v1.14.6
|
||||
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
|
||||
github.com/rs/cors v1.6.0
|
||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/afero v1.2.0 // indirect
|
||||
github.com/spf13/pflag v1.0.3
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tidwall/gjson v1.6.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/tidwall/gjson v1.8.1
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.0.1
|
||||
github.com/vektra/mockery/v2 v2.2.1
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/tools v0.0.0-20200915031644-64986481280e // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/agnivade/levenshtein v1.1.0 // indirect
|
||||
github.com/antchfx/xpath v1.1.6 // indirect
|
||||
github.com/chromedp/sysutil v1.0.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.7 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.1.0-rc.5 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/pelletier/go-toml v1.7.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rs/zerolog v1.18.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/spf13/cast v1.3.0 // indirect
|
||||
github.com/spf13/cobra v1.0.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/tidwall/match v1.0.3 // indirect
|
||||
github.com/urfave/cli/v2 v2.1.1 // indirect
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e // indirect
|
||||
go.uber.org/atomic v1.6.0 // indirect
|
||||
golang.org/x/mod v0.4.1 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/ini.v1 v1.51.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999
|
||||
|
||||
go 1.13
|
||||
go 1.17
|
||||
|
||||
18
gqlgen.yml
18
gqlgen.yml
@@ -34,23 +34,9 @@ models:
|
||||
model: github.com/stashapp/stash/pkg/models.Movie
|
||||
Tag:
|
||||
model: github.com/stashapp/stash/pkg/models.Tag
|
||||
ScrapedPerformer:
|
||||
model: github.com/stashapp/stash/pkg/models.ScrapedPerformer
|
||||
ScrapedScene:
|
||||
model: github.com/stashapp/stash/pkg/models.ScrapedScene
|
||||
ScrapedScenePerformer:
|
||||
model: github.com/stashapp/stash/pkg/models.ScrapedScenePerformer
|
||||
ScrapedSceneStudio:
|
||||
model: github.com/stashapp/stash/pkg/models.ScrapedSceneStudio
|
||||
ScrapedSceneMovie:
|
||||
model: github.com/stashapp/stash/pkg/models.ScrapedSceneMovie
|
||||
ScrapedSceneTag:
|
||||
model: github.com/stashapp/stash/pkg/models.ScrapedSceneTag
|
||||
SceneFileType:
|
||||
model: github.com/stashapp/stash/pkg/models.SceneFileType
|
||||
ScrapedMovie:
|
||||
model: github.com/stashapp/stash/pkg/models.ScrapedMovie
|
||||
ScrapedMovieStudio:
|
||||
model: github.com/stashapp/stash/pkg/models.ScrapedMovieStudio
|
||||
SavedFilter:
|
||||
model: github.com/stashapp/stash/pkg/models.SavedFilter
|
||||
StashID:
|
||||
model: github.com/stashapp/stash/pkg/models.StashID
|
||||
|
||||
@@ -2,13 +2,16 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||
stashes {
|
||||
path
|
||||
excludeVideo
|
||||
excludeImage
|
||||
excludeImage
|
||||
}
|
||||
databasePath
|
||||
generatedPath
|
||||
metadataPath
|
||||
cachePath
|
||||
calculateMD5
|
||||
videoFileNamingAlgorithm
|
||||
parallelTasks
|
||||
previewAudio
|
||||
previewSegments
|
||||
previewSegmentDuration
|
||||
previewExcludeStart
|
||||
@@ -16,9 +19,12 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||
previewPreset
|
||||
maxTranscodeSize
|
||||
maxStreamingTranscodeSize
|
||||
writeImageThumbnails
|
||||
apiKey
|
||||
username
|
||||
password
|
||||
maxSessionAge
|
||||
trustedProxies
|
||||
logFile
|
||||
logOut
|
||||
logLevel
|
||||
@@ -29,7 +35,9 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||
galleryExtensions
|
||||
excludes
|
||||
imageExcludes
|
||||
customPerformerImageLocation
|
||||
scraperUserAgent
|
||||
scraperCertCheck
|
||||
scraperCDPPath
|
||||
stashBoxes {
|
||||
name
|
||||
@@ -39,6 +47,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||
}
|
||||
|
||||
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||
menuItems
|
||||
soundOnPreview
|
||||
wallShowTitle
|
||||
wallPlayback
|
||||
@@ -48,6 +57,23 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||
css
|
||||
cssEnabled
|
||||
language
|
||||
slideshowDelay
|
||||
handyKey
|
||||
funscriptOffset
|
||||
}
|
||||
|
||||
fragment ConfigDLNAData on ConfigDLNAResult {
|
||||
serverName
|
||||
enabled
|
||||
whitelistedIPs
|
||||
interfaces
|
||||
}
|
||||
|
||||
fragment ConfigScrapingData on ConfigScrapingResult {
|
||||
scraperUserAgent
|
||||
scraperCertCheck
|
||||
scraperCDPPath
|
||||
excludeTagPatterns
|
||||
}
|
||||
|
||||
fragment ConfigData on ConfigResult {
|
||||
@@ -57,4 +83,10 @@ fragment ConfigData on ConfigResult {
|
||||
interface {
|
||||
...ConfigInterfaceData
|
||||
}
|
||||
dlna {
|
||||
...ConfigDLNAData
|
||||
}
|
||||
scraping {
|
||||
...ConfigScrapingData
|
||||
}
|
||||
}
|
||||
|
||||
6
graphql/documents/data/filter.graphql
Normal file
6
graphql/documents/data/filter.graphql
Normal file
@@ -0,0 +1,6 @@
|
||||
fragment SavedFilterData on SavedFilter {
|
||||
id
|
||||
mode
|
||||
name
|
||||
filter
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
fragment GallerySlimData on Gallery {
|
||||
fragment SlimGalleryData on Gallery {
|
||||
id
|
||||
checksum
|
||||
path
|
||||
@@ -7,20 +7,36 @@ fragment GallerySlimData on Gallery {
|
||||
url
|
||||
details
|
||||
rating
|
||||
organized
|
||||
image_count
|
||||
cover {
|
||||
...SlimImageData
|
||||
file {
|
||||
size
|
||||
width
|
||||
height
|
||||
}
|
||||
|
||||
paths {
|
||||
thumbnail
|
||||
}
|
||||
}
|
||||
studio {
|
||||
...StudioData
|
||||
id
|
||||
name
|
||||
image_path
|
||||
}
|
||||
tags {
|
||||
...TagData
|
||||
id
|
||||
name
|
||||
}
|
||||
performers {
|
||||
...PerformerData
|
||||
id
|
||||
name
|
||||
gender
|
||||
favorite
|
||||
image_path
|
||||
}
|
||||
scene {
|
||||
scenes {
|
||||
id
|
||||
title
|
||||
path
|
||||
|
||||
@@ -7,6 +7,7 @@ fragment GalleryData on Gallery {
|
||||
url
|
||||
details
|
||||
rating
|
||||
organized
|
||||
images {
|
||||
...SlimImageData
|
||||
}
|
||||
@@ -14,18 +15,16 @@ fragment GalleryData on Gallery {
|
||||
...SlimImageData
|
||||
}
|
||||
studio {
|
||||
...StudioData
|
||||
...SlimStudioData
|
||||
}
|
||||
tags {
|
||||
...TagData
|
||||
...SlimTagData
|
||||
}
|
||||
|
||||
performers {
|
||||
...PerformerData
|
||||
}
|
||||
scene {
|
||||
id
|
||||
title
|
||||
path
|
||||
scenes {
|
||||
...SlimSceneData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ fragment SlimImageData on Image {
|
||||
checksum
|
||||
title
|
||||
rating
|
||||
organized
|
||||
o_counter
|
||||
path
|
||||
|
||||
@@ -37,6 +38,7 @@ fragment SlimImageData on Image {
|
||||
performers {
|
||||
id
|
||||
name
|
||||
gender
|
||||
favorite
|
||||
image_path
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ fragment ImageData on Image {
|
||||
checksum
|
||||
title
|
||||
rating
|
||||
organized
|
||||
o_counter
|
||||
path
|
||||
|
||||
@@ -22,11 +23,11 @@ fragment ImageData on Image {
|
||||
}
|
||||
|
||||
studio {
|
||||
...StudioData
|
||||
...SlimStudioData
|
||||
}
|
||||
|
||||
tags {
|
||||
...TagData
|
||||
...SlimTagData
|
||||
}
|
||||
|
||||
performers {
|
||||
|
||||
10
graphql/documents/data/job.graphql
Normal file
10
graphql/documents/data/job.graphql
Normal file
@@ -0,0 +1,10 @@
|
||||
fragment JobData on Job {
|
||||
id
|
||||
status
|
||||
subTasks
|
||||
description
|
||||
progress
|
||||
startTime
|
||||
endTime
|
||||
addTime
|
||||
}
|
||||
@@ -2,4 +2,4 @@ fragment SlimMovieData on Movie {
|
||||
id
|
||||
name
|
||||
front_image_path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ fragment MovieData on Movie {
|
||||
director
|
||||
|
||||
studio {
|
||||
...StudioData
|
||||
...SlimStudioData
|
||||
}
|
||||
|
||||
synopsis
|
||||
@@ -17,4 +17,10 @@ fragment MovieData on Movie {
|
||||
front_image_path
|
||||
back_image_path
|
||||
scene_count
|
||||
|
||||
scenes {
|
||||
id
|
||||
title
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,14 @@ fragment SlimPerformerData on Performer {
|
||||
name
|
||||
gender
|
||||
image_path
|
||||
favorite
|
||||
tags {
|
||||
id
|
||||
name
|
||||
}
|
||||
stash_ids {
|
||||
endpoint
|
||||
stash_id
|
||||
}
|
||||
rating
|
||||
}
|
||||
|
||||
@@ -20,8 +20,21 @@ fragment PerformerData on Performer {
|
||||
favorite
|
||||
image_path
|
||||
scene_count
|
||||
image_count
|
||||
gallery_count
|
||||
movie_count
|
||||
|
||||
tags {
|
||||
...SlimTagData
|
||||
}
|
||||
|
||||
stash_ids {
|
||||
stash_id
|
||||
endpoint
|
||||
}
|
||||
rating
|
||||
details
|
||||
death_date
|
||||
hair_color
|
||||
weight
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ fragment SceneMarkerData on SceneMarker {
|
||||
seconds
|
||||
stream
|
||||
preview
|
||||
screenshot
|
||||
|
||||
scene {
|
||||
id
|
||||
@@ -12,10 +13,12 @@ fragment SceneMarkerData on SceneMarker {
|
||||
primary_tag {
|
||||
id
|
||||
name
|
||||
aliases
|
||||
}
|
||||
|
||||
tags {
|
||||
id
|
||||
name
|
||||
aliases
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ fragment SlimSceneData on Scene {
|
||||
date
|
||||
rating
|
||||
o_counter
|
||||
organized
|
||||
path
|
||||
phash
|
||||
interactive
|
||||
|
||||
file {
|
||||
size
|
||||
@@ -28,6 +31,8 @@ fragment SlimSceneData on Scene {
|
||||
webp
|
||||
vtt
|
||||
chapters_vtt
|
||||
sprite
|
||||
funscript
|
||||
}
|
||||
|
||||
scene_markers {
|
||||
@@ -36,7 +41,7 @@ fragment SlimSceneData on Scene {
|
||||
seconds
|
||||
}
|
||||
|
||||
gallery {
|
||||
galleries {
|
||||
id
|
||||
path
|
||||
title
|
||||
@@ -65,6 +70,7 @@ fragment SlimSceneData on Scene {
|
||||
performers {
|
||||
id
|
||||
name
|
||||
gender
|
||||
favorite
|
||||
image_path
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ fragment SceneData on Scene {
|
||||
date
|
||||
rating
|
||||
o_counter
|
||||
organized
|
||||
path
|
||||
phash
|
||||
interactive
|
||||
|
||||
file {
|
||||
size
|
||||
@@ -28,18 +31,20 @@ fragment SceneData on Scene {
|
||||
webp
|
||||
vtt
|
||||
chapters_vtt
|
||||
sprite
|
||||
funscript
|
||||
}
|
||||
|
||||
scene_markers {
|
||||
...SceneMarkerData
|
||||
}
|
||||
|
||||
gallery {
|
||||
...GalleryData
|
||||
galleries {
|
||||
...SlimGalleryData
|
||||
}
|
||||
|
||||
studio {
|
||||
...StudioData
|
||||
...SlimStudioData
|
||||
}
|
||||
|
||||
movies {
|
||||
@@ -50,7 +55,7 @@ fragment SceneData on Scene {
|
||||
}
|
||||
|
||||
tags {
|
||||
...TagData
|
||||
...SlimTagData
|
||||
}
|
||||
|
||||
performers {
|
||||
|
||||
@@ -1,24 +1,4 @@
|
||||
fragment ScrapedPerformerData on ScrapedPerformer {
|
||||
name
|
||||
gender
|
||||
url
|
||||
twitter
|
||||
instagram
|
||||
birthdate
|
||||
ethnicity
|
||||
country
|
||||
eye_color
|
||||
height
|
||||
measurements
|
||||
fake_tits
|
||||
career_length
|
||||
tattoos
|
||||
piercings
|
||||
aliases
|
||||
image
|
||||
}
|
||||
|
||||
fragment ScrapedScenePerformerData on ScrapedScenePerformer {
|
||||
stored_id
|
||||
name
|
||||
gender
|
||||
@@ -36,12 +16,48 @@ fragment ScrapedScenePerformerData on ScrapedScenePerformer {
|
||||
tattoos
|
||||
piercings
|
||||
aliases
|
||||
remote_site_id
|
||||
tags {
|
||||
...ScrapedSceneTagData
|
||||
}
|
||||
images
|
||||
details
|
||||
death_date
|
||||
hair_color
|
||||
weight
|
||||
remote_site_id
|
||||
}
|
||||
|
||||
fragment ScrapedMovieStudioData on ScrapedMovieStudio {
|
||||
id
|
||||
fragment ScrapedScenePerformerData on ScrapedPerformer {
|
||||
stored_id
|
||||
name
|
||||
gender
|
||||
url
|
||||
twitter
|
||||
instagram
|
||||
birthdate
|
||||
ethnicity
|
||||
country
|
||||
eye_color
|
||||
height
|
||||
measurements
|
||||
fake_tits
|
||||
career_length
|
||||
tattoos
|
||||
piercings
|
||||
aliases
|
||||
tags {
|
||||
...ScrapedSceneTagData
|
||||
}
|
||||
remote_site_id
|
||||
images
|
||||
details
|
||||
death_date
|
||||
hair_color
|
||||
weight
|
||||
}
|
||||
|
||||
fragment ScrapedMovieStudioData on ScrapedStudio {
|
||||
stored_id
|
||||
name
|
||||
url
|
||||
}
|
||||
@@ -63,7 +79,7 @@ fragment ScrapedMovieData on ScrapedMovie {
|
||||
}
|
||||
}
|
||||
|
||||
fragment ScrapedSceneMovieData on ScrapedSceneMovie {
|
||||
fragment ScrapedSceneMovieData on ScrapedMovie {
|
||||
stored_id
|
||||
name
|
||||
aliases
|
||||
@@ -75,14 +91,14 @@ fragment ScrapedSceneMovieData on ScrapedSceneMovie {
|
||||
synopsis
|
||||
}
|
||||
|
||||
fragment ScrapedSceneStudioData on ScrapedSceneStudio {
|
||||
fragment ScrapedSceneStudioData on ScrapedStudio {
|
||||
stored_id
|
||||
name
|
||||
url
|
||||
remote_site_id
|
||||
}
|
||||
|
||||
fragment ScrapedSceneTagData on ScrapedSceneTag {
|
||||
fragment ScrapedSceneTagData on ScrapedTag {
|
||||
stored_id
|
||||
name
|
||||
}
|
||||
@@ -93,6 +109,7 @@ fragment ScrapedSceneData on ScrapedScene {
|
||||
url
|
||||
date
|
||||
image
|
||||
remote_site_id
|
||||
|
||||
file {
|
||||
size
|
||||
@@ -120,6 +137,12 @@ fragment ScrapedSceneData on ScrapedScene {
|
||||
movies {
|
||||
...ScrapedSceneMovieData
|
||||
}
|
||||
|
||||
fingerprints {
|
||||
hash
|
||||
algorithm
|
||||
duration
|
||||
}
|
||||
}
|
||||
|
||||
fragment ScrapedGalleryData on ScrapedGallery {
|
||||
@@ -183,3 +206,10 @@ fragment ScrapedStashBoxSceneData on ScrapedScene {
|
||||
...ScrapedSceneMovieData
|
||||
}
|
||||
}
|
||||
|
||||
fragment ScrapedStashBoxPerformerData on StashBoxPerformerQueryResult {
|
||||
query
|
||||
results {
|
||||
...ScrapedScenePerformerData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,7 @@ fragment SlimStudioData on Studio {
|
||||
parent_studio {
|
||||
id
|
||||
}
|
||||
details
|
||||
rating
|
||||
aliases
|
||||
}
|
||||
|
||||
@@ -5,24 +5,25 @@ fragment StudioData on Studio {
|
||||
url
|
||||
parent_studio {
|
||||
id
|
||||
checksum
|
||||
name
|
||||
url
|
||||
image_path
|
||||
scene_count
|
||||
}
|
||||
child_studios {
|
||||
id
|
||||
checksum
|
||||
name
|
||||
url
|
||||
image_path
|
||||
scene_count
|
||||
}
|
||||
image_path
|
||||
scene_count
|
||||
image_count
|
||||
gallery_count
|
||||
movie_count
|
||||
stash_ids {
|
||||
stash_id
|
||||
endpoint
|
||||
}
|
||||
details
|
||||
rating
|
||||
aliases
|
||||
}
|
||||
|
||||
6
graphql/documents/data/tag-slim.graphql
Normal file
6
graphql/documents/data/tag-slim.graphql
Normal file
@@ -0,0 +1,6 @@
|
||||
fragment SlimTagData on Tag {
|
||||
id
|
||||
name
|
||||
aliases
|
||||
image_path
|
||||
}
|
||||
@@ -1,7 +1,19 @@
|
||||
fragment TagData on Tag {
|
||||
id
|
||||
name
|
||||
aliases
|
||||
image_path
|
||||
scene_count
|
||||
scene_marker_count
|
||||
image_count
|
||||
gallery_count
|
||||
performer_count
|
||||
|
||||
parents {
|
||||
...SlimTagData
|
||||
}
|
||||
|
||||
children {
|
||||
...SlimTagData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
mutation Setup($input: SetupInput!) {
|
||||
setup(input: $input)
|
||||
}
|
||||
|
||||
mutation Migrate($input: MigrateInput!) {
|
||||
migrate(input: $input)
|
||||
}
|
||||
|
||||
mutation ConfigureGeneral($input: ConfigGeneralInput!) {
|
||||
configureGeneral(input: $input) {
|
||||
...ConfigGeneralData
|
||||
@@ -8,4 +16,20 @@ mutation ConfigureInterface($input: ConfigInterfaceInput!) {
|
||||
configureInterface(input: $input) {
|
||||
...ConfigInterfaceData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation ConfigureDLNA($input: ConfigDLNAInput!) {
|
||||
configureDLNA(input: $input) {
|
||||
...ConfigDLNAData
|
||||
}
|
||||
}
|
||||
|
||||
mutation ConfigureScraping($input: ConfigScrapingInput!) {
|
||||
configureScraping(input: $input) {
|
||||
...ConfigScrapingData
|
||||
}
|
||||
}
|
||||
|
||||
mutation GenerateAPIKey($input: GenerateAPIKeyInput!) {
|
||||
generateAPIKey(input: $input)
|
||||
}
|
||||
|
||||
15
graphql/documents/mutations/dlna.graphql
Normal file
15
graphql/documents/mutations/dlna.graphql
Normal file
@@ -0,0 +1,15 @@
|
||||
mutation EnableDLNA($input: EnableDLNAInput!) {
|
||||
enableDLNA(input: $input)
|
||||
}
|
||||
|
||||
mutation DisableDLNA($input: DisableDLNAInput!) {
|
||||
disableDLNA(input: $input)
|
||||
}
|
||||
|
||||
mutation AddTempDLNAIP($input: AddTempDLNAIPInput!) {
|
||||
addTempDLNAIP(input: $input)
|
||||
}
|
||||
|
||||
mutation RemoveTempDLNAIP($input: RemoveTempDLNAIPInput!) {
|
||||
removeTempDLNAIP(input: $input)
|
||||
}
|
||||
13
graphql/documents/mutations/filter.graphql
Normal file
13
graphql/documents/mutations/filter.graphql
Normal file
@@ -0,0 +1,13 @@
|
||||
mutation SaveFilter($input: SaveFilterInput!) {
|
||||
saveFilter(input: $input) {
|
||||
...SavedFilterData
|
||||
}
|
||||
}
|
||||
|
||||
mutation DestroySavedFilter($input: DestroyFilterInput!) {
|
||||
destroySavedFilter(input: $input)
|
||||
}
|
||||
|
||||
mutation SetDefaultFilter($input: SetDefaultFilterInput!) {
|
||||
setDefaultFilter(input: $input)
|
||||
}
|
||||
@@ -1,79 +1,23 @@
|
||||
mutation GalleryCreate(
|
||||
$title: String!,
|
||||
$details: String,
|
||||
$url: String,
|
||||
$date: String,
|
||||
$rating: Int,
|
||||
$scene_id: ID,
|
||||
$studio_id: ID,
|
||||
$performer_ids: [ID!] = [],
|
||||
$tag_ids: [ID!] = []) {
|
||||
$input: GalleryCreateInput!) {
|
||||
|
||||
galleryCreate(input: {
|
||||
title: $title,
|
||||
details: $details,
|
||||
url: $url,
|
||||
date: $date,
|
||||
rating: $rating,
|
||||
scene_id: $scene_id,
|
||||
studio_id: $studio_id,
|
||||
tag_ids: $tag_ids,
|
||||
performer_ids: $performer_ids
|
||||
}) {
|
||||
galleryCreate(input: $input) {
|
||||
...GalleryData
|
||||
}
|
||||
}
|
||||
|
||||
mutation GalleryUpdate(
|
||||
$id: ID!,
|
||||
$title: String,
|
||||
$details: String,
|
||||
$url: String,
|
||||
$date: String,
|
||||
$rating: Int,
|
||||
$scene_id: ID,
|
||||
$studio_id: ID,
|
||||
$performer_ids: [ID!] = [],
|
||||
$tag_ids: [ID!] = []) {
|
||||
$input: GalleryUpdateInput!) {
|
||||
|
||||
galleryUpdate(input: {
|
||||
id: $id,
|
||||
title: $title,
|
||||
details: $details,
|
||||
url: $url,
|
||||
date: $date,
|
||||
rating: $rating,
|
||||
scene_id: $scene_id,
|
||||
studio_id: $studio_id,
|
||||
tag_ids: $tag_ids,
|
||||
performer_ids: $performer_ids
|
||||
}) {
|
||||
galleryUpdate(input: $input) {
|
||||
...GalleryData
|
||||
}
|
||||
}
|
||||
|
||||
mutation BulkGalleryUpdate(
|
||||
$ids: [ID!] = [],
|
||||
$url: String,
|
||||
$date: String,
|
||||
$details: String,
|
||||
$rating: Int,
|
||||
$scene_id: ID,
|
||||
$studio_id: ID,
|
||||
$tag_ids: BulkUpdateIds,
|
||||
$performer_ids: BulkUpdateIds) {
|
||||
$input: BulkGalleryUpdateInput!) {
|
||||
|
||||
bulkGalleryUpdate(input: {
|
||||
ids: $ids,
|
||||
details: $details,
|
||||
url: $url,
|
||||
date: $date,
|
||||
rating: $rating,
|
||||
scene_id: $scene_id,
|
||||
studio_id: $studio_id,
|
||||
tag_ids: $tag_ids,
|
||||
performer_ids: $performer_ids
|
||||
}) {
|
||||
bulkGalleryUpdate(input: $input) {
|
||||
...GalleryData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,15 @@
|
||||
mutation ImageUpdate(
|
||||
$id: ID!,
|
||||
$title: String,
|
||||
$rating: Int,
|
||||
$studio_id: ID,
|
||||
$gallery_ids: [ID!] = [],
|
||||
$performer_ids: [ID!] = [],
|
||||
$tag_ids: [ID!] = []) {
|
||||
$input: ImageUpdateInput!) {
|
||||
|
||||
imageUpdate(input: {
|
||||
id: $id,
|
||||
title: $title,
|
||||
rating: $rating,
|
||||
studio_id: $studio_id,
|
||||
gallery_ids: $gallery_ids,
|
||||
performer_ids: $performer_ids,
|
||||
tag_ids: $tag_ids
|
||||
}) {
|
||||
imageUpdate(input: $input) {
|
||||
...SlimImageData
|
||||
}
|
||||
}
|
||||
|
||||
mutation BulkImageUpdate(
|
||||
$ids: [ID!] = [],
|
||||
$title: String,
|
||||
$rating: Int,
|
||||
$studio_id: ID,
|
||||
$gallery_ids: BulkUpdateIds,
|
||||
$performer_ids: BulkUpdateIds,
|
||||
$tag_ids: BulkUpdateIds) {
|
||||
$input: BulkImageUpdateInput!) {
|
||||
|
||||
bulkImageUpdate(input: {
|
||||
ids: $ids,
|
||||
title: $title,
|
||||
rating: $rating,
|
||||
studio_id: $studio_id,
|
||||
gallery_ids: $gallery_ids,
|
||||
performer_ids: $performer_ids,
|
||||
tag_ids: $tag_ids
|
||||
}) {
|
||||
bulkImageUpdate(input: $input) {
|
||||
...SlimImageData
|
||||
}
|
||||
}
|
||||
|
||||
7
graphql/documents/mutations/job.graphql
Normal file
7
graphql/documents/mutations/job.graphql
Normal file
@@ -0,0 +1,7 @@
|
||||
mutation StopJob($job_id: ID!) {
|
||||
stopJob(job_id: $job_id)
|
||||
}
|
||||
|
||||
mutation StopAllJobs {
|
||||
stopAllJobs
|
||||
}
|
||||
@@ -26,14 +26,14 @@ mutation MetadataAutoTag($input: AutoTagMetadataInput!) {
|
||||
metadataAutoTag(input: $input)
|
||||
}
|
||||
|
||||
mutation MetadataClean {
|
||||
metadataClean
|
||||
mutation MetadataClean($input: CleanMetadataInput!) {
|
||||
metadataClean(input: $input)
|
||||
}
|
||||
|
||||
mutation MigrateHashNaming {
|
||||
migrateHashNaming
|
||||
}
|
||||
|
||||
mutation StopJob {
|
||||
stopJob
|
||||
}
|
||||
mutation BackupDatabase($input: BackupDatabaseInput!) {
|
||||
backupDatabase(input: $input)
|
||||
}
|
||||
|
||||
@@ -16,25 +16,16 @@ mutation MovieCreate(
|
||||
}
|
||||
}
|
||||
|
||||
mutation MovieUpdate(
|
||||
$id: ID!
|
||||
$name: String,
|
||||
$aliases: String,
|
||||
$duration: Int,
|
||||
$date: String,
|
||||
$rating: Int,
|
||||
$studio_id: ID,
|
||||
$director: String,
|
||||
$synopsis: String,
|
||||
$url: String,
|
||||
$front_image: String,
|
||||
$back_image: String) {
|
||||
|
||||
movieUpdate(input: { id: $id, name: $name, aliases: $aliases, duration: $duration, date: $date, rating: $rating, studio_id: $studio_id, director: $director, synopsis: $synopsis, url: $url, front_image: $front_image, back_image: $back_image }) {
|
||||
mutation MovieUpdate($input: MovieUpdateInput!) {
|
||||
movieUpdate(input: $input) {
|
||||
...MovieData
|
||||
}
|
||||
}
|
||||
|
||||
mutation MovieDestroy($id: ID!) {
|
||||
movieDestroy(input: { id: $id })
|
||||
}
|
||||
}
|
||||
|
||||
mutation MoviesDestroy($ids: [ID!]!) {
|
||||
moviesDestroy(ids: $ids)
|
||||
}
|
||||
|
||||
@@ -1,93 +1,23 @@
|
||||
mutation PerformerCreate(
|
||||
$name: String!,
|
||||
$url: String,
|
||||
$gender: GenderEnum,
|
||||
$birthdate: String,
|
||||
$ethnicity: String,
|
||||
$country: String,
|
||||
$eye_color: String,
|
||||
$height: String,
|
||||
$measurements: String,
|
||||
$fake_tits: String,
|
||||
$career_length: String,
|
||||
$tattoos: String,
|
||||
$piercings: String,
|
||||
$aliases: String,
|
||||
$twitter: String,
|
||||
$instagram: String,
|
||||
$favorite: Boolean,
|
||||
$stash_ids: [StashIDInput!],
|
||||
$image: String) {
|
||||
$input: PerformerCreateInput!) {
|
||||
|
||||
performerCreate(input: {
|
||||
name: $name,
|
||||
url: $url,
|
||||
gender: $gender,
|
||||
birthdate: $birthdate,
|
||||
ethnicity: $ethnicity,
|
||||
country: $country,
|
||||
eye_color: $eye_color,
|
||||
height: $height,
|
||||
measurements: $measurements,
|
||||
fake_tits: $fake_tits,
|
||||
career_length: $career_length,
|
||||
tattoos: $tattoos,
|
||||
piercings: $piercings,
|
||||
aliases: $aliases,
|
||||
twitter: $twitter,
|
||||
instagram: $instagram,
|
||||
favorite: $favorite,
|
||||
stash_ids: $stash_ids,
|
||||
image: $image
|
||||
}) {
|
||||
performerCreate(input: $input) {
|
||||
...PerformerData
|
||||
}
|
||||
}
|
||||
|
||||
mutation PerformerUpdate(
|
||||
$id: ID!,
|
||||
$name: String,
|
||||
$url: String,
|
||||
$gender: GenderEnum,
|
||||
$birthdate: String,
|
||||
$ethnicity: String,
|
||||
$country: String,
|
||||
$eye_color: String,
|
||||
$height: String,
|
||||
$measurements: String,
|
||||
$fake_tits: String,
|
||||
$career_length: String,
|
||||
$tattoos: String,
|
||||
$piercings: String,
|
||||
$aliases: String,
|
||||
$twitter: String,
|
||||
$instagram: String,
|
||||
$favorite: Boolean,
|
||||
$stash_ids: [StashIDInput!],
|
||||
$image: String) {
|
||||
$input: PerformerUpdateInput!) {
|
||||
|
||||
performerUpdate(input: {
|
||||
id: $id,
|
||||
name: $name,
|
||||
url: $url,
|
||||
gender: $gender,
|
||||
birthdate: $birthdate,
|
||||
ethnicity: $ethnicity,
|
||||
country: $country,
|
||||
eye_color: $eye_color,
|
||||
height: $height,
|
||||
measurements: $measurements,
|
||||
fake_tits: $fake_tits,
|
||||
career_length: $career_length,
|
||||
tattoos: $tattoos,
|
||||
piercings: $piercings,
|
||||
aliases: $aliases,
|
||||
twitter: $twitter,
|
||||
instagram: $instagram,
|
||||
favorite: $favorite,
|
||||
stash_ids: $stash_ids,
|
||||
image: $image
|
||||
}) {
|
||||
performerUpdate(input: $input) {
|
||||
...PerformerData
|
||||
}
|
||||
}
|
||||
|
||||
mutation BulkPerformerUpdate(
|
||||
$input: BulkPerformerUpdateInput!) {
|
||||
|
||||
bulkPerformerUpdate(input: $input) {
|
||||
...PerformerData
|
||||
}
|
||||
}
|
||||
@@ -95,3 +25,7 @@ mutation PerformerUpdate(
|
||||
mutation PerformerDestroy($id: ID!) {
|
||||
performerDestroy(input: { id: $id })
|
||||
}
|
||||
|
||||
mutation PerformersDestroy($ids: [ID!]!) {
|
||||
performersDestroy(ids: $ids)
|
||||
}
|
||||
|
||||
@@ -1,62 +1,16 @@
|
||||
mutation SceneUpdate(
|
||||
$id: ID!,
|
||||
$title: String,
|
||||
$details: String,
|
||||
$url: String,
|
||||
$date: String,
|
||||
$rating: Int,
|
||||
$studio_id: ID,
|
||||
$gallery_id: ID,
|
||||
$performer_ids: [ID!] = [],
|
||||
$movies: [SceneMovieInput!] = [],
|
||||
$tag_ids: [ID!] = [],
|
||||
$stash_ids: [StashIDInput!],
|
||||
$cover_image: String) {
|
||||
$input: SceneUpdateInput!) {
|
||||
|
||||
sceneUpdate(input: {
|
||||
id: $id,
|
||||
title: $title,
|
||||
details: $details,
|
||||
url: $url,
|
||||
date: $date,
|
||||
rating: $rating,
|
||||
studio_id: $studio_id,
|
||||
gallery_id: $gallery_id,
|
||||
performer_ids: $performer_ids,
|
||||
movies: $movies,
|
||||
tag_ids: $tag_ids,
|
||||
stash_ids: $stash_ids,
|
||||
cover_image: $cover_image
|
||||
}) {
|
||||
...SceneData
|
||||
sceneUpdate(input: $input) {
|
||||
...SceneData
|
||||
}
|
||||
}
|
||||
|
||||
mutation BulkSceneUpdate(
|
||||
$ids: [ID!] = [],
|
||||
$title: String,
|
||||
$details: String,
|
||||
$url: String,
|
||||
$date: String,
|
||||
$rating: Int,
|
||||
$studio_id: ID,
|
||||
$gallery_id: ID,
|
||||
$performer_ids: BulkUpdateIds,
|
||||
$tag_ids: BulkUpdateIds) {
|
||||
$input: BulkSceneUpdateInput!) {
|
||||
|
||||
bulkSceneUpdate(input: {
|
||||
ids: $ids,
|
||||
title: $title,
|
||||
details: $details,
|
||||
url: $url,
|
||||
date: $date,
|
||||
rating: $rating,
|
||||
studio_id: $studio_id,
|
||||
gallery_id: $gallery_id,
|
||||
performer_ids: $performer_ids,
|
||||
tag_ids: $tag_ids
|
||||
}) {
|
||||
...SceneData
|
||||
bulkSceneUpdate(input: $input) {
|
||||
...SceneData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
mutation SubmitStashBoxFingerprints($input: StashBoxFingerprintSubmissionInput!) {
|
||||
submitStashBoxFingerprints(input: $input)
|
||||
}
|
||||
|
||||
mutation StashBoxBatchPerformerTag($input: StashBoxBatchPerformerTagInput!) {
|
||||
stashBoxBatchPerformerTag(input: $input)
|
||||
}
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
mutation StudioCreate(
|
||||
$name: String!,
|
||||
$url: String,
|
||||
$image: String,
|
||||
$stash_ids: [StashIDInput!],
|
||||
$parent_id: ID) {
|
||||
|
||||
studioCreate(input: { name: $name, url: $url, image: $image, stash_ids: $stash_ids, parent_id: $parent_id }) {
|
||||
mutation StudioCreate($input: StudioCreateInput!) {
|
||||
studioCreate(input: $input) {
|
||||
...StudioData
|
||||
}
|
||||
}
|
||||
|
||||
mutation StudioUpdate(
|
||||
$id: ID!
|
||||
$name: String,
|
||||
$url: String,
|
||||
$image: String,
|
||||
$stash_ids: [StashIDInput!],
|
||||
$parent_id: ID) {
|
||||
|
||||
studioUpdate(input: { id: $id, name: $name, url: $url, image: $image, stash_ids: $stash_ids, parent_id: $parent_id }) {
|
||||
mutation StudioUpdate($input: StudioUpdateInput!) {
|
||||
studioUpdate(input: $input) {
|
||||
...StudioData
|
||||
}
|
||||
}
|
||||
@@ -26,3 +13,7 @@ mutation StudioUpdate(
|
||||
mutation StudioDestroy($id: ID!) {
|
||||
studioDestroy(input: { id: $id })
|
||||
}
|
||||
|
||||
mutation StudiosDestroy($ids: [ID!]!) {
|
||||
studiosDestroy(ids: $ids)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mutation TagCreate($name: String!, $image: String) {
|
||||
tagCreate(input: { name: $name, image: $image }) {
|
||||
mutation TagCreate($input: TagCreateInput!) {
|
||||
tagCreate(input: $input) {
|
||||
...TagData
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,18 @@ mutation TagDestroy($id: ID!) {
|
||||
tagDestroy(input: { id: $id })
|
||||
}
|
||||
|
||||
mutation TagUpdate($id: ID!, $name: String!, $image: String) {
|
||||
tagUpdate(input: { id: $id, name: $name, image: $image }) {
|
||||
mutation TagsDestroy($ids: [ID!]!) {
|
||||
tagsDestroy(ids: $ids)
|
||||
}
|
||||
|
||||
mutation TagUpdate($input: TagUpdateInput!) {
|
||||
tagUpdate(input: $input) {
|
||||
...TagData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation TagsMerge($source: [ID!]!, $destination: ID!) {
|
||||
tagsMerge(input: { source: $source, destination: $destination }) {
|
||||
...TagData
|
||||
}
|
||||
}
|
||||
|
||||
11
graphql/documents/queries/dlna.graphql
Normal file
11
graphql/documents/queries/dlna.graphql
Normal file
@@ -0,0 +1,11 @@
|
||||
query DLNAStatus {
|
||||
dlnaStatus {
|
||||
running
|
||||
until
|
||||
recentIPAddresses
|
||||
allowedIPAddresses {
|
||||
ipAddress
|
||||
until
|
||||
}
|
||||
}
|
||||
}
|
||||
11
graphql/documents/queries/filter.graphql
Normal file
11
graphql/documents/queries/filter.graphql
Normal file
@@ -0,0 +1,11 @@
|
||||
query FindSavedFilters($mode: FilterMode!) {
|
||||
findSavedFilters(mode: $mode) {
|
||||
...SavedFilterData
|
||||
}
|
||||
}
|
||||
|
||||
query FindDefaultFilter($mode: FilterMode!) {
|
||||
findDefaultFilter(mode: $mode) {
|
||||
...SavedFilterData
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ query FindGalleries($filter: FindFilterType, $gallery_filter: GalleryFilterType)
|
||||
findGalleries(gallery_filter: $gallery_filter, filter: $filter) {
|
||||
count
|
||||
galleries {
|
||||
...GallerySlimData
|
||||
...SlimGalleryData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
graphql/documents/queries/job.graphql
Normal file
11
graphql/documents/queries/job.graphql
Normal file
@@ -0,0 +1,11 @@
|
||||
query JobQueue {
|
||||
jobQueue {
|
||||
...JobData
|
||||
}
|
||||
}
|
||||
|
||||
query FindJob($input: FindJobInput!) {
|
||||
findJob(input: $input) {
|
||||
...JobData
|
||||
}
|
||||
}
|
||||
@@ -13,34 +13,27 @@ query AllTags {
|
||||
}
|
||||
|
||||
query AllPerformersForFilter {
|
||||
allPerformersSlim {
|
||||
allPerformers {
|
||||
...SlimPerformerData
|
||||
}
|
||||
}
|
||||
|
||||
query AllStudiosForFilter {
|
||||
allStudiosSlim {
|
||||
allStudios {
|
||||
...SlimStudioData
|
||||
}
|
||||
}
|
||||
query AllMoviesForFilter {
|
||||
allMoviesSlim {
|
||||
allMovies {
|
||||
...SlimMovieData
|
||||
}
|
||||
}
|
||||
|
||||
query AllTagsForFilter {
|
||||
allTagsSlim {
|
||||
allTags {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
query ValidGalleriesForScene($scene_id: ID!) {
|
||||
validGalleriesForScene(scene_id: $scene_id) {
|
||||
id
|
||||
path
|
||||
title
|
||||
aliases
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +41,7 @@ query Stats {
|
||||
stats {
|
||||
scene_count,
|
||||
scenes_size,
|
||||
scenes_duration,
|
||||
image_count,
|
||||
images_size,
|
||||
gallery_count,
|
||||
|
||||
@@ -10,6 +10,12 @@ query Plugins {
|
||||
name
|
||||
description
|
||||
}
|
||||
|
||||
hooks {
|
||||
name
|
||||
description
|
||||
hooks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,12 @@ query FindScenesByPathRegex($filter: FindFilterType) {
|
||||
}
|
||||
}
|
||||
|
||||
query FindDuplicateScenes($distance: Int) {
|
||||
findDuplicateScenes(distance: $distance) {
|
||||
...SlimSceneData
|
||||
}
|
||||
}
|
||||
|
||||
query FindScene($id: ID!, $checksum: String) {
|
||||
findScene(id: $id, checksum: $checksum) {
|
||||
...SceneData
|
||||
@@ -45,7 +51,7 @@ query ParseSceneFilenames($filter: FindFilterType!, $config: SceneParserInput!)
|
||||
date
|
||||
rating
|
||||
studio_id
|
||||
gallery_id
|
||||
gallery_ids
|
||||
movies {
|
||||
movie_id
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ query ScrapeFreeones($performer_name: String!) {
|
||||
tattoos
|
||||
piercings
|
||||
aliases
|
||||
details
|
||||
death_date
|
||||
hair_color
|
||||
weight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,14 +42,14 @@ query ListMovieScrapers {
|
||||
}
|
||||
}
|
||||
|
||||
query ScrapePerformerList($scraper_id: ID!, $query: String!) {
|
||||
scrapePerformerList(scraper_id: $scraper_id, query: $query) {
|
||||
query ScrapeSinglePerformer($source: ScraperSourceInput!, $input: ScrapeSinglePerformerInput!) {
|
||||
scrapeSinglePerformer(source: $source, input: $input) {
|
||||
...ScrapedPerformerData
|
||||
}
|
||||
}
|
||||
|
||||
query ScrapePerformer($scraper_id: ID!, $scraped_performer: ScrapedPerformerInput!) {
|
||||
scrapePerformer(scraper_id: $scraper_id, scraped_performer: $scraped_performer) {
|
||||
query ScrapeMultiPerformers($source: ScraperSourceInput!, $input: ScrapeMultiPerformersInput!) {
|
||||
scrapeMultiPerformers(source: $source, input: $input) {
|
||||
...ScrapedPerformerData
|
||||
}
|
||||
}
|
||||
@@ -60,8 +60,14 @@ query ScrapePerformerURL($url: String!) {
|
||||
}
|
||||
}
|
||||
|
||||
query ScrapeScene($scraper_id: ID!, $scene: SceneUpdateInput!) {
|
||||
scrapeScene(scraper_id: $scraper_id, scene: $scene) {
|
||||
query ScrapeSingleScene($source: ScraperSourceInput!, $input: ScrapeSingleSceneInput!) {
|
||||
scrapeSingleScene(source: $source, input: $input) {
|
||||
...ScrapedSceneData
|
||||
}
|
||||
}
|
||||
|
||||
query ScrapeMultiScenes($source: ScraperSourceInput!, $input: ScrapeMultiScenesInput!) {
|
||||
scrapeMultiScenes(source: $source, input: $input) {
|
||||
...ScrapedSceneData
|
||||
}
|
||||
}
|
||||
@@ -72,8 +78,8 @@ query ScrapeSceneURL($url: String!) {
|
||||
}
|
||||
}
|
||||
|
||||
query ScrapeGallery($scraper_id: ID!, $gallery: GalleryUpdateInput!) {
|
||||
scrapeGallery(scraper_id: $scraper_id, gallery: $gallery) {
|
||||
query ScrapeSingleGallery($source: ScraperSourceInput!, $input: ScrapeSingleGalleryInput!) {
|
||||
scrapeSingleGallery(source: $source, input: $input) {
|
||||
...ScrapedGalleryData
|
||||
}
|
||||
}
|
||||
@@ -89,9 +95,3 @@ query ScrapeMovieURL($url: String!) {
|
||||
...ScrapedMovieData
|
||||
}
|
||||
}
|
||||
|
||||
query QueryStashBoxScene($input: StashBoxQueryInput!) {
|
||||
queryStashBoxScene(input: $input) {
|
||||
...ScrapedStashBoxSceneData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
query JobStatus {
|
||||
jobStatus {
|
||||
progress
|
||||
query SystemStatus {
|
||||
systemStatus {
|
||||
databaseSchema
|
||||
databasePath
|
||||
appSchema
|
||||
status
|
||||
message
|
||||
configPath
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
subscription MetadataUpdate {
|
||||
metadataUpdate {
|
||||
progress
|
||||
status
|
||||
message
|
||||
subscription JobsSubscribe {
|
||||
jobsSubscribe {
|
||||
type
|
||||
job {
|
||||
id
|
||||
status
|
||||
subTasks
|
||||
description
|
||||
progress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +15,8 @@ subscription LoggingSubscribe {
|
||||
loggingSubscribe {
|
||||
...LogEntryData
|
||||
}
|
||||
}
|
||||
|
||||
subscription ScanCompleteSubscribe {
|
||||
scanCompleteSubscribe
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
"""The query root for this schema"""
|
||||
type Query {
|
||||
# Filters
|
||||
findSavedFilters(mode: FilterMode!): [SavedFilter!]!
|
||||
findDefaultFilter(mode: FilterMode!): SavedFilter
|
||||
|
||||
"""Find a scene by ID or Checksum"""
|
||||
findScene(id: ID, checksum: String): Scene
|
||||
findSceneByHash(input: SceneHashInput!): Scene
|
||||
@@ -9,6 +13,9 @@ type Query {
|
||||
|
||||
findScenesByPathRegex(filter: FindFilterType): FindScenesResultType!
|
||||
|
||||
""" Returns any groups of scenes that are perceptual duplicates within the queried distance """
|
||||
findDuplicateScenes(distance: Int): [[Scene!]!]!
|
||||
|
||||
"""Return valid stream paths"""
|
||||
sceneStreams(id: ID): [SceneStreamEndpoint!]!
|
||||
|
||||
@@ -50,8 +57,6 @@ type Query {
|
||||
|
||||
"""Get marker strings"""
|
||||
markerStrings(q: String, sort: String): [MarkerStringsResultType]!
|
||||
"""Get the list of valid galleries for a given scene ID"""
|
||||
validGalleriesForScene(scene_id: ID): [Gallery!]!
|
||||
"""Get stats"""
|
||||
stats: StatsResultType!
|
||||
"""Organize scene markers by tag for a given scene ID"""
|
||||
@@ -67,30 +72,50 @@ type Query {
|
||||
listGalleryScrapers: [Scraper!]!
|
||||
listMovieScrapers: [Scraper!]!
|
||||
|
||||
"""Scrape a list of performers based on name"""
|
||||
scrapePerformerList(scraper_id: ID!, query: String!): [ScrapedPerformer!]!
|
||||
"""Scrapes a complete performer record based on a scrapePerformerList result"""
|
||||
scrapePerformer(scraper_id: ID!, scraped_performer: ScrapedPerformerInput!): ScrapedPerformer
|
||||
"""Scrape for a single scene"""
|
||||
scrapeSingleScene(source: ScraperSourceInput!, input: ScrapeSingleSceneInput!): [ScrapedScene!]!
|
||||
"""Scrape for multiple scenes"""
|
||||
scrapeMultiScenes(source: ScraperSourceInput!, input: ScrapeMultiScenesInput!): [[ScrapedScene!]!]!
|
||||
|
||||
"""Scrape for a single performer"""
|
||||
scrapeSinglePerformer(source: ScraperSourceInput!, input: ScrapeSinglePerformerInput!): [ScrapedPerformer!]!
|
||||
"""Scrape for multiple performers"""
|
||||
scrapeMultiPerformers(source: ScraperSourceInput!, input: ScrapeMultiPerformersInput!): [[ScrapedPerformer!]!]!
|
||||
|
||||
"""Scrape for a single gallery"""
|
||||
scrapeSingleGallery(source: ScraperSourceInput!, input: ScrapeSingleGalleryInput!): [ScrapedGallery!]!
|
||||
|
||||
"""Scrape for a single movie"""
|
||||
scrapeSingleMovie(source: ScraperSourceInput!, input: ScrapeSingleMovieInput!): [ScrapedMovie!]!
|
||||
|
||||
"""Scrapes a complete performer record based on a URL"""
|
||||
scrapePerformerURL(url: String!): ScrapedPerformer
|
||||
"""Scrapes a complete scene record based on an existing scene"""
|
||||
scrapeScene(scraper_id: ID!, scene: SceneUpdateInput!): ScrapedScene
|
||||
"""Scrapes a complete performer record based on a URL"""
|
||||
scrapeSceneURL(url: String!): ScrapedScene
|
||||
"""Scrapes a complete gallery record based on an existing gallery"""
|
||||
scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery
|
||||
"""Scrapes a complete gallery record based on a URL"""
|
||||
scrapeGalleryURL(url: String!): ScrapedGallery
|
||||
"""Scrapes a complete movie record based on a URL"""
|
||||
scrapeMovieURL(url: String!): ScrapedMovie
|
||||
|
||||
"""Scrape a list of performers based on name"""
|
||||
scrapePerformerList(scraper_id: ID!, query: String!): [ScrapedPerformer!]! @deprecated(reason: "use scrapeSinglePerformer")
|
||||
"""Scrapes a complete performer record based on a scrapePerformerList result"""
|
||||
scrapePerformer(scraper_id: ID!, scraped_performer: ScrapedPerformerInput!): ScrapedPerformer @deprecated(reason: "use scrapeSinglePerformer")
|
||||
"""Scrapes a complete scene record based on an existing scene"""
|
||||
scrapeScene(scraper_id: ID!, scene: SceneUpdateInput!): ScrapedScene @deprecated(reason: "use scrapeSingleScene")
|
||||
"""Scrapes a complete gallery record based on an existing gallery"""
|
||||
scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery @deprecated(reason: "use scrapeSingleGallery")
|
||||
|
||||
"""Scrape a performer using Freeones"""
|
||||
scrapeFreeones(performer_name: String!): ScrapedPerformer
|
||||
scrapeFreeones(performer_name: String!): ScrapedPerformer @deprecated(reason: "use scrapeSinglePerformer with scraper_id = builtin_freeones")
|
||||
"""Scrape a list of performers from a query"""
|
||||
scrapeFreeonesPerformerList(query: String!): [String!]!
|
||||
scrapeFreeonesPerformerList(query: String!): [String!]! @deprecated(reason: "use scrapeSinglePerformer with scraper_id = builtin_freeones")
|
||||
|
||||
"""Query StashBox for scenes"""
|
||||
queryStashBoxScene(input: StashBoxQueryInput!): [ScrapedScene!]!
|
||||
queryStashBoxScene(input: StashBoxSceneQueryInput!): [ScrapedScene!]! @deprecated(reason: "use scrapeSingleScene or scrapeMultiScenes")
|
||||
"""Query StashBox for performers"""
|
||||
queryStashBoxPerformer(input: StashBoxPerformerQueryInput!): [StashBoxPerformerQueryResult!]! @deprecated(reason: "use scrapeSinglePerformer or scrapeMultiPerformers")
|
||||
# === end deprecated methods ===
|
||||
|
||||
# Plugins
|
||||
"""List loaded plugins"""
|
||||
@@ -104,9 +129,14 @@ type Query {
|
||||
"""Returns an array of paths for the given path"""
|
||||
directory(path: String): Directory!
|
||||
|
||||
# Metadata
|
||||
# System status
|
||||
systemStatus: SystemStatus!
|
||||
|
||||
jobStatus: MetadataUpdateStatus!
|
||||
# Job status
|
||||
jobQueue: [Job!]
|
||||
findJob(input: FindJobInput!): Job
|
||||
|
||||
dlnaStatus: DLNAStatus!
|
||||
|
||||
# Get everything
|
||||
|
||||
@@ -117,11 +147,6 @@ type Query {
|
||||
|
||||
# Get everything with minimal metadata
|
||||
|
||||
allPerformersSlim: [Performer!]!
|
||||
allStudiosSlim: [Studio!]!
|
||||
allMoviesSlim: [Movie!]!
|
||||
allTagsSlim: [Tag!]!
|
||||
|
||||
# Version
|
||||
version: Version!
|
||||
|
||||
@@ -130,6 +155,9 @@ type Query {
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
setup(input: SetupInput!): Boolean!
|
||||
migrate(input: MigrateInput!): Boolean!
|
||||
|
||||
sceneUpdate(input: SceneUpdateInput!): Scene
|
||||
bulkSceneUpdate(input: BulkSceneUpdateInput!): [Scene!]
|
||||
sceneDestroy(input: SceneDestroyInput!): Boolean!
|
||||
@@ -175,62 +203,96 @@ type Mutation {
|
||||
performerCreate(input: PerformerCreateInput!): Performer
|
||||
performerUpdate(input: PerformerUpdateInput!): Performer
|
||||
performerDestroy(input: PerformerDestroyInput!): Boolean!
|
||||
performersDestroy(ids: [ID!]!): Boolean!
|
||||
bulkPerformerUpdate(input: BulkPerformerUpdateInput!): [Performer!]
|
||||
|
||||
studioCreate(input: StudioCreateInput!): Studio
|
||||
studioUpdate(input: StudioUpdateInput!): Studio
|
||||
studioDestroy(input: StudioDestroyInput!): Boolean!
|
||||
studiosDestroy(ids: [ID!]!): Boolean!
|
||||
|
||||
movieCreate(input: MovieCreateInput!): Movie
|
||||
movieUpdate(input: MovieUpdateInput!): Movie
|
||||
movieDestroy(input: MovieDestroyInput!): Boolean!
|
||||
moviesDestroy(ids: [ID!]!): Boolean!
|
||||
|
||||
tagCreate(input: TagCreateInput!): Tag
|
||||
tagUpdate(input: TagUpdateInput!): Tag
|
||||
tagDestroy(input: TagDestroyInput!): Boolean!
|
||||
tagsDestroy(ids: [ID!]!): Boolean!
|
||||
tagsMerge(input: TagsMergeInput!): Tag
|
||||
|
||||
# Saved filters
|
||||
saveFilter(input: SaveFilterInput!): SavedFilter!
|
||||
destroySavedFilter(input: DestroyFilterInput!): Boolean!
|
||||
setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
|
||||
|
||||
"""Change general configuration options"""
|
||||
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
|
||||
configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult!
|
||||
configureDLNA(input: ConfigDLNAInput!): ConfigDLNAResult!
|
||||
configureScraping(input: ConfigScrapingInput!): ConfigScrapingResult!
|
||||
|
||||
"""Generate and set (or clear) API key"""
|
||||
generateAPIKey(input: GenerateAPIKeyInput!): String!
|
||||
|
||||
"""Returns a link to download the result"""
|
||||
exportObjects(input: ExportObjectsInput!): String
|
||||
|
||||
"""Performs an incremental import. Returns the job ID"""
|
||||
importObjects(input: ImportObjectsInput!): String!
|
||||
importObjects(input: ImportObjectsInput!): ID!
|
||||
|
||||
"""Start an full import. Completely wipes the database and imports from the metadata directory. Returns the job ID"""
|
||||
metadataImport: String!
|
||||
metadataImport: ID!
|
||||
"""Start a full export. Outputs to the metadata directory. Returns the job ID"""
|
||||
metadataExport: String!
|
||||
metadataExport: ID!
|
||||
"""Start a scan. Returns the job ID"""
|
||||
metadataScan(input: ScanMetadataInput!): String!
|
||||
metadataScan(input: ScanMetadataInput!): ID!
|
||||
"""Start generating content. Returns the job ID"""
|
||||
metadataGenerate(input: GenerateMetadataInput!): String!
|
||||
metadataGenerate(input: GenerateMetadataInput!): ID!
|
||||
"""Start auto-tagging. Returns the job ID"""
|
||||
metadataAutoTag(input: AutoTagMetadataInput!): String!
|
||||
metadataAutoTag(input: AutoTagMetadataInput!): ID!
|
||||
"""Clean metadata. Returns the job ID"""
|
||||
metadataClean: String!
|
||||
metadataClean(input: CleanMetadataInput!): ID!
|
||||
"""Migrate generated files for the current hash naming"""
|
||||
migrateHashNaming: String!
|
||||
migrateHashNaming: ID!
|
||||
|
||||
"""Reload scrapers"""
|
||||
reloadScrapers: Boolean!
|
||||
|
||||
"""Run plugin task. Returns the job ID"""
|
||||
runPluginTask(plugin_id: ID!, task_name: String!, args: [PluginArgInput!]): String!
|
||||
runPluginTask(plugin_id: ID!, task_name: String!, args: [PluginArgInput!]): ID!
|
||||
reloadPlugins: Boolean!
|
||||
|
||||
stopJob: Boolean!
|
||||
stopJob(job_id: ID!): Boolean!
|
||||
stopAllJobs: Boolean!
|
||||
|
||||
""" Submit fingerprints to stash-box instance """
|
||||
"""Submit fingerprints to stash-box instance"""
|
||||
submitStashBoxFingerprints(input: StashBoxFingerprintSubmissionInput!): Boolean!
|
||||
|
||||
"""Backup the database. Optionally returns a link to download the database file"""
|
||||
backupDatabase(input: BackupDatabaseInput!): String
|
||||
|
||||
"""Run batch performer tag task. Returns the job ID."""
|
||||
stashBoxBatchPerformerTag(input: StashBoxBatchPerformerTagInput!): String!
|
||||
|
||||
"""Enables DLNA for an optional duration. Has no effect if DLNA is enabled by default"""
|
||||
enableDLNA(input: EnableDLNAInput!): Boolean!
|
||||
"""Disables DLNA for an optional duration. Has no effect if DLNA is disabled by default"""
|
||||
disableDLNA(input: DisableDLNAInput!): Boolean!
|
||||
"""Enables an IP address for DLNA for an optional duration"""
|
||||
addTempDLNAIP(input: AddTempDLNAIPInput!): Boolean!
|
||||
"""Removes an IP address from the temporary DLNA whitelist"""
|
||||
removeTempDLNAIP(input: RemoveTempDLNAIPInput!): Boolean!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
"""Update from the metadata manager"""
|
||||
metadataUpdate: MetadataUpdateStatus!
|
||||
jobsSubscribe: JobStatusUpdate!
|
||||
|
||||
loggingSubscribe: [LogEntry!]!
|
||||
|
||||
scanCompleteSubscribe: Boolean!
|
||||
}
|
||||
|
||||
schema {
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
input SetupInput {
|
||||
"""Empty to indicate $HOME/.stash/config.yml default"""
|
||||
configLocation: String!
|
||||
stashes: [StashConfigInput!]!
|
||||
"""Empty to indicate default"""
|
||||
databaseFile: String!
|
||||
"""Empty to indicate default"""
|
||||
generatedLocation: String!
|
||||
}
|
||||
|
||||
enum StreamingResolutionEnum {
|
||||
"240p", LOW
|
||||
"480p", STANDARD
|
||||
@@ -29,12 +39,18 @@ input ConfigGeneralInput {
|
||||
databasePath: String
|
||||
"""Path to generated files"""
|
||||
generatedPath: String
|
||||
"""Path to import/export files"""
|
||||
metadataPath: String
|
||||
"""Path to cache"""
|
||||
cachePath: String
|
||||
"""Whether to calculate MD5 checksums for scene video files"""
|
||||
calculateMD5: Boolean!
|
||||
"""Hash algorithm to use for generated file naming"""
|
||||
videoFileNamingAlgorithm: HashAlgorithm!
|
||||
"""Number of parallel tasks to start during scan/generate"""
|
||||
parallelTasks: Int
|
||||
"""Include audio stream in previews"""
|
||||
previewAudio: Boolean!
|
||||
"""Number of segments in a preview file"""
|
||||
previewSegments: Int
|
||||
"""Preview segment duration, in seconds"""
|
||||
@@ -49,12 +65,16 @@ input ConfigGeneralInput {
|
||||
maxTranscodeSize: StreamingResolutionEnum
|
||||
"""Max streaming transcode size"""
|
||||
maxStreamingTranscodeSize: StreamingResolutionEnum
|
||||
"""Write image thumbnails to disk when generating on the fly"""
|
||||
writeImageThumbnails: Boolean
|
||||
"""Username"""
|
||||
username: String
|
||||
"""Password"""
|
||||
password: String
|
||||
"""Maximum session cookie age"""
|
||||
maxSessionAge: Int
|
||||
"""Comma separated list of proxies to allow traffic from"""
|
||||
trustedProxies: [String!]
|
||||
"""Name of the log file"""
|
||||
logFile: String
|
||||
"""Whether to also output to stderr"""
|
||||
@@ -75,10 +95,14 @@ input ConfigGeneralInput {
|
||||
excludes: [String!]
|
||||
"""Array of file regexp to exclude from Image Scans"""
|
||||
imageExcludes: [String!]
|
||||
"""Custom Performer Image Location"""
|
||||
customPerformerImageLocation: String
|
||||
"""Scraper user agent string"""
|
||||
scraperUserAgent: String
|
||||
scraperUserAgent: String @deprecated(reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead")
|
||||
"""Scraper CDP path. Path to chrome executable or remote address"""
|
||||
scraperCDPPath: String
|
||||
scraperCDPPath: String @deprecated(reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead")
|
||||
"""Whether the scraper should check for invalid certificates"""
|
||||
scraperCertCheck: Boolean @deprecated(reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead")
|
||||
"""Stash-box instances used for tagging"""
|
||||
stashBoxes: [StashBoxInput!]!
|
||||
}
|
||||
@@ -90,12 +114,22 @@ type ConfigGeneralResult {
|
||||
databasePath: String!
|
||||
"""Path to generated files"""
|
||||
generatedPath: String!
|
||||
"""Path to import/export files"""
|
||||
metadataPath: String!
|
||||
"""Path to the config file used"""
|
||||
configFilePath: String!
|
||||
"""Path to scrapers"""
|
||||
scrapersPath: String!
|
||||
"""Path to cache"""
|
||||
cachePath: String!
|
||||
"""Whether to calculate MD5 checksums for scene video files"""
|
||||
calculateMD5: Boolean!
|
||||
"""Hash algorithm to use for generated file naming"""
|
||||
videoFileNamingAlgorithm: HashAlgorithm!
|
||||
"""Number of parallel tasks to start during scan/generate"""
|
||||
parallelTasks: Int!
|
||||
"""Include audio stream in previews"""
|
||||
previewAudio: Boolean!
|
||||
"""Number of segments in a preview file"""
|
||||
previewSegments: Int!
|
||||
"""Preview segment duration, in seconds"""
|
||||
@@ -110,12 +144,18 @@ type ConfigGeneralResult {
|
||||
maxTranscodeSize: StreamingResolutionEnum
|
||||
"""Max streaming transcode size"""
|
||||
maxStreamingTranscodeSize: StreamingResolutionEnum
|
||||
"""Write image thumbnails to disk when generating on the fly"""
|
||||
writeImageThumbnails: Boolean!
|
||||
"""API Key"""
|
||||
apiKey: String!
|
||||
"""Username"""
|
||||
username: String!
|
||||
"""Password"""
|
||||
password: String!
|
||||
"""Maximum session cookie age"""
|
||||
maxSessionAge: Int!
|
||||
"""Comma separated list of proxies to allow traffic from"""
|
||||
trustedProxies: [String!]!
|
||||
"""Name of the log file"""
|
||||
logFile: String
|
||||
"""Whether to also output to stderr"""
|
||||
@@ -136,15 +176,21 @@ type ConfigGeneralResult {
|
||||
excludes: [String!]!
|
||||
"""Array of file regexp to exclude from Image Scans"""
|
||||
imageExcludes: [String!]!
|
||||
"""Custom Performer Image Location"""
|
||||
customPerformerImageLocation: String
|
||||
"""Scraper user agent string"""
|
||||
scraperUserAgent: String
|
||||
scraperUserAgent: String @deprecated(reason: "use ConfigResult.scraping instead")
|
||||
"""Scraper CDP path. Path to chrome executable or remote address"""
|
||||
scraperCDPPath: String
|
||||
scraperCDPPath: String @deprecated(reason: "use ConfigResult.scraping instead")
|
||||
"""Whether the scraper should check for invalid certificates"""
|
||||
scraperCertCheck: Boolean! @deprecated(reason: "use ConfigResult.scraping instead")
|
||||
"""Stash-box instances used for tagging"""
|
||||
stashBoxes: [StashBox!]!
|
||||
}
|
||||
|
||||
input ConfigInterfaceInput {
|
||||
"""Ordered list of items that should be shown in the menu"""
|
||||
menuItems: [String!]
|
||||
"""Enable sound on mouseover previews"""
|
||||
soundOnPreview: Boolean
|
||||
"""Show title and tags in wall view"""
|
||||
@@ -160,10 +206,19 @@ input ConfigInterfaceInput {
|
||||
"""Custom CSS"""
|
||||
css: String
|
||||
cssEnabled: Boolean
|
||||
"""Interface language"""
|
||||
language: String
|
||||
"""Slideshow Delay"""
|
||||
slideshowDelay: Int
|
||||
"""Handy Connection Key"""
|
||||
handyKey: String
|
||||
"""Funscript Time Offset"""
|
||||
funscriptOffset: Int
|
||||
}
|
||||
|
||||
type ConfigInterfaceResult {
|
||||
"""Ordered list of items that should be shown in the menu"""
|
||||
menuItems: [String!]
|
||||
"""Enable sound on mouseover previews"""
|
||||
soundOnPreview: Boolean
|
||||
"""Show title and tags in wall view"""
|
||||
@@ -174,19 +229,69 @@ type ConfigInterfaceResult {
|
||||
maximumLoopDuration: Int
|
||||
"""If true, video will autostart on load in the scene player"""
|
||||
autostartVideo: Boolean
|
||||
"""If true, studio overlays will be shown as text instead of logo images"""
|
||||
"""If true, studio overlays will be shown as text instead of logo images"""
|
||||
showStudioAsText: Boolean
|
||||
"""Custom CSS"""
|
||||
css: String
|
||||
cssEnabled: Boolean
|
||||
"""Interface language"""
|
||||
language: String
|
||||
"""Slideshow Delay"""
|
||||
slideshowDelay: Int
|
||||
"""Handy Connection Key"""
|
||||
handyKey: String
|
||||
"""Funscript Time Offset"""
|
||||
funscriptOffset: Int
|
||||
}
|
||||
|
||||
input ConfigDLNAInput {
|
||||
serverName: String
|
||||
"""True if DLNA service should be enabled by default"""
|
||||
enabled: Boolean
|
||||
"""List of IPs whitelisted for DLNA service"""
|
||||
whitelistedIPs: [String!]
|
||||
"""List of interfaces to run DLNA on. Empty for all"""
|
||||
interfaces: [String!]
|
||||
}
|
||||
|
||||
type ConfigDLNAResult {
|
||||
serverName: String!
|
||||
"""True if DLNA service should be enabled by default"""
|
||||
enabled: Boolean!
|
||||
"""List of IPs whitelisted for DLNA service"""
|
||||
whitelistedIPs: [String!]!
|
||||
"""List of interfaces to run DLNA on. Empty for all"""
|
||||
interfaces: [String!]!
|
||||
}
|
||||
|
||||
input ConfigScrapingInput {
|
||||
"""Scraper user agent string"""
|
||||
scraperUserAgent: String
|
||||
"""Scraper CDP path. Path to chrome executable or remote address"""
|
||||
scraperCDPPath: String
|
||||
"""Whether the scraper should check for invalid certificates"""
|
||||
scraperCertCheck: Boolean!
|
||||
"""Tags blacklist during scraping"""
|
||||
excludeTagPatterns: [String!]
|
||||
}
|
||||
|
||||
type ConfigScrapingResult {
|
||||
"""Scraper user agent string"""
|
||||
scraperUserAgent: String
|
||||
"""Scraper CDP path. Path to chrome executable or remote address"""
|
||||
scraperCDPPath: String
|
||||
"""Whether the scraper should check for invalid certificates"""
|
||||
scraperCertCheck: Boolean!
|
||||
"""Tags blacklist during scraping"""
|
||||
excludeTagPatterns: [String!]!
|
||||
}
|
||||
|
||||
"""All configuration settings"""
|
||||
type ConfigResult {
|
||||
general: ConfigGeneralResult!
|
||||
interface: ConfigInterfaceResult!
|
||||
dlna: ConfigDLNAResult!
|
||||
scraping: ConfigScrapingResult!
|
||||
}
|
||||
|
||||
"""Directory structure of a path"""
|
||||
@@ -208,3 +313,7 @@ type StashConfig {
|
||||
excludeVideo: Boolean!
|
||||
excludeImage: Boolean!
|
||||
}
|
||||
|
||||
input GenerateAPIKeyInput {
|
||||
clear: Boolean
|
||||
}
|
||||
|
||||
35
graphql/schema/types/dlna.graphql
Normal file
35
graphql/schema/types/dlna.graphql
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
|
||||
type DLNAIP {
|
||||
ipAddress: String!
|
||||
"""Time until IP will be no longer allowed/disallowed"""
|
||||
until: Time
|
||||
}
|
||||
|
||||
type DLNAStatus {
|
||||
running: Boolean!
|
||||
"""If not currently running, time until it will be started. If running, time until it will be stopped"""
|
||||
until: Time
|
||||
recentIPAddresses: [String!]!
|
||||
allowedIPAddresses: [DLNAIP!]!
|
||||
}
|
||||
|
||||
input EnableDLNAInput {
|
||||
"""Duration to enable, in minutes. 0 or null for indefinite."""
|
||||
duration: Int
|
||||
}
|
||||
|
||||
input DisableDLNAInput {
|
||||
"""Duration to enable, in minutes. 0 or null for indefinite."""
|
||||
duration: Int
|
||||
}
|
||||
|
||||
input AddTempDLNAIPInput {
|
||||
address: String!
|
||||
"""Duration to enable, in minutes. 0 or null for indefinite."""
|
||||
duration: Int
|
||||
}
|
||||
|
||||
input RemoveTempDLNAIPInput {
|
||||
address: String!
|
||||
}
|
||||
@@ -6,20 +6,41 @@ enum SortDirectionEnum {
|
||||
input FindFilterType {
|
||||
q: String
|
||||
page: Int
|
||||
"""use per_page = -1 to indicate all results. Defaults to 25."""
|
||||
per_page: Int
|
||||
sort: String
|
||||
direction: SortDirectionEnum
|
||||
}
|
||||
|
||||
enum ResolutionEnum {
|
||||
"144p", VERY_LOW
|
||||
"240p", LOW
|
||||
"360p", R360P
|
||||
"480p", STANDARD
|
||||
"540p", WEB_HD
|
||||
"720p", STANDARD_HD
|
||||
"1080p", FULL_HD
|
||||
"1440p", QUAD_HD
|
||||
"1920p", VR_HD
|
||||
"4k", FOUR_K
|
||||
"5k", FIVE_K
|
||||
"6k", SIX_K
|
||||
"8k", EIGHT_K
|
||||
}
|
||||
|
||||
input ResolutionCriterionInput {
|
||||
value: ResolutionEnum!
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
input PerformerFilterType {
|
||||
AND: PerformerFilterType
|
||||
OR: PerformerFilterType
|
||||
NOT: PerformerFilterType
|
||||
|
||||
name: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
||||
"""Filter by favorite"""
|
||||
filter_favorites: Boolean
|
||||
"""Filter by birth year"""
|
||||
@@ -38,7 +59,7 @@ input PerformerFilterType {
|
||||
measurements: StringCriterionInput
|
||||
"""Filter by fake tits value"""
|
||||
fake_tits: StringCriterionInput
|
||||
"""Filter by career length"""
|
||||
"""Filter by career length"""
|
||||
career_length: StringCriterionInput
|
||||
"""Filter by tattoos"""
|
||||
tattoos: StringCriterionInput
|
||||
@@ -50,30 +71,67 @@ input PerformerFilterType {
|
||||
gender: GenderCriterionInput
|
||||
"""Filter to only include performers missing this property"""
|
||||
is_missing: String
|
||||
"""Filter to only include performers with these tags"""
|
||||
tags: HierarchicalMultiCriterionInput
|
||||
"""Filter by tag count"""
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter by scene count"""
|
||||
scene_count: IntCriterionInput
|
||||
"""Filter by image count"""
|
||||
image_count: IntCriterionInput
|
||||
"""Filter by gallery count"""
|
||||
gallery_count: IntCriterionInput
|
||||
"""Filter by StashID"""
|
||||
stash_id: String
|
||||
stash_id: StringCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
"""Filter by hair color"""
|
||||
hair_color: StringCriterionInput
|
||||
"""Filter by weight"""
|
||||
weight: IntCriterionInput
|
||||
"""Filter by death year"""
|
||||
death_year: IntCriterionInput
|
||||
"""Filter by studios where performer appears in scene/image/gallery"""
|
||||
studios: HierarchicalMultiCriterionInput
|
||||
}
|
||||
|
||||
input SceneMarkerFilterType {
|
||||
"""Filter to only include scene markers with this tag"""
|
||||
tag_id: ID
|
||||
tag_id: ID @deprecated(reason: "use tags filter instead")
|
||||
"""Filter to only include scene markers with these tags"""
|
||||
tags: MultiCriterionInput
|
||||
tags: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include scene markers attached to a scene with these tags"""
|
||||
scene_tags: MultiCriterionInput
|
||||
scene_tags: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include scene markers with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
}
|
||||
|
||||
input SceneFilterType {
|
||||
AND: SceneFilterType
|
||||
OR: SceneFilterType
|
||||
NOT: SceneFilterType
|
||||
|
||||
title: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
||||
"""Filter by file oshash"""
|
||||
oshash: StringCriterionInput
|
||||
"""Filter by file checksum"""
|
||||
checksum: StringCriterionInput
|
||||
"""Filter by file phash"""
|
||||
phash: StringCriterionInput
|
||||
"""Filter by path"""
|
||||
path: StringCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
"""Filter by organized"""
|
||||
organized: Boolean
|
||||
"""Filter by o-counter"""
|
||||
o_counter: IntCriterionInput
|
||||
"""Filter by resolution"""
|
||||
resolution: ResolutionEnum
|
||||
resolution: ResolutionCriterionInput
|
||||
"""Filter by duration (in seconds)"""
|
||||
duration: IntCriterionInput
|
||||
"""Filter to only include scenes which have markers. `true` or `false`"""
|
||||
@@ -81,34 +139,84 @@ input SceneFilterType {
|
||||
"""Filter to only include scenes missing this property"""
|
||||
is_missing: String
|
||||
"""Filter to only include scenes with this studio"""
|
||||
studios: MultiCriterionInput
|
||||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include scenes with this movie"""
|
||||
movies: MultiCriterionInput
|
||||
"""Filter to only include scenes with these tags"""
|
||||
tags: MultiCriterionInput
|
||||
tags: HierarchicalMultiCriterionInput
|
||||
"""Filter by tag count"""
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter to only include scenes with performers with these tags"""
|
||||
performer_tags: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include scenes with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter by StashID"""
|
||||
stash_id: String
|
||||
stash_id: StringCriterionInput
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
"""Filter by interactive"""
|
||||
interactive: Boolean
|
||||
}
|
||||
|
||||
input MovieFilterType {
|
||||
|
||||
name: StringCriterionInput
|
||||
director: StringCriterionInput
|
||||
synopsis: StringCriterionInput
|
||||
|
||||
"""Filter by duration (in seconds)"""
|
||||
duration: IntCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
"""Filter to only include movies with this studio"""
|
||||
studios: MultiCriterionInput
|
||||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include movies missing this property"""
|
||||
is_missing: String
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
"""Filter to only include movies where performer appears in a scene"""
|
||||
performers: MultiCriterionInput
|
||||
}
|
||||
|
||||
input StudioFilterType {
|
||||
AND: StudioFilterType
|
||||
OR: StudioFilterType
|
||||
NOT: StudioFilterType
|
||||
|
||||
name: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
"""Filter to only include studios with this parent studio"""
|
||||
parents: MultiCriterionInput
|
||||
"""Filter by StashID"""
|
||||
stash_id: String
|
||||
stash_id: StringCriterionInput
|
||||
"""Filter to only include studios missing this property"""
|
||||
is_missing: String
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
"""Filter by scene count"""
|
||||
scene_count: IntCriterionInput
|
||||
"""Filter by image count"""
|
||||
image_count: IntCriterionInput
|
||||
"""Filter by gallery count"""
|
||||
gallery_count: IntCriterionInput
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
"""Filter by studio aliases"""
|
||||
aliases: StringCriterionInput
|
||||
}
|
||||
|
||||
input GalleryFilterType {
|
||||
AND: GalleryFilterType
|
||||
OR: GalleryFilterType
|
||||
NOT: GalleryFilterType
|
||||
|
||||
title: StringCriterionInput
|
||||
details: StringCriterionInput
|
||||
|
||||
"""Filter by file checksum"""
|
||||
checksum: StringCriterionInput
|
||||
"""Filter by path"""
|
||||
path: StringCriterionInput
|
||||
"""Filter to only include galleries missing this property"""
|
||||
@@ -117,46 +225,103 @@ input GalleryFilterType {
|
||||
is_zip: Boolean
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
"""Filter by organized"""
|
||||
organized: Boolean
|
||||
"""Filter by average image resolution"""
|
||||
average_resolution: ResolutionEnum
|
||||
"""Filter to only include scenes with this studio"""
|
||||
studios: MultiCriterionInput
|
||||
"""Filter to only include scenes with these tags"""
|
||||
tags: MultiCriterionInput
|
||||
"""Filter to only include scenes with these performers"""
|
||||
average_resolution: ResolutionCriterionInput
|
||||
"""Filter to only include galleries with this studio"""
|
||||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include galleries with these tags"""
|
||||
tags: HierarchicalMultiCriterionInput
|
||||
"""Filter by tag count"""
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter to only include galleries with performers with these tags"""
|
||||
performer_tags: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include galleries with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter by number of images in this gallery"""
|
||||
image_count: IntCriterionInput
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
}
|
||||
|
||||
input TagFilterType {
|
||||
AND: TagFilterType
|
||||
OR: TagFilterType
|
||||
NOT: TagFilterType
|
||||
|
||||
"""Filter by tag name"""
|
||||
name: StringCriterionInput
|
||||
|
||||
"""Filter by tag aliases"""
|
||||
aliases: StringCriterionInput
|
||||
|
||||
"""Filter to only include tags missing this property"""
|
||||
is_missing: String
|
||||
|
||||
"""Filter by number of scenes with this tag"""
|
||||
scene_count: IntCriterionInput
|
||||
|
||||
"""Filter by number of images with this tag"""
|
||||
image_count: IntCriterionInput
|
||||
|
||||
"""Filter by number of galleries with this tag"""
|
||||
gallery_count: IntCriterionInput
|
||||
|
||||
"""Filter by number of performers with this tag"""
|
||||
performer_count: IntCriterionInput
|
||||
|
||||
"""Filter by number of markers with this tag"""
|
||||
marker_count: IntCriterionInput
|
||||
|
||||
"""Filter by parent tags"""
|
||||
parents: HierarchicalMultiCriterionInput
|
||||
|
||||
"""Filter by child tags"""
|
||||
children: HierarchicalMultiCriterionInput
|
||||
|
||||
"""Filter by number of parent tags the tag has"""
|
||||
parent_count: IntCriterionInput
|
||||
|
||||
"""Filter by number f child tags the tag has"""
|
||||
child_count: IntCriterionInput
|
||||
}
|
||||
|
||||
input ImageFilterType {
|
||||
AND: ImageFilterType
|
||||
OR: ImageFilterType
|
||||
NOT: ImageFilterType
|
||||
|
||||
title: StringCriterionInput
|
||||
|
||||
"""Filter by file checksum"""
|
||||
checksum: StringCriterionInput
|
||||
"""Filter by path"""
|
||||
path: StringCriterionInput
|
||||
"""Filter by rating"""
|
||||
rating: IntCriterionInput
|
||||
"""Filter by organized"""
|
||||
organized: Boolean
|
||||
"""Filter by o-counter"""
|
||||
o_counter: IntCriterionInput
|
||||
"""Filter by resolution"""
|
||||
resolution: ResolutionEnum
|
||||
resolution: ResolutionCriterionInput
|
||||
"""Filter to only include images missing this property"""
|
||||
is_missing: String
|
||||
"""Filter to only include images with this studio"""
|
||||
studios: MultiCriterionInput
|
||||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include images with these tags"""
|
||||
tags: MultiCriterionInput
|
||||
tags: HierarchicalMultiCriterionInput
|
||||
"""Filter by tag count"""
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter to only include images with performers with these tags"""
|
||||
performer_tags: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include images with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter to only include images with these galleries"""
|
||||
galleries: MultiCriterionInput
|
||||
}
|
||||
@@ -178,6 +343,14 @@ enum CriterionModifier {
|
||||
INCLUDES_ALL,
|
||||
INCLUDES,
|
||||
EXCLUDES,
|
||||
"""MATCHES REGEX"""
|
||||
MATCHES_REGEX,
|
||||
"""NOT MATCHES REGEX"""
|
||||
NOT_MATCHES_REGEX,
|
||||
""">= AND <="""
|
||||
BETWEEN,
|
||||
"""< OR >"""
|
||||
NOT_BETWEEN,
|
||||
}
|
||||
|
||||
input StringCriterionInput {
|
||||
@@ -187,6 +360,7 @@ input StringCriterionInput {
|
||||
|
||||
input IntCriterionInput {
|
||||
value: Int!
|
||||
value2: Int
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
@@ -199,3 +373,47 @@ input GenderCriterionInput {
|
||||
value: GenderEnum
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
input HierarchicalMultiCriterionInput {
|
||||
value: [ID!]
|
||||
modifier: CriterionModifier!
|
||||
depth: Int
|
||||
}
|
||||
|
||||
enum FilterMode {
|
||||
SCENES,
|
||||
PERFORMERS,
|
||||
STUDIOS,
|
||||
GALLERIES,
|
||||
SCENE_MARKERS,
|
||||
MOVIES,
|
||||
TAGS,
|
||||
IMAGES,
|
||||
}
|
||||
|
||||
type SavedFilter {
|
||||
id: ID!
|
||||
mode: FilterMode!
|
||||
name: String!
|
||||
"""JSON-encoded filter string"""
|
||||
filter: String!
|
||||
}
|
||||
|
||||
input SaveFilterInput {
|
||||
"""provide ID to overwrite existing filter"""
|
||||
id: ID
|
||||
mode: FilterMode!
|
||||
name: String!
|
||||
"""JSON-encoded filter string"""
|
||||
filter: String!
|
||||
}
|
||||
|
||||
input DestroyFilterInput {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
input SetDefaultFilterInput {
|
||||
mode: FilterMode!
|
||||
"""JSON-encoded filter string - null to clear"""
|
||||
filter: String
|
||||
}
|
||||
|
||||
@@ -8,7 +8,12 @@ type Gallery {
|
||||
date: String
|
||||
details: String
|
||||
rating: Int
|
||||
scene: Scene
|
||||
organized: Boolean!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
file_mod_time: Time
|
||||
|
||||
scenes: [Scene!]!
|
||||
studio: Studio
|
||||
image_count: Int!
|
||||
tags: [Tag!]!
|
||||
@@ -31,7 +36,8 @@ input GalleryCreateInput {
|
||||
date: String
|
||||
details: String
|
||||
rating: Int
|
||||
scene_id: ID
|
||||
organized: Boolean
|
||||
scene_ids: [ID!]
|
||||
studio_id: ID
|
||||
tag_ids: [ID!]
|
||||
performer_ids: [ID!]
|
||||
@@ -45,7 +51,8 @@ input GalleryUpdateInput {
|
||||
date: String
|
||||
details: String
|
||||
rating: Int
|
||||
scene_id: ID
|
||||
organized: Boolean
|
||||
scene_ids: [ID!]
|
||||
studio_id: ID
|
||||
tag_ids: [ID!]
|
||||
performer_ids: [ID!]
|
||||
@@ -58,7 +65,8 @@ input BulkGalleryUpdateInput {
|
||||
date: String
|
||||
details: String
|
||||
rating: Int
|
||||
scene_id: ID
|
||||
organized: Boolean
|
||||
scene_ids: BulkUpdateIds
|
||||
studio_id: ID
|
||||
tag_ids: BulkUpdateIds
|
||||
performer_ids: BulkUpdateIds
|
||||
|
||||
@@ -4,7 +4,11 @@ type Image {
|
||||
title: String
|
||||
rating: Int
|
||||
o_counter: Int
|
||||
organized: Boolean!
|
||||
path: String!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
file_mod_time: Time
|
||||
|
||||
file: ImageFileType! # Resolver
|
||||
paths: ImagePathsType! # Resolver
|
||||
@@ -31,6 +35,7 @@ input ImageUpdateInput {
|
||||
id: ID!
|
||||
title: String
|
||||
rating: Int
|
||||
organized: Boolean
|
||||
|
||||
studio_id: ID
|
||||
performer_ids: [ID!]
|
||||
@@ -43,6 +48,7 @@ input BulkImageUpdateInput {
|
||||
ids: [ID!]
|
||||
title: String
|
||||
rating: Int
|
||||
organized: Boolean
|
||||
|
||||
studio_id: ID
|
||||
performer_ids: BulkUpdateIds
|
||||
|
||||
33
graphql/schema/types/job.graphql
Normal file
33
graphql/schema/types/job.graphql
Normal file
@@ -0,0 +1,33 @@
|
||||
enum JobStatus {
|
||||
READY
|
||||
RUNNING
|
||||
FINISHED
|
||||
STOPPING
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
type Job {
|
||||
id: ID!
|
||||
status: JobStatus!
|
||||
subTasks: [String!]
|
||||
description: String!
|
||||
progress: Float
|
||||
startTime: Time
|
||||
endTime: Time
|
||||
addTime: Time!
|
||||
}
|
||||
|
||||
input FindJobInput {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
enum JobStatusUpdateType {
|
||||
ADD
|
||||
REMOVE
|
||||
UPDATE
|
||||
}
|
||||
|
||||
type JobStatusUpdate {
|
||||
type: JobStatusUpdateType!
|
||||
job: Job!
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
scalar Upload
|
||||
|
||||
input GenerateMetadataInput {
|
||||
sprites: Boolean!
|
||||
previews: Boolean!
|
||||
imagePreviews: Boolean!
|
||||
sprites: Boolean
|
||||
previews: Boolean
|
||||
imagePreviews: Boolean
|
||||
previewOptions: GeneratePreviewOptionsInput
|
||||
markers: Boolean!
|
||||
transcodes: Boolean!
|
||||
markers: Boolean
|
||||
markerImagePreviews: Boolean
|
||||
markerScreenshots: Boolean
|
||||
transcodes: Boolean
|
||||
phashes: Boolean
|
||||
|
||||
"""scene ids to generate for"""
|
||||
sceneIDs: [ID!]
|
||||
@@ -32,10 +35,30 @@ input GeneratePreviewOptionsInput {
|
||||
|
||||
input ScanMetadataInput {
|
||||
paths: [String!]
|
||||
useFileMetadata: Boolean!
|
||||
"""Set name, date, details from metadata (if present)"""
|
||||
useFileMetadata: Boolean
|
||||
"""Strip file extension from title"""
|
||||
stripFileExtension: Boolean
|
||||
"""Generate previews during scan"""
|
||||
scanGeneratePreviews: Boolean
|
||||
"""Generate image previews during scan"""
|
||||
scanGenerateImagePreviews: Boolean
|
||||
"""Generate sprites during scan"""
|
||||
scanGenerateSprites: Boolean
|
||||
"""Generate phashes during scan"""
|
||||
scanGeneratePhashes: Boolean
|
||||
"""Generate image thumbnails during scan"""
|
||||
scanGenerateThumbnails: Boolean
|
||||
}
|
||||
|
||||
input CleanMetadataInput {
|
||||
"""Do a dry run. Don't delete any files"""
|
||||
dryRun: Boolean!
|
||||
}
|
||||
|
||||
input AutoTagMetadataInput {
|
||||
"""Paths to tag, null for all files"""
|
||||
paths: [String!]
|
||||
"""IDs of performers to tag files with, or "*" for all"""
|
||||
performers: [String!]
|
||||
"""IDs of studios to tag files with, or "*" for all"""
|
||||
@@ -44,12 +67,6 @@ input AutoTagMetadataInput {
|
||||
tags: [String!]
|
||||
}
|
||||
|
||||
type MetadataUpdateStatus {
|
||||
progress: Float!
|
||||
status: String!
|
||||
message: String!
|
||||
}
|
||||
|
||||
input ExportObjectTypeInput {
|
||||
ids: [String!]
|
||||
all: Boolean
|
||||
@@ -83,3 +100,25 @@ input ImportObjectsInput {
|
||||
duplicateBehaviour: ImportDuplicateEnum!
|
||||
missingRefBehaviour: ImportMissingRefEnum!
|
||||
}
|
||||
|
||||
input BackupDatabaseInput {
|
||||
download: Boolean
|
||||
}
|
||||
|
||||
enum SystemStatusEnum {
|
||||
SETUP
|
||||
NEEDS_MIGRATION
|
||||
OK
|
||||
}
|
||||
|
||||
type SystemStatus {
|
||||
databaseSchema: Int
|
||||
databasePath: String
|
||||
configPath: String
|
||||
appSchema: Int!
|
||||
status: SystemStatusEnum!
|
||||
}
|
||||
|
||||
input MigrateInput {
|
||||
backupPath: String!
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ type Movie {
|
||||
director: String
|
||||
synopsis: String
|
||||
url: String
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
|
||||
front_image_path: String # Resolver
|
||||
back_image_path: String # Resolver
|
||||
scene_count: Int # Resolver
|
||||
scenes: [Scene!]!
|
||||
}
|
||||
|
||||
input MovieCreateInput {
|
||||
@@ -28,8 +31,9 @@ input MovieCreateInput {
|
||||
director: String
|
||||
synopsis: String
|
||||
url: String
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
front_image: String
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
back_image: String
|
||||
}
|
||||
|
||||
@@ -44,8 +48,9 @@ input MovieUpdateInput {
|
||||
director: String
|
||||
synopsis: String
|
||||
url: String
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
front_image: String
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
back_image: String
|
||||
}
|
||||
|
||||
@@ -56,4 +61,4 @@ input MovieDestroyInput {
|
||||
type FindMoviesResultType {
|
||||
count: Int!
|
||||
movies: [Movie!]!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,23 @@ type Performer {
|
||||
piercings: String
|
||||
aliases: String
|
||||
favorite: Boolean!
|
||||
tags: [Tag!]!
|
||||
|
||||
image_path: String # Resolver
|
||||
scene_count: Int # Resolver
|
||||
image_count: Int # Resolver
|
||||
gallery_count: Int # Resolver
|
||||
scenes: [Scene!]!
|
||||
stash_ids: [StashID!]!
|
||||
rating: Int
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
weight: Int
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
movie_count: Int
|
||||
movies: [Movie!]!
|
||||
}
|
||||
|
||||
input PerformerCreateInput {
|
||||
@@ -52,9 +64,15 @@ input PerformerCreateInput {
|
||||
twitter: String
|
||||
instagram: String
|
||||
favorite: Boolean
|
||||
"""This should be base64 encoded"""
|
||||
tag_ids: [ID!]
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
rating: Int
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
weight: Int
|
||||
}
|
||||
|
||||
input PerformerUpdateInput {
|
||||
@@ -76,9 +94,42 @@ input PerformerUpdateInput {
|
||||
twitter: String
|
||||
instagram: String
|
||||
favorite: Boolean
|
||||
"""This should be base64 encoded"""
|
||||
tag_ids: [ID!]
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
rating: Int
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
weight: Int
|
||||
}
|
||||
|
||||
input BulkPerformerUpdateInput {
|
||||
clientMutationId: String
|
||||
ids: [ID!]
|
||||
url: String
|
||||
gender: GenderEnum
|
||||
birthdate: String
|
||||
ethnicity: String
|
||||
country: String
|
||||
eye_color: String
|
||||
height: String
|
||||
measurements: String
|
||||
fake_tits: String
|
||||
career_length: String
|
||||
tattoos: String
|
||||
piercings: String
|
||||
aliases: String
|
||||
twitter: String
|
||||
instagram: String
|
||||
favorite: Boolean
|
||||
tag_ids: BulkUpdateIds
|
||||
rating: Int
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
weight: Int
|
||||
}
|
||||
|
||||
input PerformerDestroyInput {
|
||||
|
||||
@@ -7,6 +7,7 @@ type Plugin {
|
||||
version: String
|
||||
|
||||
tasks: [PluginTask!]
|
||||
hooks: [PluginHook!]
|
||||
}
|
||||
|
||||
type PluginTask {
|
||||
@@ -15,6 +16,13 @@ type PluginTask {
|
||||
plugin: Plugin!
|
||||
}
|
||||
|
||||
type PluginHook {
|
||||
name: String!
|
||||
description: String
|
||||
hooks: [String!]
|
||||
plugin: Plugin!
|
||||
}
|
||||
|
||||
type PluginResult {
|
||||
error: String
|
||||
result: String
|
||||
|
||||
@@ -5,11 +5,15 @@ type SceneMarker {
|
||||
seconds: Float!
|
||||
primary_tag: Tag!
|
||||
tags: [Tag!]!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
|
||||
"""The path to stream this marker"""
|
||||
stream: String! # Resolver
|
||||
"""The path to the preview image for this marker"""
|
||||
preview: String! # Resolver
|
||||
"""The path to the screenshot image for this marker"""
|
||||
screenshot: String! # Resolver
|
||||
}
|
||||
|
||||
input SceneMarkerCreateInput {
|
||||
|
||||
@@ -16,6 +16,8 @@ type ScenePathsType {
|
||||
webp: String # Resolver
|
||||
vtt: String # Resolver
|
||||
chapters_vtt: String # Resolver
|
||||
sprite: String # Resolver
|
||||
funscript: String # Resolver
|
||||
}
|
||||
|
||||
type SceneMovie {
|
||||
@@ -32,14 +34,20 @@ type Scene {
|
||||
url: String
|
||||
date: String
|
||||
rating: Int
|
||||
organized: Boolean!
|
||||
o_counter: Int
|
||||
path: String!
|
||||
phash: String
|
||||
interactive: Boolean!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
file_mod_time: Time
|
||||
|
||||
file: SceneFileType! # Resolver
|
||||
paths: ScenePathsType! # Resolver
|
||||
|
||||
scene_markers: [SceneMarker!]!
|
||||
gallery: Gallery
|
||||
galleries: [Gallery!]!
|
||||
studio: Studio
|
||||
movies: [SceneMovie!]!
|
||||
tags: [Tag!]!
|
||||
@@ -60,12 +68,13 @@ input SceneUpdateInput {
|
||||
url: String
|
||||
date: String
|
||||
rating: Int
|
||||
organized: Boolean
|
||||
studio_id: ID
|
||||
gallery_id: ID
|
||||
gallery_ids: [ID!]
|
||||
performer_ids: [ID!]
|
||||
movies: [SceneMovieInput!]
|
||||
tag_ids: [ID!]
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
cover_image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
}
|
||||
@@ -89,10 +98,12 @@ input BulkSceneUpdateInput {
|
||||
url: String
|
||||
date: String
|
||||
rating: Int
|
||||
organized: Boolean
|
||||
studio_id: ID
|
||||
gallery_id: ID
|
||||
gallery_ids: BulkUpdateIds
|
||||
performer_ids: BulkUpdateIds
|
||||
tag_ids: BulkUpdateIds
|
||||
movie_ids: BulkUpdateIds
|
||||
}
|
||||
|
||||
input SceneDestroyInput {
|
||||
@@ -115,7 +126,8 @@ type FindScenesResultType {
|
||||
input SceneParserInput {
|
||||
ignoreWords: [String!],
|
||||
whitespaceCharacters: String,
|
||||
capitalizeTitle: Boolean
|
||||
capitalizeTitle: Boolean,
|
||||
ignoreOrganized: Boolean
|
||||
}
|
||||
|
||||
type SceneMovieID {
|
||||
@@ -131,7 +143,7 @@ type SceneParserResult {
|
||||
date: String
|
||||
rating: Int
|
||||
studio_id: ID
|
||||
gallery_id: ID
|
||||
gallery_ids: [ID!]
|
||||
performer_ids: [ID!]
|
||||
movies: [SceneMovieID!]
|
||||
tag_ids: [ID!]
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
type ScrapedMovieStudio {
|
||||
"""Set if studio matched"""
|
||||
id: ID
|
||||
name: String!
|
||||
url: String
|
||||
}
|
||||
|
||||
"""A movie from a scraping operation..."""
|
||||
type ScrapedMovie {
|
||||
stored_id: ID
|
||||
name: String
|
||||
aliases: String
|
||||
duration: String
|
||||
@@ -15,10 +9,11 @@ type ScrapedMovie {
|
||||
director: String
|
||||
url: String
|
||||
synopsis: String
|
||||
studio: ScrapedMovieStudio
|
||||
studio: ScrapedStudio
|
||||
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a base64 encoded data URL"""
|
||||
front_image: String
|
||||
"""This should be a base64 encoded data URL"""
|
||||
back_image: String
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""A performer from a scraping operation..."""
|
||||
type ScrapedPerformer {
|
||||
"""Set if performer matched"""
|
||||
stored_id: ID
|
||||
name: String
|
||||
gender: String
|
||||
url: String
|
||||
@@ -16,12 +18,21 @@ type ScrapedPerformer {
|
||||
tattoos: String
|
||||
piercings: String
|
||||
aliases: String
|
||||
tags: [ScrapedTag!]
|
||||
|
||||
"""This should be base64 encoded"""
|
||||
image: String
|
||||
"""This should be a base64 encoded data URL"""
|
||||
image: String @deprecated(reason: "use images instead")
|
||||
images: [String!]
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
weight: String
|
||||
remote_site_id: String
|
||||
}
|
||||
|
||||
input ScrapedPerformerInput {
|
||||
"""Set if performer matched"""
|
||||
stored_id: ID
|
||||
name: String
|
||||
gender: String
|
||||
url: String
|
||||
@@ -39,5 +50,11 @@ input ScrapedPerformerInput {
|
||||
piercings: String
|
||||
aliases: String
|
||||
|
||||
# not including tags for the input
|
||||
# not including image for the input
|
||||
details: String
|
||||
death_date: String
|
||||
hair_color: String
|
||||
weight: String
|
||||
remote_site_id: String
|
||||
}
|
||||
@@ -26,44 +26,7 @@ type Scraper {
|
||||
movie: ScraperSpec
|
||||
}
|
||||
|
||||
type ScrapedScenePerformer {
|
||||
"""Set if performer matched"""
|
||||
stored_id: ID
|
||||
name: String!
|
||||
gender: String
|
||||
url: String
|
||||
twitter: String
|
||||
instagram: String
|
||||
birthdate: String
|
||||
ethnicity: String
|
||||
country: String
|
||||
eye_color: String
|
||||
height: String
|
||||
measurements: String
|
||||
fake_tits: String
|
||||
career_length: String
|
||||
tattoos: String
|
||||
piercings: String
|
||||
aliases: String
|
||||
|
||||
remote_site_id: String
|
||||
images: [String!]
|
||||
}
|
||||
|
||||
type ScrapedSceneMovie {
|
||||
"""Set if movie matched"""
|
||||
stored_id: ID
|
||||
name: String!
|
||||
aliases: String
|
||||
duration: String
|
||||
date: String
|
||||
rating: String
|
||||
director: String
|
||||
synopsis: String
|
||||
url: String
|
||||
}
|
||||
|
||||
type ScrapedSceneStudio {
|
||||
type ScrapedStudio {
|
||||
"""Set if studio matched"""
|
||||
stored_id: ID
|
||||
name: String!
|
||||
@@ -72,7 +35,7 @@ type ScrapedSceneStudio {
|
||||
remote_site_id: String
|
||||
}
|
||||
|
||||
type ScrapedSceneTag {
|
||||
type ScrapedTag {
|
||||
"""Set if tag matched"""
|
||||
stored_id: ID
|
||||
name: String!
|
||||
@@ -84,33 +47,106 @@ type ScrapedScene {
|
||||
url: String
|
||||
date: String
|
||||
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a base64 encoded data URL"""
|
||||
image: String
|
||||
|
||||
file: SceneFileType # Resolver
|
||||
|
||||
studio: ScrapedSceneStudio
|
||||
tags: [ScrapedSceneTag!]
|
||||
performers: [ScrapedScenePerformer!]
|
||||
movies: [ScrapedSceneMovie!]
|
||||
studio: ScrapedStudio
|
||||
tags: [ScrapedTag!]
|
||||
performers: [ScrapedPerformer!]
|
||||
movies: [ScrapedMovie!]
|
||||
|
||||
remote_site_id: String
|
||||
duration: Int
|
||||
fingerprints: [StashBoxFingerprint!]
|
||||
}
|
||||
|
||||
input ScrapedSceneInput {
|
||||
title: String
|
||||
details: String
|
||||
url: String
|
||||
date: String
|
||||
|
||||
# no image, file, duration or relationships
|
||||
|
||||
remote_site_id: String
|
||||
}
|
||||
|
||||
type ScrapedGallery {
|
||||
title: String
|
||||
details: String
|
||||
url: String
|
||||
date: String
|
||||
|
||||
studio: ScrapedSceneStudio
|
||||
tags: [ScrapedSceneTag!]
|
||||
performers: [ScrapedScenePerformer!]
|
||||
studio: ScrapedStudio
|
||||
tags: [ScrapedTag!]
|
||||
performers: [ScrapedPerformer!]
|
||||
}
|
||||
|
||||
input StashBoxQueryInput {
|
||||
input ScrapedGalleryInput {
|
||||
title: String
|
||||
details: String
|
||||
url: String
|
||||
date: String
|
||||
|
||||
# no studio, tags or performers
|
||||
}
|
||||
|
||||
input ScraperSourceInput {
|
||||
"""Index of the configured stash-box instance to use. Should be unset if scraper_id is set"""
|
||||
stash_box_index: Int
|
||||
"""Scraper ID to scrape with. Should be unset if stash_box_index is set"""
|
||||
scraper_id: ID
|
||||
}
|
||||
|
||||
input ScrapeSingleSceneInput {
|
||||
"""Instructs to query by string"""
|
||||
query: String
|
||||
"""Instructs to query by scene fingerprints"""
|
||||
scene_id: ID
|
||||
"""Instructs to query by scene fragment"""
|
||||
scene_input: ScrapedSceneInput
|
||||
}
|
||||
|
||||
input ScrapeMultiScenesInput {
|
||||
"""Instructs to query by scene fingerprints"""
|
||||
scene_ids: [ID!]
|
||||
}
|
||||
|
||||
input ScrapeSinglePerformerInput {
|
||||
"""Instructs to query by string"""
|
||||
query: String
|
||||
"""Instructs to query by performer id"""
|
||||
performer_id: ID
|
||||
"""Instructs to query by performer fragment"""
|
||||
performer_input: ScrapedPerformerInput
|
||||
}
|
||||
|
||||
input ScrapeMultiPerformersInput {
|
||||
"""Instructs to query by scene fingerprints"""
|
||||
performer_ids: [ID!]
|
||||
}
|
||||
|
||||
input ScrapeSingleGalleryInput {
|
||||
"""Instructs to query by string"""
|
||||
query: String
|
||||
"""Instructs to query by gallery id"""
|
||||
gallery_id: ID
|
||||
"""Instructs to query by gallery fragment"""
|
||||
gallery_input: ScrapedGalleryInput
|
||||
}
|
||||
|
||||
input ScrapeSingleMovieInput {
|
||||
"""Instructs to query by string"""
|
||||
query: String
|
||||
"""Instructs to query by movie id"""
|
||||
movie_id: ID
|
||||
"""Instructs to query by gallery fragment"""
|
||||
movie_input: ScrapedMovieInput
|
||||
}
|
||||
|
||||
input StashBoxSceneQueryInput {
|
||||
"""Index of the configured stash-box instance to use"""
|
||||
stash_box_index: Int!
|
||||
"""Instructs query by scene fingerprints"""
|
||||
@@ -119,8 +155,30 @@ input StashBoxQueryInput {
|
||||
q: String
|
||||
}
|
||||
|
||||
input StashBoxPerformerQueryInput {
|
||||
"""Index of the configured stash-box instance to use"""
|
||||
stash_box_index: Int!
|
||||
"""Instructs query by scene fingerprints"""
|
||||
performer_ids: [ID!]
|
||||
"""Query by query string"""
|
||||
q: String
|
||||
}
|
||||
|
||||
type StashBoxPerformerQueryResult {
|
||||
query: String!
|
||||
results: [ScrapedPerformer!]!
|
||||
}
|
||||
|
||||
type StashBoxFingerprint {
|
||||
algorithm: String!
|
||||
hash: String!
|
||||
duration: Int!
|
||||
}
|
||||
|
||||
input StashBoxBatchPerformerTagInput {
|
||||
endpoint: Int!
|
||||
exclude_fields: [String!]
|
||||
refresh: Boolean!
|
||||
performer_ids: [ID!]
|
||||
performer_names: [String!]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
type StatsResultType {
|
||||
scene_count: Int!
|
||||
scenes_size: Int!
|
||||
scenes_size: Float!
|
||||
scenes_duration: Float!
|
||||
image_count: Int!
|
||||
images_size: Int!
|
||||
images_size: Float!
|
||||
gallery_count: Int!
|
||||
performer_count: Int!
|
||||
studio_count: Int!
|
||||
|
||||
@@ -5,19 +5,31 @@ type Studio {
|
||||
url: String
|
||||
parent_studio: Studio
|
||||
child_studios: [Studio!]!
|
||||
aliases: [String!]!
|
||||
|
||||
image_path: String # Resolver
|
||||
scene_count: Int # Resolver
|
||||
image_count: Int # Resolver
|
||||
gallery_count: Int # Resolver
|
||||
stash_ids: [StashID!]!
|
||||
rating: Int
|
||||
details: String
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
movie_count: Int
|
||||
movies: [Movie!]!
|
||||
}
|
||||
|
||||
input StudioCreateInput {
|
||||
name: String!
|
||||
url: String
|
||||
parent_id: ID
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
rating: Int
|
||||
details: String
|
||||
aliases: [String!]
|
||||
}
|
||||
|
||||
input StudioUpdateInput {
|
||||
@@ -25,9 +37,12 @@ input StudioUpdateInput {
|
||||
name: String
|
||||
url: String
|
||||
parent_id: ID,
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
stash_ids: [StashIDInput!]
|
||||
rating: Int
|
||||
details: String
|
||||
aliases: [String!]
|
||||
}
|
||||
|
||||
input StudioDestroyInput {
|
||||
|
||||
@@ -1,25 +1,42 @@
|
||||
type Tag {
|
||||
id: ID!
|
||||
name: String!
|
||||
aliases: [String!]!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
|
||||
image_path: String # Resolver
|
||||
scene_count: Int # Resolver
|
||||
scene_marker_count: Int # Resolver
|
||||
image_count: Int # Resolver
|
||||
gallery_count: Int # Resolver
|
||||
performer_count: Int
|
||||
|
||||
parents: [Tag!]!
|
||||
children: [Tag!]!
|
||||
}
|
||||
|
||||
input TagCreateInput {
|
||||
name: String!
|
||||
aliases: [String!]
|
||||
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
|
||||
parent_ids: [ID!]
|
||||
child_ids: [ID!]
|
||||
}
|
||||
|
||||
input TagUpdateInput {
|
||||
id: ID!
|
||||
name: String!
|
||||
name: String
|
||||
aliases: [String!]
|
||||
|
||||
"""This should be base64 encoded"""
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
|
||||
parent_ids: [ID!]
|
||||
child_ids: [ID!]
|
||||
}
|
||||
|
||||
input TagDestroyInput {
|
||||
@@ -29,4 +46,9 @@ input TagDestroyInput {
|
||||
type FindTagsResultType {
|
||||
count: Int!
|
||||
tags: [Tag!]!
|
||||
}
|
||||
}
|
||||
|
||||
input TagsMergeInput {
|
||||
source: [ID!]!
|
||||
destination: ID!
|
||||
}
|
||||
|
||||
@@ -75,6 +75,11 @@ fragment PerformerFragment on Performer {
|
||||
piercings {
|
||||
...BodyModificationFragment
|
||||
}
|
||||
details
|
||||
death_date {
|
||||
...FuzzyDateFragment
|
||||
}
|
||||
weight
|
||||
}
|
||||
|
||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||
@@ -134,6 +139,18 @@ query SearchScene($term: String!) {
|
||||
}
|
||||
}
|
||||
|
||||
query SearchPerformer($term: String!) {
|
||||
searchPerformer(term: $term) {
|
||||
...PerformerFragment
|
||||
}
|
||||
}
|
||||
|
||||
query FindPerformerByID($id: ID!) {
|
||||
findPerformer(id: $id) {
|
||||
...PerformerFragment
|
||||
}
|
||||
}
|
||||
|
||||
mutation SubmitFingerprint($input: FingerprintSubmission!) {
|
||||
submitFingerprint(input: $input)
|
||||
}
|
||||
|
||||
35
main.go
35
main.go
@@ -2,27 +2,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/pprof"
|
||||
"syscall"
|
||||
|
||||
"github.com/stashapp/stash/pkg/api"
|
||||
"github.com/stashapp/stash/pkg/database"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
|
||||
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
)
|
||||
|
||||
//go:embed ui/v2.5/build
|
||||
var uiBox embed.FS
|
||||
|
||||
//go:embed ui/login
|
||||
var loginUIBox embed.FS
|
||||
|
||||
func main() {
|
||||
manager.Initialize()
|
||||
api.Start(uiBox, loginUIBox)
|
||||
|
||||
// perform the post-migration for new databases
|
||||
if database.Initialize(config.GetDatabasePath()) {
|
||||
manager.GetInstance().PostMigrate()
|
||||
}
|
||||
|
||||
api.Start()
|
||||
// stop any profiling at exit
|
||||
defer pprof.StopCPUProfile()
|
||||
blockForever()
|
||||
|
||||
err := manager.GetInstance().Shutdown()
|
||||
if err != nil {
|
||||
logger.Errorf("Error when closing: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func blockForever() {
|
||||
select {}
|
||||
// handle signals
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
<-signals
|
||||
}
|
||||
|
||||
140
pkg/api/authentication.go
Normal file
140
pkg/api/authentication.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/session"
|
||||
)
|
||||
|
||||
const loginEndPoint = "/login"
|
||||
|
||||
const (
|
||||
tripwireActivatedErrMsg = "Stash is exposed to the public internet without authentication, and is not serving any more content to protect your privacy. " +
|
||||
"More information and fixes are available at https://github.com/stashapp/stash/wiki/Authentication-Required-When-Accessing-Stash-From-the-Internet"
|
||||
|
||||
externalAccessErrMsg = "You have attempted to access Stash over the internet, and authentication is not enabled. " +
|
||||
"This is extremely dangerous! The whole world can see your your stash page and browse your files! " +
|
||||
"Stash is not answering any other requests to protect your privacy. " +
|
||||
"Please read the log entry or visit https://github.com/stashapp/stash/wiki/Authentication-Required-When-Accessing-Stash-From-the-Internet"
|
||||
)
|
||||
|
||||
func allowUnauthenticated(r *http.Request) bool {
|
||||
return strings.HasPrefix(r.URL.Path, loginEndPoint) || r.URL.Path == "/css"
|
||||
}
|
||||
|
||||
func authenticateHandler() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
c := config.GetInstance()
|
||||
|
||||
if !checkSecurityTripwireActivated(c, w) {
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := manager.GetInstance().SessionStore.Authenticate(w, r)
|
||||
if err != nil {
|
||||
if err != session.ErrUnauthorized {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, err = w.Write([]byte(err.Error()))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// unauthorized error
|
||||
w.Header().Add("WWW-Authenticate", `FormBased`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if err := session.CheckAllowPublicWithoutAuth(c, r); err != nil {
|
||||
switch err := err.(type) {
|
||||
case session.ExternalAccessError:
|
||||
securityActivateTripwireAccessedFromInternetWithoutAuth(c, err, w)
|
||||
return
|
||||
case session.UntrustedProxyError:
|
||||
logger.Warnf("Rejected request from untrusted proxy: %s", net.IP(err).String())
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
default:
|
||||
logger.Errorf("Error checking external access security: %s", err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
if c.HasCredentials() {
|
||||
// authentication is required
|
||||
if userID == "" && !allowUnauthenticated(r) {
|
||||
// authentication was not received, redirect
|
||||
// if graphql was requested, we just return a forbidden error
|
||||
if r.URL.Path == "/graphql" {
|
||||
w.Header().Add("WWW-Authenticate", `FormBased`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
prefix := getProxyPrefix(r.Header)
|
||||
|
||||
// otherwise redirect to the login page
|
||||
u := url.URL{
|
||||
Path: prefix + "/login",
|
||||
}
|
||||
q := u.Query()
|
||||
q.Set(returnURLParam, prefix+r.URL.Path)
|
||||
u.RawQuery = q.Encode()
|
||||
http.Redirect(w, r, u.String(), http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx = session.SetCurrentUserID(ctx, userID)
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkSecurityTripwireActivated(c *config.Instance, w http.ResponseWriter) bool {
|
||||
if accessErr := session.CheckExternalAccessTripwire(c); accessErr != nil {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, err := w.Write([]byte(tripwireActivatedErrMsg))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func securityActivateTripwireAccessedFromInternetWithoutAuth(c *config.Instance, accessErr session.ExternalAccessError, w http.ResponseWriter) {
|
||||
session.LogExternalAccessError(accessErr)
|
||||
|
||||
err := c.ActivatePublicAccessTripwire(net.IP(accessErr).String())
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, err = w.Write([]byte(externalAccessErrMsg))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
err = manager.GetInstance().Shutdown()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
150
pkg/api/changeset_translator.go
Normal file
150
pkg/api/changeset_translator.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strconv"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
const updateInputField = "input"
|
||||
|
||||
func getArgumentMap(ctx context.Context) map[string]interface{} {
|
||||
rctx := graphql.GetFieldContext(ctx)
|
||||
reqCtx := graphql.GetOperationContext(ctx)
|
||||
return rctx.Field.ArgumentMap(reqCtx.Variables)
|
||||
}
|
||||
|
||||
func getUpdateInputMap(ctx context.Context) map[string]interface{} {
|
||||
args := getArgumentMap(ctx)
|
||||
|
||||
input := args[updateInputField]
|
||||
var ret map[string]interface{}
|
||||
if input != nil {
|
||||
ret, _ = input.(map[string]interface{})
|
||||
}
|
||||
|
||||
if ret == nil {
|
||||
ret = make(map[string]interface{})
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getUpdateInputMaps(ctx context.Context) []map[string]interface{} {
|
||||
args := getArgumentMap(ctx)
|
||||
|
||||
input := args[updateInputField]
|
||||
var ret []map[string]interface{}
|
||||
if input != nil {
|
||||
// convert []interface{} into []map[string]interface{}
|
||||
iSlice, _ := input.([]interface{})
|
||||
for _, i := range iSlice {
|
||||
m, _ := i.(map[string]interface{})
|
||||
if m != nil {
|
||||
ret = append(ret, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type changesetTranslator struct {
|
||||
inputMap map[string]interface{}
|
||||
}
|
||||
|
||||
func (t changesetTranslator) hasField(field string) bool {
|
||||
if t.inputMap == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, found := t.inputMap[field]
|
||||
return found
|
||||
}
|
||||
|
||||
func (t changesetTranslator) getFields() []string {
|
||||
var ret []string
|
||||
for k := range t.inputMap {
|
||||
ret = append(ret, k)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t changesetTranslator) nullString(value *string, field string) *sql.NullString {
|
||||
if !t.hasField(field) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := &sql.NullString{}
|
||||
|
||||
if value != nil {
|
||||
ret.String = *value
|
||||
ret.Valid = true
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t changesetTranslator) sqliteDate(value *string, field string) *models.SQLiteDate {
|
||||
if !t.hasField(field) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := &models.SQLiteDate{}
|
||||
|
||||
if value != nil {
|
||||
ret.String = *value
|
||||
ret.Valid = true
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t changesetTranslator) nullInt64(value *int, field string) *sql.NullInt64 {
|
||||
if !t.hasField(field) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := &sql.NullInt64{}
|
||||
|
||||
if value != nil {
|
||||
ret.Int64 = int64(*value)
|
||||
ret.Valid = true
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t changesetTranslator) nullInt64FromString(value *string, field string) *sql.NullInt64 {
|
||||
if !t.hasField(field) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := &sql.NullInt64{}
|
||||
|
||||
if value != nil {
|
||||
ret.Int64, _ = strconv.ParseInt(*value, 10, 64)
|
||||
ret.Valid = true
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t changesetTranslator) nullBool(value *bool, field string) *sql.NullBool {
|
||||
if !t.hasField(field) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := &sql.NullBool{}
|
||||
|
||||
if value != nil {
|
||||
ret.Bool = *value
|
||||
ret.Valid = true
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
)
|
||||
|
||||
@@ -18,6 +20,7 @@ const apiReleases string = "https://api.github.com/repos/stashapp/stash/releases
|
||||
const apiTags string = "https://api.github.com/repos/stashapp/stash/tags"
|
||||
const apiAcceptHeader string = "application/vnd.github.v3+json"
|
||||
const developmentTag string = "latest_develop"
|
||||
const defaultSHLength int = 7 // default length of SHA short hash returned by <git rev-parse --short HEAD>
|
||||
|
||||
// ErrNoVersion indicates that no version information has been embedded in the
|
||||
// stash binary
|
||||
@@ -25,10 +28,13 @@ var ErrNoVersion = errors.New("no stash version")
|
||||
|
||||
var stashReleases = func() map[string]string {
|
||||
return map[string]string{
|
||||
"windows/amd64": "stash-win.exe",
|
||||
"linux/amd64": "stash-linux",
|
||||
"darwin/amd64": "stash-osx",
|
||||
"darwin/arm64": "stash-osx-applesilicon",
|
||||
"linux/amd64": "stash-linux",
|
||||
"windows/amd64": "stash-win.exe",
|
||||
"linux/arm": "stash-pi",
|
||||
"linux/arm64": "stash-linux-arm64v8",
|
||||
"linux/armv7": "stash-linux-arm32v7",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,23 +118,26 @@ func makeGithubRequest(url string, output interface{}) error {
|
||||
response, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
//lint:ignore ST1005 Github is a proper capitalized noun
|
||||
return fmt.Errorf("Github API request failed: %s", err)
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
//lint:ignore ST1005 Github is a proper capitalized noun
|
||||
return fmt.Errorf("Github API request failed: %s", response.Status)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(response.Body)
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
//lint:ignore ST1005 Github is a proper capitalized noun
|
||||
return fmt.Errorf("Github API read response failed: %s", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, output)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unmarshalling Github API response failed: %s", err)
|
||||
return fmt.Errorf("unmarshalling Github API response failed: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -140,7 +149,13 @@ func makeGithubRequest(url string, output interface{}) error {
|
||||
// which is the latest pre-release build.
|
||||
func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease string, err error) {
|
||||
|
||||
platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
arch := runtime.GOARCH // https://en.wikipedia.org/wiki/Comparison_of_ARM_cores
|
||||
isARMv7 := cpu.ARM.HasNEON || cpu.ARM.HasVFPv3 || cpu.ARM.HasVFPv3D16 || cpu.ARM.HasVFPv4 // armv6 doesn't support any of these features
|
||||
if arch == "arm" && isARMv7 {
|
||||
arch = "armv7"
|
||||
}
|
||||
|
||||
platform := fmt.Sprintf("%s/%s", runtime.GOOS, arch)
|
||||
wantedRelease := stashReleases()[platform]
|
||||
|
||||
version, _, _ := GetVersion()
|
||||
@@ -185,20 +200,26 @@ func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease strin
|
||||
}
|
||||
|
||||
if latestVersion == "" {
|
||||
return "", "", fmt.Errorf("No version found for \"%s\"", version)
|
||||
return "", "", fmt.Errorf("no version found for \"%s\"", version)
|
||||
}
|
||||
return latestVersion, latestRelease, nil
|
||||
}
|
||||
|
||||
func getReleaseHash(release githubReleasesResponse, shortHash bool, usePreRelease bool) string {
|
||||
shaLength := len(release.Target_commitish)
|
||||
// the /latest API call doesn't return the hash in target_commitish
|
||||
// also add sanity check in case Target_commitish is not 40 characters
|
||||
if !usePreRelease || len(release.Target_commitish) != 40 {
|
||||
if !usePreRelease || shaLength != 40 {
|
||||
return getShaFromTags(shortHash, release.Tag_name)
|
||||
}
|
||||
|
||||
if shortHash {
|
||||
return release.Target_commitish[0:7] //shorthash is first 7 digits of git commit hash
|
||||
last := defaultSHLength // default length of git short hash
|
||||
_, gitShort, _ := GetVersion() // retrieve it to check actual length
|
||||
if len(gitShort) > last && len(gitShort) < shaLength { // sometimes short hash is longer
|
||||
last = len(gitShort)
|
||||
}
|
||||
return release.Target_commitish[0:last]
|
||||
}
|
||||
|
||||
return release.Target_commitish
|
||||
@@ -229,14 +250,20 @@ func getShaFromTags(shortHash bool, name string) string {
|
||||
logger.Errorf("Github Tags Api %v", err)
|
||||
return ""
|
||||
}
|
||||
_, gitShort, _ := GetVersion() // retrieve short hash to check actual length
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.Name == name {
|
||||
if len(tag.Commit.Sha) != 40 {
|
||||
shaLength := len(tag.Commit.Sha)
|
||||
if shaLength != 40 {
|
||||
return ""
|
||||
}
|
||||
if shortHash {
|
||||
return tag.Commit.Sha[0:7] //shorthash is first 7 digits of git commit hash
|
||||
last := defaultSHLength // default length of git short hash
|
||||
if len(gitShort) > last && len(gitShort) < shaLength { // sometimes short hash is longer
|
||||
last = len(gitShort)
|
||||
}
|
||||
return tag.Commit.Sha[0:last]
|
||||
}
|
||||
|
||||
return tag.Commit.Sha
|
||||
|
||||
@@ -5,13 +5,12 @@ package api
|
||||
type key int
|
||||
|
||||
const (
|
||||
galleryKey key = 0
|
||||
performerKey key = 1
|
||||
sceneKey key = 2
|
||||
studioKey key = 3
|
||||
movieKey key = 4
|
||||
ContextUser key = 5
|
||||
tagKey key = 6
|
||||
downloadKey key = 7
|
||||
imageKey key = 8
|
||||
// galleryKey key = 0
|
||||
performerKey key = iota + 1
|
||||
sceneKey
|
||||
studioKey
|
||||
movieKey
|
||||
tagKey
|
||||
downloadKey
|
||||
imageKey
|
||||
)
|
||||
|
||||
@@ -1,49 +1,96 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/static"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
var performerBox *packr.Box
|
||||
var performerBoxMale *packr.Box
|
||||
type imageBox struct {
|
||||
box fs.FS
|
||||
files []string
|
||||
}
|
||||
|
||||
func newImageBox(box fs.FS) (*imageBox, error) {
|
||||
ret := &imageBox{
|
||||
box: box,
|
||||
}
|
||||
|
||||
err := fs.WalkDir(box, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if !d.IsDir() {
|
||||
ret.files = append(ret.files, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
var performerBox *imageBox
|
||||
var performerBoxMale *imageBox
|
||||
var performerBoxCustom *imageBox
|
||||
|
||||
func initialiseImages() {
|
||||
performerBox = packr.New("Performer Box", "../../static/performer")
|
||||
performerBoxMale = packr.New("Male Performer Box", "../../static/performer_male")
|
||||
var err error
|
||||
performerBox, err = newImageBox(&static.Performer)
|
||||
if err != nil {
|
||||
logger.Warnf("error loading performer images: %v", err)
|
||||
}
|
||||
performerBoxMale, err = newImageBox(&static.PerformerMale)
|
||||
if err != nil {
|
||||
logger.Warnf("error loading male performer images: %v", err)
|
||||
}
|
||||
initialiseCustomImages()
|
||||
}
|
||||
|
||||
func getRandomPerformerImage(gender string) ([]byte, error) {
|
||||
var box *packr.Box
|
||||
switch strings.ToUpper(gender) {
|
||||
case "FEMALE":
|
||||
box = performerBox
|
||||
case "MALE":
|
||||
box = performerBoxMale
|
||||
default:
|
||||
box = performerBox
|
||||
|
||||
func initialiseCustomImages() {
|
||||
customPath := config.GetInstance().GetCustomPerformerImageLocation()
|
||||
if customPath != "" {
|
||||
logger.Debugf("Loading custom performer images from %s", customPath)
|
||||
// We need to set performerBoxCustom at runtime, as this is a custom path, and store it in a pointer.
|
||||
var err error
|
||||
performerBoxCustom, err = newImageBox(os.DirFS(customPath))
|
||||
if err != nil {
|
||||
logger.Warnf("error loading custom performer from %s: %v", customPath, err)
|
||||
}
|
||||
} else {
|
||||
performerBoxCustom = nil
|
||||
}
|
||||
imageFiles := box.List()
|
||||
index := rand.Intn(len(imageFiles))
|
||||
return box.Find(imageFiles[index])
|
||||
}
|
||||
|
||||
func getRandomPerformerImageUsingName(name, gender string) ([]byte, error) {
|
||||
var box *packr.Box
|
||||
switch strings.ToUpper(gender) {
|
||||
case "FEMALE":
|
||||
box = performerBox
|
||||
case "MALE":
|
||||
box = performerBoxMale
|
||||
default:
|
||||
box = performerBox
|
||||
func getRandomPerformerImageUsingName(name, gender, customPath string) ([]byte, error) {
|
||||
var box *imageBox
|
||||
|
||||
// If we have a custom path, we should return a new box in the given path.
|
||||
if performerBoxCustom != nil && len(performerBoxCustom.files) > 0 {
|
||||
box = performerBoxCustom
|
||||
}
|
||||
imageFiles := box.List()
|
||||
|
||||
if box == nil {
|
||||
switch strings.ToUpper(gender) {
|
||||
case "FEMALE":
|
||||
box = performerBox
|
||||
case "MALE":
|
||||
box = performerBoxMale
|
||||
default:
|
||||
box = performerBox
|
||||
}
|
||||
}
|
||||
|
||||
imageFiles := box.files
|
||||
index := utils.IntFromString(name) % uint64(len(imageFiles))
|
||||
return box.Find(imageFiles[index])
|
||||
img, err := box.box.Open(imageFiles[index])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer img.Close()
|
||||
|
||||
return io.ReadAll(img)
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/stashapp/stash/pkg/database"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager"
|
||||
)
|
||||
|
||||
type migrateData struct {
|
||||
ExistingVersion uint
|
||||
MigrateVersion uint
|
||||
BackupPath string
|
||||
}
|
||||
|
||||
func getMigrateData() migrateData {
|
||||
return migrateData{
|
||||
ExistingVersion: database.Version(),
|
||||
MigrateVersion: database.AppSchemaVersion(),
|
||||
BackupPath: database.DatabaseBackupPath(),
|
||||
}
|
||||
}
|
||||
|
||||
func getMigrateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !database.NeedsMigration() {
|
||||
http.Redirect(w, r, "/", 301)
|
||||
return
|
||||
}
|
||||
|
||||
data, _ := setupUIBox.Find("migrate.html")
|
||||
templ, err := template.New("Migrate").Parse(string(data))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("error: %s", err), 500)
|
||||
return
|
||||
}
|
||||
|
||||
err = templ.Execute(w, getMigrateData())
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("error: %s", err), 500)
|
||||
}
|
||||
}
|
||||
|
||||
func doMigrateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("error: %s", err), 500)
|
||||
}
|
||||
|
||||
formBackupPath := r.Form.Get("backuppath")
|
||||
|
||||
// always backup so that we can roll back to the previous version if
|
||||
// migration fails
|
||||
backupPath := formBackupPath
|
||||
if formBackupPath == "" {
|
||||
backupPath = database.DatabaseBackupPath()
|
||||
}
|
||||
|
||||
// perform database backup
|
||||
if err = database.Backup(backupPath); err != nil {
|
||||
http.Error(w, fmt.Sprintf("error backing up database: %s", err), 500)
|
||||
return
|
||||
}
|
||||
|
||||
err = database.RunMigrations()
|
||||
if err != nil {
|
||||
errStr := fmt.Sprintf("error performing migration: %s", err)
|
||||
|
||||
// roll back to the backed up version
|
||||
restoreErr := database.RestoreFromBackup(backupPath)
|
||||
if restoreErr != nil {
|
||||
errStr = fmt.Sprintf("ERROR: unable to restore database from backup after migration failure: %s\n%s", restoreErr.Error(), errStr)
|
||||
} else {
|
||||
errStr = "An error occurred migrating the database to the latest schema version. The backup database file was automatically renamed to restore the database.\n" + errStr
|
||||
}
|
||||
|
||||
http.Error(w, errStr, 500)
|
||||
return
|
||||
}
|
||||
|
||||
// perform post-migration operations
|
||||
manager.GetInstance().PostMigrate()
|
||||
|
||||
// if no backup path was provided, then delete the created backup
|
||||
if formBackupPath == "" {
|
||||
err = os.Remove(backupPath)
|
||||
if err != nil {
|
||||
logger.Warnf("error removing unwanted database backup (%s): %s", backupPath, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", 301)
|
||||
}
|
||||
@@ -5,12 +5,19 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/plugin"
|
||||
)
|
||||
|
||||
type Resolver struct{}
|
||||
type hookExecutor interface {
|
||||
ExecutePostHooks(ctx context.Context, id int, hookType plugin.HookTriggerEnum, input interface{}, inputFields []string)
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
txnManager models.TransactionManager
|
||||
hookExecutor hookExecutor
|
||||
}
|
||||
|
||||
func (r *Resolver) Gallery() models.GalleryResolver {
|
||||
return &galleryResolver{r}
|
||||
@@ -46,22 +53,6 @@ func (r *Resolver) Tag() models.TagResolver {
|
||||
return &tagResolver{r}
|
||||
}
|
||||
|
||||
func (r *Resolver) ScrapedSceneTag() models.ScrapedSceneTagResolver {
|
||||
return &scrapedSceneTagResolver{r}
|
||||
}
|
||||
|
||||
func (r *Resolver) ScrapedSceneMovie() models.ScrapedSceneMovieResolver {
|
||||
return &scrapedSceneMovieResolver{r}
|
||||
}
|
||||
|
||||
func (r *Resolver) ScrapedScenePerformer() models.ScrapedScenePerformerResolver {
|
||||
return &scrapedScenePerformerResolver{r}
|
||||
}
|
||||
|
||||
func (r *Resolver) ScrapedSceneStudio() models.ScrapedSceneStudioResolver {
|
||||
return &scrapedSceneStudioResolver{r}
|
||||
}
|
||||
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
type subscriptionResolver struct{ *Resolver }
|
||||
@@ -74,74 +65,87 @@ type imageResolver struct{ *Resolver }
|
||||
type studioResolver struct{ *Resolver }
|
||||
type movieResolver struct{ *Resolver }
|
||||
type tagResolver struct{ *Resolver }
|
||||
type scrapedSceneTagResolver struct{ *Resolver }
|
||||
type scrapedSceneMovieResolver struct{ *Resolver }
|
||||
type scrapedScenePerformerResolver struct{ *Resolver }
|
||||
type scrapedSceneStudioResolver struct{ *Resolver }
|
||||
|
||||
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) ([]*models.SceneMarker, error) {
|
||||
qb := models.NewSceneMarkerQueryBuilder()
|
||||
return qb.Wall(q)
|
||||
func (r *Resolver) withTxn(ctx context.Context, fn func(r models.Repository) error) error {
|
||||
return r.txnManager.WithTxn(ctx, fn)
|
||||
}
|
||||
|
||||
func (r *queryResolver) SceneWall(ctx context.Context, q *string) ([]*models.Scene, error) {
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
return qb.Wall(q)
|
||||
func (r *Resolver) withReadTxn(ctx context.Context, fn func(r models.ReaderRepository) error) error {
|
||||
return r.txnManager.WithReadTxn(ctx, fn)
|
||||
}
|
||||
|
||||
func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *string) ([]*models.MarkerStringsResultType, error) {
|
||||
qb := models.NewSceneMarkerQueryBuilder()
|
||||
return qb.GetMarkerStrings(q, sort)
|
||||
}
|
||||
|
||||
func (r *queryResolver) ValidGalleriesForScene(ctx context.Context, scene_id *string) ([]*models.Gallery, error) {
|
||||
if scene_id == nil {
|
||||
panic("nil scene id") // TODO make scene_id mandatory
|
||||
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*models.SceneMarker, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.SceneMarker().Wall(q)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sceneID, _ := strconv.Atoi(*scene_id)
|
||||
sqb := models.NewSceneQueryBuilder()
|
||||
scene, err := sqb.Find(sceneID)
|
||||
if err != nil {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) SceneWall(ctx context.Context, q *string) (ret []*models.Scene, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Scene().Wall(q)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qb := models.NewGalleryQueryBuilder()
|
||||
validGalleries, err := qb.ValidGalleriesForScenePath(scene.Path)
|
||||
sceneGallery, _ := qb.FindBySceneID(sceneID, nil)
|
||||
if sceneGallery != nil {
|
||||
validGalleries = append(validGalleries, sceneGallery)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *string) (ret []*models.MarkerStringsResultType, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.SceneMarker().GetMarkerStrings(q, sort)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return validGalleries, nil
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, error) {
|
||||
scenesQB := models.NewSceneQueryBuilder()
|
||||
scenesCount, _ := scenesQB.Count()
|
||||
scenesSize, _ := scenesQB.Size()
|
||||
imageQB := models.NewImageQueryBuilder()
|
||||
imageCount, _ := imageQB.Count()
|
||||
imageSize, _ := imageQB.Size()
|
||||
galleryQB := models.NewGalleryQueryBuilder()
|
||||
galleryCount, _ := galleryQB.Count()
|
||||
performersQB := models.NewPerformerQueryBuilder()
|
||||
performersCount, _ := performersQB.Count()
|
||||
studiosQB := models.NewStudioQueryBuilder()
|
||||
studiosCount, _ := studiosQB.Count()
|
||||
moviesQB := models.NewMovieQueryBuilder()
|
||||
moviesCount, _ := moviesQB.Count()
|
||||
tagsQB := models.NewTagQueryBuilder()
|
||||
tagsCount, _ := tagsQB.Count()
|
||||
return &models.StatsResultType{
|
||||
SceneCount: scenesCount,
|
||||
ScenesSize: int(scenesSize),
|
||||
ImageCount: imageCount,
|
||||
ImagesSize: int(imageSize),
|
||||
GalleryCount: galleryCount,
|
||||
PerformerCount: performersCount,
|
||||
StudioCount: studiosCount,
|
||||
MovieCount: moviesCount,
|
||||
TagCount: tagsCount,
|
||||
}, nil
|
||||
var ret models.StatsResultType
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
scenesQB := repo.Scene()
|
||||
imageQB := repo.Image()
|
||||
galleryQB := repo.Gallery()
|
||||
studiosQB := repo.Studio()
|
||||
performersQB := repo.Performer()
|
||||
moviesQB := repo.Movie()
|
||||
tagsQB := repo.Tag()
|
||||
scenesCount, _ := scenesQB.Count()
|
||||
scenesSize, _ := scenesQB.Size()
|
||||
scenesDuration, _ := scenesQB.Duration()
|
||||
imageCount, _ := imageQB.Count()
|
||||
imageSize, _ := imageQB.Size()
|
||||
galleryCount, _ := galleryQB.Count()
|
||||
performersCount, _ := performersQB.Count()
|
||||
studiosCount, _ := studiosQB.Count()
|
||||
moviesCount, _ := moviesQB.Count()
|
||||
tagsCount, _ := tagsQB.Count()
|
||||
|
||||
ret = models.StatsResultType{
|
||||
SceneCount: scenesCount,
|
||||
ScenesSize: scenesSize,
|
||||
ScenesDuration: scenesDuration,
|
||||
ImageCount: imageCount,
|
||||
ImagesSize: imageSize,
|
||||
GalleryCount: galleryCount,
|
||||
PerformerCount: performersCount,
|
||||
StudioCount: studiosCount,
|
||||
MovieCount: moviesCount,
|
||||
TagCount: tagsCount,
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Version(ctx context.Context) (*models.Version, error) {
|
||||
@@ -171,31 +175,38 @@ func (r *queryResolver) Latestversion(ctx context.Context) (*models.ShortVersion
|
||||
|
||||
// Get scene marker tags which show up under the video.
|
||||
func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([]*models.SceneMarkerTag, error) {
|
||||
sceneID, _ := strconv.Atoi(scene_id)
|
||||
sqb := models.NewSceneMarkerQueryBuilder()
|
||||
sceneMarkers, err := sqb.FindBySceneID(sceneID, nil)
|
||||
sceneID, err := strconv.Atoi(scene_id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := make(map[int]*models.SceneMarkerTag)
|
||||
var keys []int
|
||||
tqb := models.NewTagQueryBuilder()
|
||||
for _, sceneMarker := range sceneMarkers {
|
||||
markerPrimaryTag, err := tqb.Find(sceneMarker.PrimaryTagID, nil)
|
||||
tags := make(map[int]*models.SceneMarkerTag)
|
||||
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
sceneMarkers, err := repo.SceneMarker().FindBySceneID(sceneID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
_, hasKey := tags[markerPrimaryTag.ID]
|
||||
var sceneMarkerTag *models.SceneMarkerTag
|
||||
if !hasKey {
|
||||
sceneMarkerTag = &models.SceneMarkerTag{Tag: markerPrimaryTag}
|
||||
tags[markerPrimaryTag.ID] = sceneMarkerTag
|
||||
keys = append(keys, markerPrimaryTag.ID)
|
||||
} else {
|
||||
sceneMarkerTag = tags[markerPrimaryTag.ID]
|
||||
|
||||
tqb := repo.Tag()
|
||||
for _, sceneMarker := range sceneMarkers {
|
||||
markerPrimaryTag, err := tqb.Find(sceneMarker.PrimaryTagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, hasKey := tags[markerPrimaryTag.ID]
|
||||
if !hasKey {
|
||||
sceneMarkerTag := &models.SceneMarkerTag{Tag: markerPrimaryTag}
|
||||
tags[markerPrimaryTag.ID] = sceneMarkerTag
|
||||
keys = append(keys, markerPrimaryTag.ID)
|
||||
}
|
||||
tags[markerPrimaryTag.ID].SceneMarkers = append(tags[markerPrimaryTag.ID].SceneMarkers, sceneMarker)
|
||||
}
|
||||
tags[markerPrimaryTag.ID].SceneMarkers = append(tags[markerPrimaryTag.ID].SceneMarkers, sceneMarker)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sort so that primary tags that show up earlier in the video are first.
|
||||
@@ -212,13 +223,3 @@ func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// wasFieldIncluded returns true if the given field was included in the request.
|
||||
// Slices are unmarshalled to empty slices even if the field was omitted. This
|
||||
// method determines if it was omitted altogether.
|
||||
func wasFieldIncluded(ctx context.Context, field string) bool {
|
||||
rctx := graphql.GetRequestContext(ctx)
|
||||
|
||||
_, ret := rctx.Variables[field]
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/image"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
@@ -22,30 +23,39 @@ func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*stri
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) ([]*models.Image, error) {
|
||||
qb := models.NewImageQueryBuilder()
|
||||
|
||||
return qb.FindByGalleryID(obj.ID)
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Cover(ctx context.Context, obj *models.Gallery) (*models.Image, error) {
|
||||
qb := models.NewImageQueryBuilder()
|
||||
|
||||
imgs, err := qb.FindByGalleryID(obj.ID)
|
||||
if err != nil {
|
||||
func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) (ret []*models.Image, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
ret, err = repo.Image().FindByGalleryID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret *models.Image
|
||||
if len(imgs) > 0 {
|
||||
ret = imgs[0]
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
for _, img := range imgs {
|
||||
if image.IsCover(img) {
|
||||
ret = img
|
||||
break
|
||||
func (r *galleryResolver) Cover(ctx context.Context, obj *models.Gallery) (ret *models.Image, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
imgs, err := repo.Image().FindByGalleryID(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
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
@@ -81,35 +91,78 @@ func (r *galleryResolver) Rating(ctx context.Context, obj *models.Gallery) (*int
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Scene(ctx context.Context, obj *models.Gallery) (*models.Scene, error) {
|
||||
if !obj.SceneID.Valid {
|
||||
return nil, nil
|
||||
func (r *galleryResolver) Scenes(ctx context.Context, obj *models.Gallery) (ret []*models.Scene, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
ret, err = repo.Scene().FindByGalleryID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
return qb.Find(int(obj.SceneID.Int64))
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Studio(ctx context.Context, obj *models.Gallery) (*models.Studio, error) {
|
||||
func (r *galleryResolver) Studio(ctx context.Context, obj *models.Gallery) (ret *models.Studio, err error) {
|
||||
if !obj.StudioID.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
qb := models.NewStudioQueryBuilder()
|
||||
return qb.Find(int(obj.StudioID.Int64), nil)
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Tags(ctx context.Context, obj *models.Gallery) ([]*models.Tag, error) {
|
||||
qb := models.NewTagQueryBuilder()
|
||||
return qb.FindByGalleryID(obj.ID, nil)
|
||||
func (r *galleryResolver) Tags(ctx context.Context, obj *models.Gallery) (ret []*models.Tag, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
ret, err = repo.Tag().FindByGalleryID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Performers(ctx context.Context, obj *models.Gallery) ([]*models.Performer, error) {
|
||||
qb := models.NewPerformerQueryBuilder()
|
||||
return qb.FindByGalleryID(obj.ID, nil)
|
||||
func (r *galleryResolver) Performers(ctx context.Context, obj *models.Gallery) (ret []*models.Performer, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
ret, err = repo.Performer().FindByGalleryID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) ImageCount(ctx context.Context, obj *models.Gallery) (int, error) {
|
||||
qb := models.NewImageQueryBuilder()
|
||||
return qb.CountByGalleryID(obj.ID)
|
||||
func (r *galleryResolver) ImageCount(ctx context.Context, obj *models.Gallery) (ret int, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
ret, err = repo.Image().CountByGalleryID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) CreatedAt(ctx context.Context, obj *models.Gallery) (*time.Time, error) {
|
||||
return &obj.CreatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) UpdatedAt(ctx context.Context, obj *models.Gallery) (*time.Time, error) {
|
||||
return &obj.UpdatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) FileModTime(ctx context.Context, obj *models.Gallery) (*time.Time, error) {
|
||||
return &obj.FileModTime.Timestamp, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/image"
|
||||
@@ -34,7 +35,7 @@ func (r *imageResolver) File(ctx context.Context, obj *models.Image) (*models.Im
|
||||
|
||||
func (r *imageResolver) Paths(ctx context.Context, obj *models.Image) (*models.ImagePathsType, error) {
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
builder := urlbuilders.NewImageURLBuilder(baseURL, obj.ID)
|
||||
builder := urlbuilders.NewImageURLBuilder(baseURL, obj)
|
||||
thumbnailPath := builder.GetThumbnailURL()
|
||||
imagePath := builder.GetImageURL()
|
||||
return &models.ImagePathsType{
|
||||
@@ -43,26 +44,63 @@ func (r *imageResolver) Paths(ctx context.Context, obj *models.Image) (*models.I
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) Galleries(ctx context.Context, obj *models.Image) ([]*models.Gallery, error) {
|
||||
qb := models.NewGalleryQueryBuilder()
|
||||
return qb.FindByImageID(obj.ID, nil)
|
||||
func (r *imageResolver) Galleries(ctx context.Context, obj *models.Image) (ret []*models.Gallery, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
ret, err = repo.Gallery().FindByImageID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) Studio(ctx context.Context, obj *models.Image) (*models.Studio, error) {
|
||||
func (r *imageResolver) Studio(ctx context.Context, obj *models.Image) (ret *models.Studio, err error) {
|
||||
if !obj.StudioID.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
qb := models.NewStudioQueryBuilder()
|
||||
return qb.Find(int(obj.StudioID.Int64), nil)
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) Tags(ctx context.Context, obj *models.Image) ([]*models.Tag, error) {
|
||||
qb := models.NewTagQueryBuilder()
|
||||
return qb.FindByImageID(obj.ID, nil)
|
||||
func (r *imageResolver) Tags(ctx context.Context, obj *models.Image) (ret []*models.Tag, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Tag().FindByImageID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) Performers(ctx context.Context, obj *models.Image) ([]*models.Performer, error) {
|
||||
qb := models.NewPerformerQueryBuilder()
|
||||
return qb.FindByImageID(obj.ID, nil)
|
||||
func (r *imageResolver) Performers(ctx context.Context, obj *models.Image) (ret []*models.Performer, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Performer().FindByImageID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) CreatedAt(ctx context.Context, obj *models.Image) (*time.Time, error) {
|
||||
return &obj.CreatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) UpdatedAt(ctx context.Context, obj *models.Image) (*time.Time, error) {
|
||||
return &obj.UpdatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *imageResolver) FileModTime(ctx context.Context, obj *models.Image) (*time.Time, error) {
|
||||
return &obj.FileModTime.Timestamp, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
@@ -53,10 +54,16 @@ func (r *movieResolver) Rating(ctx context.Context, obj *models.Movie) (*int, er
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) Studio(ctx context.Context, obj *models.Movie) (*models.Studio, error) {
|
||||
qb := models.NewStudioQueryBuilder()
|
||||
func (r *movieResolver) Studio(ctx context.Context, obj *models.Movie) (ret *models.Studio, err error) {
|
||||
if obj.StudioID.Valid {
|
||||
return qb.Find(int(obj.StudioID.Int64), nil)
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
@@ -78,18 +85,62 @@ func (r *movieResolver) Synopsis(ctx context.Context, obj *models.Movie) (*strin
|
||||
|
||||
func (r *movieResolver) FrontImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
frontimagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj.ID).GetMovieFrontImageURL()
|
||||
frontimagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj).GetMovieFrontImageURL()
|
||||
return &frontimagePath, nil
|
||||
}
|
||||
|
||||
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.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
img, err = repo.Movie().GetBackImage(obj.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if img == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
backimagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj.ID).GetMovieBackImageURL()
|
||||
backimagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj).GetMovieBackImageURL()
|
||||
return &backimagePath, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (*int, error) {
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
res, err := qb.CountByMovieID(obj.ID)
|
||||
func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
res, err = repo.Scene().CountByMovieID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, err
|
||||
}
|
||||
|
||||
func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*models.Scene, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
var err error
|
||||
ret, err = repo.Scene().FindByMovieID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) CreatedAt(ctx context.Context, obj *models.Movie) (*time.Time, error) {
|
||||
return &obj.CreatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *movieResolver) UpdatedAt(ctx context.Context, obj *models.Movie) (*time.Time, error) {
|
||||
return &obj.UpdatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/gallery"
|
||||
"github.com/stashapp/stash/pkg/image"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
@@ -134,22 +137,143 @@ func (r *performerResolver) Favorite(ctx context.Context, obj *models.Performer)
|
||||
|
||||
func (r *performerResolver) ImagePath(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
imagePath := urlbuilders.NewPerformerURLBuilder(baseURL, obj.ID).GetPerformerImageURL()
|
||||
imagePath := urlbuilders.NewPerformerURLBuilder(baseURL, obj).GetPerformerImageURL()
|
||||
return &imagePath, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (*int, error) {
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
res, err := qb.CountByPerformerID(obj.ID)
|
||||
return &res, err
|
||||
func (r *performerResolver) Tags(ctx context.Context, obj *models.Performer) (ret []*models.Tag, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Tag().FindByPerformerID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) ([]*models.Scene, error) {
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
return qb.FindByPerformerID(obj.ID)
|
||||
func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
res, err = repo.Scene().CountByPerformerID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer) ([]*models.StashID, error) {
|
||||
qb := models.NewJoinsQueryBuilder()
|
||||
return qb.GetPerformerStashIDs(obj.ID)
|
||||
func (r *performerResolver) ImageCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
res, err = image.CountByPerformerID(repo.Image(), obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
res, err = gallery.CountByPerformerID(repo.Gallery(), obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) (ret []*models.Scene, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Scene().FindByPerformerID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer) (ret []*models.StashID, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Performer().GetStashIDs(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) Rating(ctx context.Context, obj *models.Performer) (*int, error) {
|
||||
if obj.Rating.Valid {
|
||||
rating := int(obj.Rating.Int64)
|
||||
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) DeathDate(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||
if obj.DeathDate.Valid {
|
||||
return &obj.DeathDate.String, 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.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Movie().FindByPerformerID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
|
||||
var res int
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
res, err = repo.Movie().CountByPerformerID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
@@ -65,12 +67,12 @@ func (r *sceneResolver) File(ctx context.Context, obj *models.Scene) (*models.Sc
|
||||
bitrate := int(obj.Bitrate.Int64)
|
||||
return &models.SceneFileType{
|
||||
Size: &obj.Size.String,
|
||||
Duration: &obj.Duration.Float64,
|
||||
Duration: handleFloat64(obj.Duration.Float64),
|
||||
VideoCodec: &obj.VideoCodec.String,
|
||||
AudioCodec: &obj.AudioCodec.String,
|
||||
Width: &width,
|
||||
Height: &height,
|
||||
Framerate: &obj.Framerate.Float64,
|
||||
Framerate: handleFloat64(obj.Framerate.Float64),
|
||||
Bitrate: &bitrate,
|
||||
}, nil
|
||||
}
|
||||
@@ -78,12 +80,16 @@ func (r *sceneResolver) File(ctx context.Context, obj *models.Scene) (*models.Sc
|
||||
func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*models.ScenePathsType, error) {
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
builder := urlbuilders.NewSceneURLBuilder(baseURL, obj.ID)
|
||||
builder.APIKey = config.GetInstance().GetAPIKey()
|
||||
screenshotPath := builder.GetScreenshotURL(obj.UpdatedAt.Timestamp)
|
||||
previewPath := builder.GetStreamPreviewURL()
|
||||
streamPath := builder.GetStreamURL()
|
||||
webpPath := builder.GetStreamPreviewImageURL()
|
||||
vttPath := builder.GetSpriteVTTURL()
|
||||
spritePath := builder.GetSpriteURL()
|
||||
chaptersVttPath := builder.GetChaptersVTTURL()
|
||||
funscriptPath := builder.GetFunscriptURL()
|
||||
|
||||
return &models.ScenePathsType{
|
||||
Screenshot: &screenshotPath,
|
||||
Preview: &previewPath,
|
||||
@@ -91,67 +97,133 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*models.S
|
||||
Webp: &webpPath,
|
||||
Vtt: &vttPath,
|
||||
ChaptersVtt: &chaptersVttPath,
|
||||
Sprite: &spritePath,
|
||||
Funscript: &funscriptPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) ([]*models.SceneMarker, error) {
|
||||
qb := models.NewSceneMarkerQueryBuilder()
|
||||
return qb.FindBySceneID(obj.ID, nil)
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Gallery(ctx context.Context, obj *models.Scene) (*models.Gallery, error) {
|
||||
qb := models.NewGalleryQueryBuilder()
|
||||
return qb.FindBySceneID(obj.ID, nil)
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (*models.Studio, error) {
|
||||
qb := models.NewStudioQueryBuilder()
|
||||
return qb.FindBySceneID(obj.ID)
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) ([]*models.SceneMovie, error) {
|
||||
joinQB := models.NewJoinsQueryBuilder()
|
||||
qb := models.NewMovieQueryBuilder()
|
||||
|
||||
sceneMovies, err := joinQB.GetSceneMovies(obj.ID, nil)
|
||||
if err != nil {
|
||||
func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) (ret []*models.SceneMarker, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.SceneMarker().FindBySceneID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret []*models.SceneMovie
|
||||
for _, sm := range sceneMovies {
|
||||
movie, err := qb.Find(sm.MovieID, nil)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Galleries(ctx context.Context, obj *models.Scene) (ret []*models.Gallery, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Gallery().FindBySceneID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (ret *models.Studio, err error) {
|
||||
if !obj.StudioID.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*models.SceneMovie, err error) {
|
||||
if err := r.withTxn(ctx, func(repo models.Repository) error {
|
||||
qb := repo.Scene()
|
||||
mqb := repo.Movie()
|
||||
|
||||
sceneMovies, err := qb.GetMovies(obj.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
sceneIdx := sm.SceneIndex
|
||||
sceneMovie := &models.SceneMovie{
|
||||
Movie: movie,
|
||||
for _, sm := range sceneMovies {
|
||||
movie, err := mqb.Find(sm.MovieID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sceneIdx := sm.SceneIndex
|
||||
sceneMovie := &models.SceneMovie{
|
||||
Movie: movie,
|
||||
}
|
||||
|
||||
if sceneIdx.Valid {
|
||||
idx := int(sceneIdx.Int64)
|
||||
sceneMovie.SceneIndex = &idx
|
||||
}
|
||||
|
||||
ret = append(ret, sceneMovie)
|
||||
}
|
||||
|
||||
if sceneIdx.Valid {
|
||||
var idx int
|
||||
idx = int(sceneIdx.Int64)
|
||||
sceneMovie.SceneIndex = &idx
|
||||
}
|
||||
|
||||
ret = append(ret, sceneMovie)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) ([]*models.Tag, error) {
|
||||
qb := models.NewTagQueryBuilder()
|
||||
return qb.FindBySceneID(obj.ID, nil)
|
||||
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) (ret []*models.Tag, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Tag().FindBySceneID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Performers(ctx context.Context, obj *models.Scene) ([]*models.Performer, error) {
|
||||
qb := models.NewPerformerQueryBuilder()
|
||||
return qb.FindBySceneID(obj.ID, nil)
|
||||
func (r *sceneResolver) Performers(ctx context.Context, obj *models.Scene) (ret []*models.Performer, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Performer().FindBySceneID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) StashIds(ctx context.Context, obj *models.Scene) ([]*models.StashID, error) {
|
||||
qb := models.NewJoinsQueryBuilder()
|
||||
return qb.GetSceneStashIDs(obj.ID)
|
||||
func (r *sceneResolver) StashIds(ctx context.Context, obj *models.Scene) (ret []*models.StashID, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Scene().GetStashIDs(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) Phash(ctx context.Context, obj *models.Scene) (*string, error) {
|
||||
if obj.Phash.Valid {
|
||||
hexval := utils.PhashToString(obj.Phash.Int64)
|
||||
return &hexval, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) CreatedAt(ctx context.Context, obj *models.Scene) (*time.Time, error) {
|
||||
return &obj.CreatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) UpdatedAt(ctx context.Context, obj *models.Scene) (*time.Time, error) {
|
||||
return &obj.UpdatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *sceneResolver) FileModTime(ctx context.Context, obj *models.Scene) (*time.Time, error) {
|
||||
return &obj.FileModTime.Timestamp, nil
|
||||
}
|
||||
|
||||
@@ -2,29 +2,48 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
func (r *sceneMarkerResolver) Scene(ctx context.Context, obj *models.SceneMarker) (*models.Scene, error) {
|
||||
func (r *sceneMarkerResolver) Scene(ctx context.Context, obj *models.SceneMarker) (ret *models.Scene, err error) {
|
||||
if !obj.SceneID.Valid {
|
||||
panic("Invalid scene id")
|
||||
}
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
sceneID := int(obj.SceneID.Int64)
|
||||
scene, err := qb.Find(sceneID)
|
||||
return scene, err
|
||||
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
sceneID := int(obj.SceneID.Int64)
|
||||
ret, err = repo.Scene().Find(sceneID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *sceneMarkerResolver) PrimaryTag(ctx context.Context, obj *models.SceneMarker) (*models.Tag, error) {
|
||||
qb := models.NewTagQueryBuilder()
|
||||
tag, err := qb.Find(obj.PrimaryTagID, nil)
|
||||
return tag, err
|
||||
func (r *sceneMarkerResolver) PrimaryTag(ctx context.Context, obj *models.SceneMarker) (ret *models.Tag, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Tag().Find(obj.PrimaryTagID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (r *sceneMarkerResolver) Tags(ctx context.Context, obj *models.SceneMarker) ([]*models.Tag, error) {
|
||||
qb := models.NewTagQueryBuilder()
|
||||
return qb.FindBySceneMarkerID(obj.ID, nil)
|
||||
func (r *sceneMarkerResolver) Tags(ctx context.Context, obj *models.SceneMarker) (ret []*models.Tag, err error) {
|
||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||
ret, err = repo.Tag().FindBySceneMarkerID(obj.ID)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (r *sceneMarkerResolver) Stream(ctx context.Context, obj *models.SceneMarker) (string, error) {
|
||||
@@ -38,3 +57,17 @@ func (r *sceneMarkerResolver) Preview(ctx context.Context, obj *models.SceneMark
|
||||
sceneID := int(obj.SceneID.Int64)
|
||||
return urlbuilders.NewSceneURLBuilder(baseURL, sceneID).GetSceneMarkerStreamPreviewURL(obj.ID), nil
|
||||
}
|
||||
|
||||
func (r *sceneMarkerResolver) Screenshot(ctx context.Context, obj *models.SceneMarker) (string, error) {
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
sceneID := int(obj.SceneID.Int64)
|
||||
return urlbuilders.NewSceneURLBuilder(baseURL, sceneID).GetSceneMarkerStreamScreenshotURL(obj.ID), nil
|
||||
}
|
||||
|
||||
func (r *sceneMarkerResolver) CreatedAt(ctx context.Context, obj *models.SceneMarker) (*time.Time, error) {
|
||||
return &obj.CreatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
func (r *sceneMarkerResolver) UpdatedAt(ctx context.Context, obj *models.SceneMarker) (*time.Time, error) {
|
||||
return &obj.UpdatedAt.Timestamp, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user