mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Compare commits
529 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
061d21dede | ||
|
|
88a149c085 | ||
|
|
d994df2900 | ||
|
|
39fd8a6550 | ||
|
|
877491e62b | ||
|
|
3d044896ad | ||
|
|
63e8830db4 | ||
|
|
0bc4faef2a | ||
|
|
ee61fc879b | ||
|
|
e02ef436a5 | ||
|
|
41f0612025 | ||
|
|
730e877e73 | ||
|
|
e213fde0cc | ||
|
|
69fd073d5d | ||
|
|
5f16547e58 | ||
|
|
90dd0b58d8 | ||
|
|
4017c42fe2 | ||
|
|
49fd47562e | ||
|
|
84e24eb612 | ||
|
|
c6ae43c1d6 | ||
|
|
de8139cf1b | ||
|
|
0ca416f75a | ||
|
|
1bc32a3099 | ||
|
|
d1ee64d36f | ||
|
|
e052a431d1 | ||
|
|
7e66ce8a49 | ||
|
|
88747b962a | ||
|
|
97c01c70b3 | ||
|
|
a3ed381901 | ||
|
|
b3da730a05 | ||
|
|
e0c1d4c51d | ||
|
|
90d1b2df2d | ||
|
|
4ef3a605dd | ||
|
|
f811590021 | ||
|
|
0bd78f4b62 | ||
|
|
a8bb9ae4d3 | ||
|
|
d10995302d | ||
|
|
d14053b570 | ||
|
|
ca357b9eb3 | ||
|
|
6892c7151c | ||
|
|
d6a2953371 | ||
|
|
50ad3c0778 | ||
|
|
dc520e2b2f | ||
|
|
ecd9c6ec5b | ||
|
|
ca8ee6bc2a | ||
|
|
5d02f916c2 | ||
|
|
e176cf5f71 | ||
|
|
2cac7d5b20 | ||
|
|
58b6833380 | ||
|
|
68ebeda5c8 | ||
|
|
2332401dbf | ||
|
|
33b59e02af | ||
|
|
367b96df0f | ||
|
|
a31df336f8 | ||
|
|
78aeb06f20 | ||
|
|
2f65a1da3e | ||
|
|
51999135be | ||
|
|
bb56b619f5 | ||
|
|
a590caa3d3 | ||
|
|
0a05a0b45b | ||
|
|
9ef2169055 | ||
|
|
1ec8d4afe5 | ||
|
|
15db2da361 | ||
|
|
892858a803 | ||
|
|
bc91ca0a25 | ||
|
|
d743787bb3 | ||
|
|
957c4fe1b5 | ||
|
|
e3b3fbbf63 | ||
|
|
c99825a453 | ||
|
|
a08d2e258a | ||
|
|
b2c8f09585 | ||
|
|
5e34df7b7b | ||
|
|
678b3de7c8 | ||
|
|
f434c1f529 | ||
|
|
12a9a0b5f6 | ||
|
|
34becdf436 | ||
|
|
d5b1046267 | ||
|
|
2e766952dd | ||
|
|
1cc983fb5b | ||
|
|
a76e515112 | ||
|
|
1a9a62eae9 | ||
|
|
638ebfc319 | ||
|
|
53655e51c4 | ||
|
|
289b698598 | ||
|
|
b4d148bdb0 | ||
|
|
600cb15102 | ||
|
|
d52b6afd4a | ||
|
|
96a7e087f2 | ||
|
|
20fa5d3146 | ||
|
|
095e5d50ab | ||
|
|
42f76ca34f | ||
|
|
a50a0d4289 | ||
|
|
04fcf6f512 | ||
|
|
7716c4dd87 | ||
|
|
2925325e68 | ||
|
|
d831e4573c | ||
|
|
1b864f28f6 | ||
|
|
8c4b607454 | ||
|
|
2a2a730296 | ||
|
|
beee37bc38 | ||
|
|
9be0cc3210 | ||
|
|
f2a787a2ba | ||
|
|
6cace4ff88 | ||
|
|
fa2fd31ac7 | ||
|
|
1b2b4c5221 | ||
|
|
336fa3b70e | ||
|
|
299e1ac1f9 | ||
|
|
fb7bd89834 | ||
|
|
f04be76224 | ||
|
|
db79cf9bb1 | ||
|
|
90baa31ee3 | ||
|
|
9b8300e882 | ||
|
|
d70ff551d4 | ||
|
|
1dccecc39c | ||
|
|
648875995c | ||
|
|
96b5a9448c | ||
|
|
fda97e7f6c | ||
|
|
869cbd496b | ||
|
|
5049d6e5c9 | ||
|
|
98df51755e | ||
|
|
947a17355c | ||
|
|
71e4071871 | ||
|
|
a6778d7d22 | ||
|
|
415e88808f | ||
|
|
d0283fe330 | ||
|
|
c162c3843d | ||
|
|
cb6c53deb5 | ||
|
|
97ca5a28d3 | ||
|
|
cee68ab87b | ||
|
|
c6bf20dd77 | ||
|
|
914bbfc164 | ||
|
|
060daef0b7 | ||
|
|
de5a9129b3 | ||
|
|
13953c2fbd | ||
|
|
479ad49e81 | ||
|
|
ce4b86daf5 | ||
|
|
0c5285c949 | ||
|
|
fbba4f06a9 | ||
|
|
e1b3b33c24 | ||
|
|
eb816d2e4f | ||
|
|
05e2fb26be | ||
|
|
7b182ac04b | ||
|
|
2e8bc3536f | ||
|
|
6d76fe690b | ||
|
|
d3f6301101 | ||
|
|
72c9c436be | ||
|
|
2ed9e5332d | ||
|
|
c5bad48ece | ||
|
|
af76f4a24a | ||
|
|
15bf28d5be | ||
|
|
c9ca40152f | ||
|
|
724d438721 | ||
|
|
acddf97771 | ||
|
|
823ed346c1 | ||
|
|
3bb771a149 | ||
|
|
793a5f826e | ||
|
|
8012f2eb8a | ||
|
|
98716d5568 | ||
|
|
edcc4e8968 | ||
|
|
12c4e1f61c | ||
|
|
cc97e96eaa | ||
|
|
b1883f3df5 | ||
|
|
fd36c0fac7 | ||
|
|
b5b207c940 | ||
|
|
c0ba119ebf | ||
|
|
e23bdfa204 | ||
|
|
14be3c24ff | ||
|
|
21ee88b149 | ||
|
|
dd9a1f5ce3 | ||
|
|
694675470e | ||
|
|
642b0f2291 | ||
|
|
1b3a8acab2 | ||
|
|
108c0c7de5 | ||
|
|
dcfb3b7d37 | ||
|
|
d98e9c6618 | ||
|
|
f01f95ddfb | ||
|
|
3a232b1d6c | ||
|
|
6f4920cb81 | ||
|
|
61ab6ce6bd | ||
|
|
7215b6e918 | ||
|
|
bd8ec8cb83 | ||
|
|
429022a468 | ||
|
|
5323d68d3d | ||
|
|
3af472d3b2 | ||
|
|
7eff7f02d0 | ||
|
|
661d2f64bb | ||
|
|
d0a7b09bf3 | ||
|
|
27bc6c8fca | ||
|
|
704041d5e0 | ||
|
|
8d78fd682d | ||
|
|
81c3988777 | ||
|
|
4b5424dd51 | ||
|
|
e69238307c | ||
|
|
019fe81de9 | ||
|
|
5177f71dbd | ||
|
|
497146adc5 | ||
|
|
f81f60e76f | ||
|
|
849a368d3d | ||
|
|
c09913a614 | ||
|
|
c5fe6748c0 | ||
|
|
fe9a6d87d2 | ||
|
|
7d692232ed | ||
|
|
a145576f39 | ||
|
|
574fd680c9 | ||
|
|
e95c1bbc76 | ||
|
|
155dbc370b | ||
|
|
60f1ee2360 | ||
|
|
3d03072da0 | ||
|
|
ed4d17b8f0 | ||
|
|
a91b9c4d92 | ||
|
|
709fdb14de | ||
|
|
46b0b8cba4 | ||
|
|
815ce7139c | ||
|
|
358193e25e | ||
|
|
4aca81ad9b | ||
|
|
c66ef42480 | ||
|
|
d9a316d083 | ||
|
|
96d2b36a08 | ||
|
|
00f5d0d984 | ||
|
|
044ed2708f | ||
|
|
8e697b50eb | ||
|
|
5ea4c507b2 | ||
|
|
10d4fcce8d | ||
|
|
86848e7d70 | ||
|
|
91ac2833f5 | ||
|
|
8ecbf4f7e4 | ||
|
|
0bd4edd9f4 | ||
|
|
af34829f38 | ||
|
|
155c4ec72a | ||
|
|
26fe812be4 | ||
|
|
997e9bfa52 | ||
|
|
d0ece86bb8 | ||
|
|
62d7076ff3 | ||
|
|
f9fb33e8cc | ||
|
|
2375bc6cac | ||
|
|
87d01e86fd | ||
|
|
e774706f43 | ||
|
|
8efae13afb | ||
|
|
6ed66f3275 | ||
|
|
2eb7bde95a | ||
|
|
edbd9b69eb | ||
|
|
db06eae7cb | ||
|
|
0f2bc3e01d | ||
|
|
ffee4df8b7 | ||
|
|
2d5160c576 | ||
|
|
3489dca83a | ||
|
|
1d3bc40a6b | ||
|
|
4bfc93b7ae | ||
|
|
c0d5d1e5a7 | ||
|
|
bac0b0a379 | ||
|
|
d9b4e62420 | ||
|
|
c8d74f0bcf | ||
|
|
18381664aa | ||
|
|
e9a67eb51f | ||
|
|
2ec264ed62 | ||
|
|
e5446a2336 | ||
|
|
db7d45792e | ||
|
|
5d3d02e1e7 | ||
|
|
2541e9d1eb | ||
|
|
cc6917f29d | ||
|
|
9636ff7c16 | ||
|
|
81f642b8b8 | ||
|
|
6f848f7f1c | ||
|
|
720bbcb5c0 | ||
|
|
8ce31a2831 | ||
|
|
7a4ff20d66 | ||
|
|
daed09e487 | ||
|
|
529e4f6514 | ||
|
|
6d451d52ea | ||
|
|
4d61c88661 | ||
|
|
bc923929bb | ||
|
|
193b175618 | ||
|
|
913a58057a | ||
|
|
a621514c71 | ||
|
|
c2bc31387c | ||
|
|
9b7e20351a | ||
|
|
df5566771a | ||
|
|
cbcc1994e8 | ||
|
|
bfdc4bac59 | ||
|
|
a3f8c36536 | ||
|
|
0f32311f6e | ||
|
|
fdb2dd9a8b | ||
|
|
ea5073fef4 | ||
|
|
ce2d779dbc | ||
|
|
a391fa4345 | ||
|
|
23e36b12fe | ||
|
|
59014f14ca | ||
|
|
bf3a0e7944 | ||
|
|
5f595f8ca7 | ||
|
|
4d447c3340 | ||
|
|
661e9eba51 | ||
|
|
b49157f968 | ||
|
|
7f58309143 | ||
|
|
4f45ea8e7d | ||
|
|
ccf79d077f | ||
|
|
f23450c380 | ||
|
|
f65976cf4d | ||
|
|
b8af147a8d | ||
|
|
1e05766571 | ||
|
|
587fd9e6b8 | ||
|
|
e97f647a43 | ||
|
|
b6ace42973 | ||
|
|
46d424fbaf | ||
|
|
d915787840 | ||
|
|
57e044e689 | ||
|
|
3f90e57861 | ||
|
|
0296b63be5 | ||
|
|
e041ad190f | ||
|
|
3ea49c6c2e | ||
|
|
c8032f04fa | ||
|
|
50a900e83c | ||
|
|
638398808b | ||
|
|
d2daf6c69f | ||
|
|
dd40c07a6d | ||
|
|
d95e35783a | ||
|
|
3078cb39c1 | ||
|
|
5a8725b233 | ||
|
|
b0a10399d7 | ||
|
|
9f7d00d83f | ||
|
|
b30bd8d2fe | ||
|
|
8bacaa17f4 | ||
|
|
4d43763a39 | ||
|
|
44d764d832 | ||
|
|
726296bb54 | ||
|
|
4ed522c5f8 | ||
|
|
b7592374aa | ||
|
|
077cd774f3 | ||
|
|
b5cb52bb5e | ||
|
|
0621d87133 | ||
|
|
cacfe5a268 | ||
|
|
8c8be22fe4 | ||
|
|
a0e09bbe5c | ||
|
|
4be793d4b3 | ||
|
|
60bb6bf50b | ||
|
|
7f8349469a | ||
|
|
6ad0951878 | ||
|
|
e097f2b3f4 | ||
|
|
3c81d3b154 | ||
|
|
ef2231f97b | ||
|
|
f81202660c | ||
|
|
6c5bf5f052 | ||
|
|
5f690d96bd | ||
|
|
64fed3553a | ||
|
|
a18c538c1f | ||
|
|
41d1b45fb9 | ||
|
|
602f95dd29 | ||
|
|
2a454e5a1e | ||
|
|
a100f8ffc8 | ||
|
|
527c282b92 | ||
|
|
e8125d08db | ||
|
|
0d40056f8c | ||
|
|
180a0fa8dd | ||
|
|
b1d5dc2a0e | ||
|
|
89f539ee24 | ||
|
|
f949fab231 | ||
|
|
edb66bd4e4 | ||
|
|
1b7e729750 | ||
|
|
7fb8f9172e | ||
|
|
069a4b1f80 | ||
|
|
c6bcdd89be | ||
|
|
093de3bce2 | ||
|
|
8c5ebf3797 | ||
|
|
33e46bad64 | ||
|
|
eca41dc7b4 | ||
|
|
33ca4f8887 | ||
|
|
76648fee66 | ||
|
|
6d07ecf751 | ||
|
|
5283eb8ce3 | ||
|
|
32c48443b5 | ||
|
|
ad00bee393 | ||
|
|
a54996d8a2 | ||
|
|
b6db4c31ca | ||
|
|
f82e24762b | ||
|
|
35b74be585 | ||
|
|
7199d2b5ac | ||
|
|
4697271294 | ||
|
|
3e4515e62a | ||
|
|
58c58beb4a | ||
|
|
f05518860f | ||
|
|
9b567fa6f4 | ||
|
|
c92de09ece | ||
|
|
9765b6d50e | ||
|
|
c6c3754f02 | ||
|
|
76a5b2a06a | ||
|
|
93a2ee1277 | ||
|
|
be6431ac13 | ||
|
|
4dd8dd948e | ||
|
|
e253ba71f8 | ||
|
|
30fc2d1209 | ||
|
|
cef5b46f93 | ||
|
|
c45ae068fc | ||
|
|
a20fbe33c0 | ||
|
|
82f4a8f671 | ||
|
|
33050f700e | ||
|
|
4ad0241c53 | ||
|
|
7e8c764dc7 | ||
|
|
fd9e4b3ec2 | ||
|
|
3abdcbee6f | ||
|
|
476688c84d | ||
|
|
7152be6086 | ||
|
|
e4ef14e830 | ||
|
|
f543046349 | ||
|
|
c9f76a01c5 | ||
|
|
5c4bf4ecdf | ||
|
|
17be7e97d3 | ||
|
|
71e39e5cb8 | ||
|
|
a17199ba21 | ||
|
|
d1c207e40b | ||
|
|
129dd0ffcc | ||
|
|
a3838734c5 | ||
|
|
b897de3e5e | ||
|
|
5407596e0d | ||
|
|
f7a164ffe5 | ||
|
|
653cd16eb2 | ||
|
|
a2153ced52 | ||
|
|
a44993bbf4 | ||
|
|
ba83da1983 | ||
|
|
0a98296642 | ||
|
|
ca970b9706 | ||
|
|
2b288fd67c | ||
|
|
7f1ad30db1 | ||
|
|
5721ea2b70 | ||
|
|
8c2a25b833 | ||
|
|
601a16b5cb | ||
|
|
879c20efc7 | ||
|
|
283f76240f | ||
|
|
ad17e7defe | ||
|
|
7a2e59fcef | ||
|
|
7c09f24f34 | ||
|
|
fb82866512 | ||
|
|
15da2c1f4c | ||
|
|
1dac598755 | ||
|
|
ad442fbee5 | ||
|
|
4e9925fd3f | ||
|
|
7b064ac99e | ||
|
|
a8a3b4cfd9 | ||
|
|
306ba63ab6 | ||
|
|
c21ded028a | ||
|
|
899ee713ab | ||
|
|
a3c34a51aa | ||
|
|
010a355e0b | ||
|
|
bcf0fda7ac | ||
|
|
96fdd94a01 | ||
|
|
68738bd227 | ||
|
|
8133aa8c91 | ||
|
|
ae1841efb0 | ||
|
|
27aef4ac2e | ||
|
|
b3d6a8eedd | ||
|
|
a023a86ca6 | ||
|
|
294e2090d0 | ||
|
|
c69d72b243 | ||
|
|
cdea9374d8 | ||
|
|
b1b223c90a | ||
|
|
c74456c07e | ||
|
|
ca55f96fd8 | ||
|
|
b7799df2a6 | ||
|
|
10341fba58 | ||
|
|
996dfb1c2f | ||
|
|
ce47efc415 | ||
|
|
3089e1ad69 | ||
|
|
62ff6f3c7f | ||
|
|
e49beb139c | ||
|
|
d8ee57cd50 | ||
|
|
427c18be7d | ||
|
|
7788a6fd07 | ||
|
|
49060e6686 | ||
|
|
a94bf29b34 | ||
|
|
ecb53cee55 | ||
|
|
fb77e18182 | ||
|
|
c47aafff66 | ||
|
|
aa1894964f | ||
|
|
c8d4dacffd | ||
|
|
c79f299d1a | ||
|
|
6a5dc4e774 | ||
|
|
540d72bc44 | ||
|
|
d96850c008 | ||
|
|
48c6373afa | ||
|
|
5512d37da3 | ||
|
|
f79677ba96 | ||
|
|
bfd8e81ffd | ||
|
|
720b233be6 | ||
|
|
3ddfafa831 | ||
|
|
f598fa71da | ||
|
|
6cebf146cb | ||
|
|
d0caf87eeb | ||
|
|
a0b082a36d | ||
|
|
ec23b26c60 | ||
|
|
15a7b8a859 | ||
|
|
b69d9cc840 | ||
|
|
12917f51d0 | ||
|
|
a3e72b61ee | ||
|
|
2739696813 | ||
|
|
f477b996b5 | ||
|
|
70250c93f1 | ||
|
|
4cca3b298d | ||
|
|
436ae0a027 | ||
|
|
dc3ce2b414 | ||
|
|
4244bd0b18 | ||
|
|
cbd273a19c | ||
|
|
b7f938531b | ||
|
|
af6841be49 | ||
|
|
d986a9eb4f | ||
|
|
3156191b83 | ||
|
|
593207866f | ||
|
|
1f5377da1c | ||
|
|
a4e25f32ea | ||
|
|
6775a28ec7 | ||
|
|
a8fca47a8c | ||
|
|
2b1a57c6d0 | ||
|
|
a7e5ccd080 | ||
|
|
a1fc14f8c4 | ||
|
|
9c13b39f99 | ||
|
|
b3d35dfae4 | ||
|
|
f26766033e | ||
|
|
fda4776d30 | ||
|
|
f9a624b803 | ||
|
|
4be60310c3 | ||
|
|
2d483f2d11 | ||
|
|
e18c050fb1 | ||
|
|
da4d49d940 | ||
|
|
845d718c67 | ||
|
|
ed057c971f | ||
|
|
94a978d063 | ||
|
|
dcb86d9186 | ||
|
|
62bdff351d | ||
|
|
bf25759a57 | ||
|
|
621e890a48 | ||
|
|
e843c890fb | ||
|
|
ff23d4e20b |
982 changed files with 86672 additions and 36606 deletions
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,40 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: "[Bug Report] Short Form Subject (50 Chars or less)"
|
|
||||||
labels: bug report
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots**
|
|
||||||
If applicable, add screenshots to help explain your problem please ensure that your screenshots are SFW or at least appropriately censored.
|
|
||||||
|
|
||||||
**Stash Version: (from Settings -> About):**
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: Create a report to help us fix the bug
|
||||||
|
labels: ["bug report"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: Provide a clear and concise description of what the bug is.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
description: Detail the steps that would replicate this issue.
|
||||||
|
placeholder: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: expected
|
||||||
|
attributes:
|
||||||
|
label: Expected behaviour
|
||||||
|
description: Provide clear and concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Screenshots or additional context
|
||||||
|
description: Provide any additional context and SFW screenshots here to help us solve this issue.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: stashversion
|
||||||
|
attributes:
|
||||||
|
label: Stash version
|
||||||
|
description: This can be found in Settings > About.
|
||||||
|
placeholder: (e.g. v0.28.1)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: devicedetails
|
||||||
|
attributes:
|
||||||
|
label: Device details
|
||||||
|
description: |
|
||||||
|
If this is an issue that occurs when using the Stash interface, please provide details of the device/browser used which presents the reported issue.
|
||||||
|
placeholder: (e.g. Firefox 97 (64-bit) on Windows 11)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: Please copy and paste any relevant log output from Settings > Logs. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Community forum
|
||||||
|
url: https://discourse.stashapp.cc
|
||||||
|
about: Start a discussion on the community forum.
|
||||||
|
- name: Community Discord
|
||||||
|
url: https://discord.gg/Y8MNsvQBvZ
|
||||||
|
about: Chat with the community on Discord.
|
||||||
|
- name: Documentation
|
||||||
|
url: https://docs.stashapp.cc
|
||||||
|
about: Check out documentation for help and information.
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
---
|
|
||||||
name: Discussion / Request for Commentary [RFC]
|
|
||||||
about: This is for issues that will be discussed and won't necessarily result directly
|
|
||||||
in commits or pull requests.
|
|
||||||
title: "[RFC] Short Form Title"
|
|
||||||
labels: help wanted
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Update or delete the title if you need to delegate your title gore to something
|
|
||||||
# Title
|
|
||||||
|
|
||||||
*### Scope*
|
|
||||||
<!-- describe the scope of your topic and your goals ideally within a single paragraph or TL;DR kind of summary so its easier for people to determine if they can contribute at a glance. -->
|
|
||||||
|
|
||||||
## Long Form
|
|
||||||
<!-- Only required if your scope and titles can't cover everything. -->
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
<!-- if you can show a picture or video examples post them here, please ensure that you respect people's time and attention and understand that people are volunteering their time, so concision is ideal and considerate. -->
|
|
||||||
|
|
||||||
## Reference Reading
|
|
||||||
<!-- if there is any reference reading or documentation, please refer to it here. -->
|
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: "[Feature] Short Form Title (50 chars or less.)"
|
|
||||||
labels: feature request
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
44
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
44
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
name: Feature Request
|
||||||
|
description: Request a new feature or idea to be added to Stash
|
||||||
|
labels: ["feature request"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the feature you'd like
|
||||||
|
description: Provide a clear description of the feature you'd like implemented
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: benefits
|
||||||
|
attributes:
|
||||||
|
label: Describe the benefits this would bring to existing users
|
||||||
|
description: |
|
||||||
|
Explain the measurable benefits this feature would achieve for existing users.
|
||||||
|
The benefits should be described in terms of outcomes for users, not specific implementations.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: already_possible
|
||||||
|
attributes:
|
||||||
|
label: Is there an existing way to achieve this goal?
|
||||||
|
description: |
|
||||||
|
Yes/No. If Yes, describe how your proposed feature differs from or improves upon the current method
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: confirm-search
|
||||||
|
attributes:
|
||||||
|
label: Have you searched for an existing open/closed issue?
|
||||||
|
description: |
|
||||||
|
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/stashapp/stash/issues?q=is%3Aissue) for any existing issues that cover the core request or benefit of your proposal.
|
||||||
|
options:
|
||||||
|
- label: I have searched for existing issues and none cover the core request of my proposal
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context or screenshots about the feature request here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
|
|
@ -2,7 +2,10 @@ name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ develop, master ]
|
branches:
|
||||||
|
- develop
|
||||||
|
- master
|
||||||
|
- 'releases/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
release:
|
release:
|
||||||
types: [ published ]
|
types: [ published ]
|
||||||
|
|
@ -12,17 +15,22 @@ concurrency:
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
COMPILER_IMAGE: stashapp/compiler:8
|
COMPILER_IMAGE: stashapp/compiler:12
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
run: git fetch --prune --unshallow --tags
|
run: git fetch --prune --unshallow --tags
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
- name: Pull compiler image
|
- name: Pull compiler image
|
||||||
run: docker pull $COMPILER_IMAGE
|
run: docker pull $COMPILER_IMAGE
|
||||||
|
|
||||||
|
|
@ -32,7 +40,7 @@ jobs:
|
||||||
cache-name: cache-node_modules
|
cache-name: cache-node_modules
|
||||||
with:
|
with:
|
||||||
path: ui/v2.5/node_modules
|
path: ui/v2.5/node_modules
|
||||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock') }}
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/pnpm-lock.yaml') }}
|
||||||
|
|
||||||
- name: Cache UI build
|
- name: Cache UI build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
|
@ -41,7 +49,7 @@ jobs:
|
||||||
cache-name: cache-ui
|
cache-name: cache-ui
|
||||||
with:
|
with:
|
||||||
path: ui/v2.5/build
|
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') }}
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/pnpm-lock.yaml', 'ui/v2.5/public/**', 'ui/v2.5/src/**', 'graphql/**/*.graphql') }}
|
||||||
|
|
||||||
- name: Cache go build
|
- name: Cache go build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
|
@ -60,7 +68,7 @@ jobs:
|
||||||
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 --env OFFICIAL_BUILD=${{ env.official-build }} -w /stash $COMPILER_IMAGE tail -f /dev/null
|
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 --env OFFICIAL_BUILD=${{ env.official-build }} -w /stash $COMPILER_IMAGE tail -f /dev/null
|
||||||
|
|
||||||
- name: Pre-install
|
- name: Pre-install
|
||||||
run: docker exec -t build /bin/bash -c "make pre-ui"
|
run: docker exec -t build /bin/bash -c "make CI=1 pre-ui"
|
||||||
|
|
||||||
- name: Generate
|
- name: Generate
|
||||||
run: docker exec -t build /bin/bash -c "make generate"
|
run: docker exec -t build /bin/bash -c "make generate"
|
||||||
|
|
@ -108,7 +116,7 @@ jobs:
|
||||||
- name: Upload Windows binary
|
- name: Upload Windows binary
|
||||||
# only upload binaries for pull requests
|
# only upload binaries for pull requests
|
||||||
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: stash-win.exe
|
name: stash-win.exe
|
||||||
path: dist/stash-win.exe
|
path: dist/stash-win.exe
|
||||||
|
|
@ -116,7 +124,7 @@ jobs:
|
||||||
- name: Upload macOS binary
|
- name: Upload macOS binary
|
||||||
# only upload binaries for pull requests
|
# only upload binaries for pull requests
|
||||||
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: stash-macos
|
name: stash-macos
|
||||||
path: dist/stash-macos
|
path: dist/stash-macos
|
||||||
|
|
@ -124,7 +132,7 @@ jobs:
|
||||||
- name: Upload Linux binary
|
- name: Upload Linux binary
|
||||||
# only upload binaries for pull requests
|
# only upload binaries for pull requests
|
||||||
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: stash-linux
|
name: stash-linux
|
||||||
path: dist/stash-linux
|
path: dist/stash-linux
|
||||||
|
|
@ -132,7 +140,7 @@ jobs:
|
||||||
- name: Upload UI
|
- name: Upload UI
|
||||||
# only upload for pull requests
|
# only upload for pull requests
|
||||||
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: stash-ui.zip
|
name: stash-ui.zip
|
||||||
path: dist/stash-ui.zip
|
path: dist/stash-ui.zip
|
||||||
|
|
|
||||||
10
.github/workflows/golangci-lint.yml
vendored
10
.github/workflows/golangci-lint.yml
vendored
|
|
@ -6,10 +6,11 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
|
- 'releases/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
COMPILER_IMAGE: stashapp/compiler:8
|
COMPILER_IMAGE: stashapp/compiler:12
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
golangci:
|
golangci:
|
||||||
|
|
@ -21,6 +22,11 @@ jobs:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
run: git fetch --prune --unshallow --tags
|
run: git fetch --prune --unshallow --tags
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
- name: Pull compiler image
|
- name: Pull compiler image
|
||||||
run: docker pull $COMPILER_IMAGE
|
run: docker pull $COMPILER_IMAGE
|
||||||
|
|
||||||
|
|
@ -33,7 +39,7 @@ jobs:
|
||||||
run: docker exec -t build /bin/bash -c "make generate-backend"
|
run: docker exec -t build /bin/bash -c "make generate-backend"
|
||||||
|
|
||||||
- name: Run golangci-lint
|
- name: Run golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||||
version: latest
|
version: latest
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -21,6 +21,9 @@ vendor
|
||||||
# GraphQL generated output
|
# GraphQL generated output
|
||||||
internal/api/generated_*.go
|
internal/api/generated_*.go
|
||||||
|
|
||||||
|
# Generated locale files
|
||||||
|
ui/login/locales/*
|
||||||
|
|
||||||
####
|
####
|
||||||
# Visual Studio
|
# Visual Studio
|
||||||
####
|
####
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ linters:
|
||||||
- unused
|
- unused
|
||||||
# Linters added by the stash project.
|
# Linters added by the stash project.
|
||||||
# - contextcheck
|
# - contextcheck
|
||||||
|
- copyloopvar
|
||||||
- dogsled
|
- dogsled
|
||||||
- errchkjson
|
- errchkjson
|
||||||
- errorlint
|
- errorlint
|
||||||
# - exhaustive
|
# - exhaustive
|
||||||
- exportloopref
|
|
||||||
- gocritic
|
- gocritic
|
||||||
# - goerr113
|
# - goerr113
|
||||||
- gofmt
|
- gofmt
|
||||||
|
|
@ -48,8 +48,6 @@ linters-settings:
|
||||||
ignore-generated-header: true
|
ignore-generated-header: true
|
||||||
severity: error
|
severity: error
|
||||||
confidence: 0.8
|
confidence: 0.8
|
||||||
error-code: 1
|
|
||||||
warning-code: 1
|
|
||||||
rules:
|
rules:
|
||||||
- name: blank-imports
|
- name: blank-imports
|
||||||
disabled: true
|
disabled: true
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
model:
|
model:
|
||||||
package: graphql
|
package: graphql
|
||||||
filename: ./pkg/scraper/stashbox/graphql/generated_models.go
|
filename: ./pkg/stashbox/graphql/generated_models.go
|
||||||
client:
|
client:
|
||||||
package: graphql
|
package: graphql
|
||||||
filename: ./pkg/scraper/stashbox/graphql/generated_client.go
|
filename: ./pkg/stashbox/graphql/generated_client.go
|
||||||
models:
|
models:
|
||||||
Date:
|
Date:
|
||||||
model: github.com/99designs/gqlgen/graphql.String
|
model: github.com/99designs/gqlgen/graphql.String
|
||||||
SceneDraftInput:
|
|
||||||
model: github.com/stashapp/stash/pkg/scraper/stashbox/graphql.SceneDraftInput
|
|
||||||
endpoint:
|
endpoint:
|
||||||
# This points to stashdb.org currently, but can be directed at any stash-box
|
# This points to stashdb.org currently, but can be directed at any stash-box
|
||||||
# instance. It is used for generation only.
|
# instance. It is used for generation only.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="WEB_MODULE" version="4">
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/certs" />
|
<excludeFolder url="file://$MODULE_DIR$/certs" />
|
||||||
|
|
@ -10,4 +11,4 @@
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
52
Makefile
52
Makefile
|
|
@ -275,12 +275,16 @@ generate: generate-backend generate-ui
|
||||||
|
|
||||||
.PHONY: generate-ui
|
.PHONY: generate-ui
|
||||||
generate-ui:
|
generate-ui:
|
||||||
cd ui/v2.5 && yarn run gqlgen
|
cd ui/v2.5 && npm run gqlgen
|
||||||
|
|
||||||
.PHONY: generate-backend
|
.PHONY: generate-backend
|
||||||
generate-backend: touch-ui
|
generate-backend: touch-ui
|
||||||
go generate ./cmd/stash
|
go generate ./cmd/stash
|
||||||
|
|
||||||
|
.PHONY: generate-login-locale
|
||||||
|
generate-login-locale:
|
||||||
|
go generate ./ui
|
||||||
|
|
||||||
.PHONY: generate-dataloaders
|
.PHONY: generate-dataloaders
|
||||||
generate-dataloaders:
|
generate-dataloaders:
|
||||||
go generate ./internal/api/loaders
|
go generate ./internal/api/loaders
|
||||||
|
|
@ -307,7 +311,8 @@ test:
|
||||||
# runs all tests - including integration tests
|
# runs all tests - including integration tests
|
||||||
.PHONY: it
|
.PHONY: it
|
||||||
it:
|
it:
|
||||||
go test -tags=integration ./...
|
$(eval GO_BUILD_TAGS += integration)
|
||||||
|
go test -tags "$(GO_BUILD_TAGS)" ./...
|
||||||
|
|
||||||
# generates test mocks
|
# generates test mocks
|
||||||
.PHONY: generate-test-mocks
|
.PHONY: generate-test-mocks
|
||||||
|
|
@ -333,9 +338,19 @@ server-clean:
|
||||||
|
|
||||||
# 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
|
# dependencies have changed
|
||||||
|
# If CI is set, configures pnpm to use a local store to avoid
|
||||||
|
# putting .pnpm-store in /stash
|
||||||
|
# NOTE: to run in the docker build container, using the existing
|
||||||
|
# node_modules folder, rename the .modules.yaml to .modules.yaml.bak
|
||||||
|
# and a new one will be generated. This will need to be reversed after
|
||||||
|
# building.
|
||||||
.PHONY: pre-ui
|
.PHONY: pre-ui
|
||||||
pre-ui:
|
pre-ui:
|
||||||
cd ui/v2.5 && yarn install --frozen-lockfile
|
ifdef CI
|
||||||
|
cd ui/v2.5 && pnpm config set store-dir ~/.pnpm-store && pnpm install --frozen-lockfile
|
||||||
|
else
|
||||||
|
cd ui/v2.5 && pnpm install --frozen-lockfile
|
||||||
|
endif
|
||||||
|
|
||||||
.PHONY: ui-env
|
.PHONY: ui-env
|
||||||
ui-env: build-info
|
ui-env: build-info
|
||||||
|
|
@ -350,8 +365,11 @@ ifdef STASH_SOURCEMAPS
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: ui
|
.PHONY: ui
|
||||||
ui: ui-env
|
ui: ui-only generate-login-locale
|
||||||
cd ui/v2.5 && yarn build
|
|
||||||
|
.PHONY: ui-only
|
||||||
|
ui-only: ui-env
|
||||||
|
cd ui/v2.5 && npm run build
|
||||||
|
|
||||||
.PHONY: zip-ui
|
.PHONY: zip-ui
|
||||||
zip-ui:
|
zip-ui:
|
||||||
|
|
@ -360,16 +378,34 @@ zip-ui:
|
||||||
|
|
||||||
.PHONY: ui-start
|
.PHONY: ui-start
|
||||||
ui-start: ui-env
|
ui-start: ui-env
|
||||||
cd ui/v2.5 && yarn start --host
|
cd ui/v2.5 && npm run start -- --host
|
||||||
|
|
||||||
.PHONY: fmt-ui
|
.PHONY: fmt-ui
|
||||||
fmt-ui:
|
fmt-ui:
|
||||||
cd ui/v2.5 && yarn format
|
cd ui/v2.5 && npm run format
|
||||||
|
|
||||||
# runs all of the frontend PR-acceptance steps
|
# runs all of the frontend PR-acceptance steps
|
||||||
.PHONY: validate-ui
|
.PHONY: validate-ui
|
||||||
validate-ui:
|
validate-ui:
|
||||||
cd ui/v2.5 && yarn run validate
|
cd ui/v2.5 && npm run validate
|
||||||
|
|
||||||
|
# these targets run the same steps as fmt-ui and validate-ui, but only on files that have changed
|
||||||
|
fmt-ui-quick:
|
||||||
|
cd ui/v2.5 && \
|
||||||
|
files=$$(git diff --name-only --relative --diff-filter d . ../../graphql); \
|
||||||
|
if [ -n "$$files" ]; then \
|
||||||
|
npm run prettier -- --write $$files; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# does not run tsc checks, as they are slow
|
||||||
|
validate-ui-quick:
|
||||||
|
cd ui/v2.5 && \
|
||||||
|
tsfiles=$$(git diff --name-only --relative --diff-filter d src | grep -e "\.tsx\?\$$"); \
|
||||||
|
scssfiles=$$(git diff --name-only --relative --diff-filter d src | grep "\.scss"); \
|
||||||
|
prettyfiles=$$(git diff --name-only --relative --diff-filter d . ../../graphql); \
|
||||||
|
if [ -n "$$tsfiles" ]; then npm run eslint -- $$tsfiles; fi && \
|
||||||
|
if [ -n "$$scssfiles" ]; then npm run stylelint -- $$scssfiles; fi && \
|
||||||
|
if [ -n "$$prettyfiles" ]; then npm run prettier -- --check $$prettyfiles; fi
|
||||||
|
|
||||||
# runs all of the backend PR-acceptance steps
|
# runs all of the backend PR-acceptance steps
|
||||||
.PHONY: validate-backend
|
.PHONY: validate-backend
|
||||||
|
|
|
||||||
76
README.md
76
README.md
|
|
@ -5,13 +5,13 @@
|
||||||
[](https://github.com/sponsors/stashapp)
|
[](https://github.com/sponsors/stashapp)
|
||||||
[](https://opencollective.com/stashapp)
|
[](https://opencollective.com/stashapp)
|
||||||
[](https://goreportcard.com/report/github.com/stashapp/stash)
|
[](https://goreportcard.com/report/github.com/stashapp/stash)
|
||||||
[](https://matrix.to/#/#stashapp:unredacted.org)
|
|
||||||
[](https://discord.gg/2TsNFKt)
|
[](https://discord.gg/2TsNFKt)
|
||||||
[](https://github.com/stashapp/stash/releases/latest)
|
[](https://github.com/stashapp/stash/releases/latest)
|
||||||
[](https://github.com/stashapp/stash/labels/bounty)
|
[](https://github.com/stashapp/stash/labels/bounty)
|
||||||
|
|
||||||
### **Stash is a self-hosted webapp written in Go which organizes and serves your porn.**
|
### **Stash is a self-hosted webapp written in Go which organizes and serves your diverse content collection, catering to both your SFW and NSFW needs.**
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
* Stash gathers 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 and sites.
|
* Stash gathers 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 and sites.
|
||||||
* Stash supports a wide variety of both video and image formats.
|
* Stash supports a wide variety of both video and image formats.
|
||||||
|
|
@ -20,64 +20,88 @@
|
||||||
|
|
||||||
You can [watch a SFW demo video](https://vimeo.com/545323354) to see it in action.
|
You can [watch a SFW demo video](https://vimeo.com/545323354) to see it in action.
|
||||||
|
|
||||||
For further information you can consult the [documentation](https://docs.stashapp.cc) or [read the in-app manual](ui/v2.5/src/docs/en).
|
For further information you can consult the [documentation](https://docs.stashapp.cc) or access the in-app manual from within the application (also available at [docs.stashapp.cc/in-app-manual](https://docs.stashapp.cc/in-app-manual)).
|
||||||
|
|
||||||
# Installing Stash
|
# Installing Stash
|
||||||
|
|
||||||
|
Step-by-step instructions are available at [docs.stashapp.cc/installation](https://docs.stashapp.cc/installation/).
|
||||||
|
|
||||||
|
#### Windows Users:
|
||||||
|
|
||||||
|
As of version 0.27.0, Stash no longer supports _Windows 7, 8, Server 2008 and Server 2012._
|
||||||
|
At least Windows 10 or Server 2016 is required.
|
||||||
|
|
||||||
|
#### Mac Users:
|
||||||
|
|
||||||
|
As of version 0.29.0, Stash requires _macOS 11 Big Sur_ or later.
|
||||||
|
Stash can still be run through docker on older versions of macOS.
|
||||||
|
|
||||||
<img src="docs/readme_assets/windows_logo.svg" width="100%" height="75"> Windows | <img src="docs/readme_assets/mac_logo.svg" width="100%" height="75"> macOS | <img src="docs/readme_assets/linux_logo.svg" width="100%" height="75"> Linux | <img src="docs/readme_assets/docker_logo.svg" width="100%" height="75"> Docker
|
<img src="docs/readme_assets/windows_logo.svg" width="100%" height="75"> Windows | <img src="docs/readme_assets/mac_logo.svg" width="100%" height="75"> macOS | <img src="docs/readme_assets/linux_logo.svg" width="100%" height="75"> Linux | <img src="docs/readme_assets/docker_logo.svg" width="100%" height="75"> Docker
|
||||||
:---:|:---:|:---:|:---:
|
:---:|:---:|:---:|:---:
|
||||||
[Latest Release](https://github.com/stashapp/stash/releases/latest/download/stash-win.exe) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/stash-win.exe)</sub></sup> | [Latest Release](https://github.com/stashapp/stash/releases/latest/download/Stash.app.zip) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/Stash.app.zip)</sub></sup> | [Latest Release (amd64)](https://github.com/stashapp/stash/releases/latest/download/stash-linux) <br /> <sup><sub>[Development Preview (amd64)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-linux)</sub></sup> <br /> [More Architectures...](https://github.com/stashapp/stash/releases/latest) | [Instructions](docker/production/README.md) <br /> <sup><sub>[Sample docker-compose.yml](docker/production/docker-compose.yml)</sub></sup>
|
[Latest Release](https://github.com/stashapp/stash/releases/latest/download/stash-win.exe) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/stash-win.exe)</sub></sup> | [Latest Release](https://github.com/stashapp/stash/releases/latest/download/Stash.app.zip) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/Stash.app.zip)</sub></sup> | [Latest Release (amd64)](https://github.com/stashapp/stash/releases/latest/download/stash-linux) <br /> <sup><sub>[Development Preview (amd64)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-linux)</sub></sup> <br /> [More Architectures...](https://github.com/stashapp/stash/releases/latest) | [Instructions](docker/production/README.md) <br /> <sup><sub>[Sample docker-compose.yml](docker/production/docker-compose.yml)</sub></sup>
|
||||||
|
|
||||||
Download links for other platforms and architectures are available on the [Releases page](https://github.com/stashapp/stash/releases).
|
Download links for other platforms and architectures are available on the [Releases](https://github.com/stashapp/stash/releases) page.
|
||||||
|
|
||||||
## First Run
|
## First Run
|
||||||
|
|
||||||
#### Windows/macOS Users: Security Prompt
|
#### Windows/macOS Users: Security Prompt
|
||||||
|
|
||||||
On Windows or macOS, running the app might present a security prompt since the binary isn't yet signed.
|
On Windows or macOS, running the app might present a security prompt since the application binary isn't yet signed.
|
||||||
|
|
||||||
On Windows, bypass this by clicking "more info" and then the "run anyway" button. On macOS, Control+Click the app, click "Open", and then "Open" again.
|
- On Windows, bypass this by clicking "more info" and then the "run anyway" button.
|
||||||
|
- On macOS, Control+Click the app, click "Open", and then "Open" again.
|
||||||
|
|
||||||
#### FFmpeg
|
#### ffmpeg
|
||||||
Stash requires FFmpeg. If you don't have it installed, Stash will download a copy for you. It is recommended that Linux users install `ffmpeg` from their distro's package manager.
|
|
||||||
|
Stash requires FFmpeg. If you don't have it installed, Stash will prompt you to download a copy during setup. It is recommended that Linux users install `ffmpeg` from their distro's package manager.
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
## Quickstart Guide
|
## Quickstart Guide
|
||||||
Stash is a web-based application. Once the application is running, the interface is available (by default) from http://localhost:9999.
|
|
||||||
|
Stash is a web-based application. Once the application is running, the interface is available (by default) from `http://localhost:9999`.
|
||||||
|
|
||||||
On first run, Stash will prompt you for some configuration options and media directories to index, called "Scanning" in Stash. After scanning, your media will be available for browsing, curating, editing, and tagging.
|
On first run, Stash will prompt you for some configuration options and media directories to index, called "Scanning" in Stash. After scanning, your media will be available for browsing, curating, editing, and tagging.
|
||||||
|
|
||||||
Stash can pull metadata (performers, tags, descriptions, studios, and more) directly from many sites through the use of [scrapers](https://github.com/stashapp/stash/blob/develop/ui/v2.5/src/docs/en/Manual/Scraping.md), which integrate directly into Stash. Identifying an entire collection will typically require a mix of multiple sources:
|
Stash can pull metadata (performers, tags, descriptions, studios, and more) directly from many sites through the use of [scrapers](https://github.com/stashapp/stash/blob/develop/ui/v2.5/src/docs/en/Manual/Scraping.md), which integrate directly into Stash. Identifying an entire collection will typically require a mix of multiple sources:
|
||||||
- The project maintains [StashDB](https://stashdb.org/), a crowd-sourced repository of scene, studio, and performer information. Connecting it to Stash will allow you to automatically identify much of a typical media collection. It runs on our stash-box software and is primarily focused on mainstream digital scenes and studios. Instructions, invite codes, and more can be found in this guide to [Accessing StashDB](https://guidelines.stashdb.org/docs/faq_getting-started/stashdb/accessing-stashdb/).
|
- The stashapp team maintains [StashDB](https://stashdb.org/), a crowd-sourced repository of scene, studio, and performer information. Connecting it to Stash will allow you to automatically identify much of a typical media collection. It runs on our stash-box software and is primarily focused on mainstream digital scenes and studios. Instructions, invite codes, and more can be found in this guide to [Accessing StashDB](https://guidelines.stashdb.org/docs/faq_getting-started/stashdb/accessing-stashdb/).
|
||||||
- Several community-managed stash-box databases can also be connected to Stash in a similar manner. Each one serves a slightly different niche and follows their own methodology. A rundown of each stash-box, their differences, and the information you need to sign up can be found in this guide to [Accessing Stash-Boxes](https://guidelines.stashdb.org/docs/faq_getting-started/stashdb/accessing-stash-boxes/).
|
- Several community-managed stash-box databases can also be connected to Stash in a similar manner. Each one serves a slightly different niche and follows their own methodology. A rundown of each stash-box, their differences, and the information you need to sign up can be found in this guide to [Accessing Stash-Boxes](https://guidelines.stashdb.org/docs/faq_getting-started/stashdb/accessing-stash-boxes/).
|
||||||
- Many community-maintained scrapers can also be downloaded, installed, and updated from within Stash, allowing you to pull data from a wide range of other websites and databases. They can be found by navigating to Settings -> Metadata Providers -> Available Scrapers -> Community (stable). These can be trickier to use than a stash-box because every scraper works a little differently. For more information, please visit the [CommunityScrapers repository](https://github.com/stashapp/CommunityScrapers).
|
- Many community-maintained scrapers can also be downloaded, installed, and updated from within Stash, allowing you to pull data from a wide range of other websites and databases. They can be found by navigating to `Settings → Metadata Providers → Available Scrapers → Community (stable)`. These can be trickier to use than a stash-box because every scraper works a little differently. For more information, please visit the [CommunityScrapers repository](https://github.com/stashapp/CommunityScrapers).
|
||||||
- All of the above methods of scraping data into Stash are also covered in more detail in our [Guide to Scraping](https://docs.stashapp.cc/beginner-guides/guide-to-scraping/).
|
- All of the above methods of scraping data into Stash are also covered in more detail in our [Guide to Scraping](https://docs.stashapp.cc/beginner-guides/guide-to-scraping/).
|
||||||
|
|
||||||
<sub>[StashDB](http://stashdb.org) is the canonical instance of our open source metadata API, [stash-box](https://github.com/stashapp/stash-box).</sub>
|
<sub>[StashDB](http://stashdb.org) is the canonical instance of our open source metadata API, [stash-box](https://github.com/stashapp/stash-box).</sub>
|
||||||
|
|
||||||
# Translation
|
# Translation
|
||||||
[](https://hosted.weblate.org/engage/stashapp/)
|
|
||||||
🇧🇷 🇨🇳 🇩🇰 🇳🇱 🇬🇧 🇪🇪 🇫🇮 🇫🇷 🇩🇪 🇮🇹 🇯🇵 🇰🇷 🇵🇱 🇷🇺 🇪🇸 🇸🇪 🇹🇼 🇹🇷
|
|
||||||
|
|
||||||
Stash is available in 25 languages (so far!) and it could be in your language too. We use Weblate to coordinate community translations. If you want to help us translate Stash into your language, you can make an account at [Stash's Weblate](https://hosted.weblate.org/projects/stashapp/stash/) to get started contributing new languages or improving existing ones. Thanks!
|
[](https://translate.codeberg.org/engage/stash/)
|
||||||
|
|
||||||
# Support (FAQ)
|
Stash is available in 32 languages (so far!) and it could be in your language too. We use Weblate to coordinate community translations. If you want to help us translate Stash, you can make an account at [Codeberg's Weblate](https://translate.codeberg.org/projects/stash/stash/) to contribute to new or existing languages. Thanks!
|
||||||
|
|
||||||
Check out our documentation on [Stash-Docs](https://docs.stashapp.cc) for information about the software, questions, guides, add-ons and more.
|
The badge below shows the current translation status of Stash across all supported languages:
|
||||||
|
|
||||||
For more help you can:
|
[](https://translate.codeberg.org/engage/stash/)
|
||||||
* Check the in-app documentation, in the top right corner of the app (it's also mirrored on [Stash-Docs](https://docs.stashapp.cc/in-app-manual))
|
|
||||||
* Join the [Matrix space](https://matrix.to/#/#stashapp:unredacted.org)
|
|
||||||
* Join the [Discord server](https://discord.gg/2TsNFKt), where the community can offer support.
|
|
||||||
* Start a [discussion on GitHub](https://github.com/stashapp/stash/discussions)
|
|
||||||
|
|
||||||
# Customization
|
# Support & Resources
|
||||||
|
|
||||||
## Themes and CSS Customization
|
Need help or want to get involved? Start with the documentation, then reach out to the community if you need further assistance.
|
||||||
There is a [directory of community-created themes](https://docs.stashapp.cc/user-interface-ui/themes) on Stash-Docs, along with instructions on how to install them.
|
|
||||||
|
|
||||||
You can also change the Stash interface to fit your desired style with various snippets from [Custom CSS snippets](https://docs.stashapp.cc/user-interface-ui/custom-css-snippets).
|
- Documentation
|
||||||
|
- Official docs: https://docs.stashapp.cc - official guides guides and troubleshooting.
|
||||||
|
- In-app manual: press <kbd>Shift</kbd> + <kbd>?</kbd> in the app or view the manual online: https://docs.stashapp.cc/in-app-manual.
|
||||||
|
- FAQ: https://discourse.stashapp.cc/c/support/faq/28 - common questions and answers.
|
||||||
|
- Community wiki: https://discourse.stashapp.cc/tags/c/community-wiki/22/stash - guides, how-to’s and tips.
|
||||||
|
|
||||||
|
- Community & discussion
|
||||||
|
- Community forum: https://discourse.stashapp.cc - community support, feature requests and discussions.
|
||||||
|
- Discord: https://discord.gg/2TsNFKt - real-time chat and community support.
|
||||||
|
- GitHub discussions: https://github.com/stashapp/stash/discussions - community support and feature discussions.
|
||||||
|
- Lemmy community: https://discuss.online/c/stashapp - Reddit-style community space.
|
||||||
|
|
||||||
|
- Community scrapers & plugins
|
||||||
|
- Metadata sources: https://docs.stashapp.cc/metadata-sources/
|
||||||
|
- Plugins: https://docs.stashapp.cc/plugins/
|
||||||
|
- Themes: https://docs.stashapp.cc/themes/
|
||||||
|
- Other projects: https://docs.stashapp.cc/other-projects/
|
||||||
|
|
||||||
# For Developers
|
# For Developers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ func customUsage() {
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
func printPhash(ff *ffmpeg.FFMpeg, ffp ffmpeg.FFProbe, inputfile string, quiet *bool) error {
|
func printPhash(ff *ffmpeg.FFMpeg, ffp *ffmpeg.FFProbe, inputfile string, quiet *bool) error {
|
||||||
ffvideoFile, err := ffp.NewVideoFile(inputfile)
|
ffvideoFile, err := ffp.NewVideoFile(inputfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -80,7 +80,7 @@ func main() {
|
||||||
ffmpegPath, ffprobePath := getPaths()
|
ffmpegPath, ffprobePath := getPaths()
|
||||||
encoder := ffmpeg.NewEncoder(ffmpegPath)
|
encoder := ffmpeg.NewEncoder(ffmpegPath)
|
||||||
// don't need to InitHWSupport, phashing doesn't use hw acceleration
|
// don't need to InitHWSupport, phashing doesn't use hw acceleration
|
||||||
ffprobe := ffmpeg.FFProbe(ffprobePath)
|
ffprobe := ffmpeg.NewFFProbe(ffprobePath)
|
||||||
|
|
||||||
for _, item := range args {
|
for _, item := range args {
|
||||||
if err := printPhash(encoder, ffprobe, item, quiet); err != nil {
|
if err := printPhash(encoder, ffprobe, item, quiet); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ func main() {
|
||||||
// Logs only error level message to stderr.
|
// Logs only error level message to stderr.
|
||||||
func initLogTemp() *log.Logger {
|
func initLogTemp() *log.Logger {
|
||||||
l := log.NewLogger()
|
l := log.NewLogger()
|
||||||
l.Init("", true, "Error")
|
l.Init("", true, "Error", 0)
|
||||||
logger.Logger = l
|
logger.Logger = l
|
||||||
|
|
||||||
return l
|
return l
|
||||||
|
|
@ -118,7 +118,7 @@ func initLogTemp() *log.Logger {
|
||||||
|
|
||||||
func initLog(cfg *config.Config) *log.Logger {
|
func initLog(cfg *config.Config) *log.Logger {
|
||||||
l := log.NewLogger()
|
l := log.NewLogger()
|
||||||
l.Init(cfg.GetLogFile(), cfg.GetLogOut(), cfg.GetLogLevel())
|
l.Init(cfg.GetLogFile(), cfg.GetLogOut(), cfg.GetLogLevel(), cfg.GetLogFileMaxSize())
|
||||||
logger.Logger = l
|
logger.Logger = l
|
||||||
|
|
||||||
return l
|
return l
|
||||||
|
|
@ -152,6 +152,9 @@ func recoverPanic() {
|
||||||
func exitError(err error) {
|
func exitError(err error) {
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
// #5784 - log to stdout as well as the logger
|
||||||
|
// this does mean that it will log twice if the logger is set to stdout
|
||||||
|
fmt.Println(err)
|
||||||
if desktop.IsDesktop() {
|
if desktop.IsDesktop() {
|
||||||
desktop.FatalError(err)
|
desktop.FatalError(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,36 @@
|
||||||
# This dockerfile should be built with `make docker-build` from the stash root.
|
# This dockerfile should be built with `make docker-build` from the stash root.
|
||||||
|
|
||||||
# Build Frontend
|
# Build Frontend
|
||||||
FROM node:alpine as frontend
|
FROM node:24-alpine AS frontend
|
||||||
RUN apk add --no-cache make git
|
RUN apk add --no-cache make git
|
||||||
## cache node_modules separately
|
## cache node_modules separately
|
||||||
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
|
COPY ./ui/v2.5/package.json ./ui/v2.5/pnpm-lock.yaml /stash/ui/v2.5/
|
||||||
WORKDIR /stash
|
WORKDIR /stash
|
||||||
COPY Makefile /stash/
|
COPY Makefile /stash/
|
||||||
COPY ./graphql /stash/graphql/
|
COPY ./graphql /stash/graphql/
|
||||||
COPY ./ui /stash/ui/
|
COPY ./ui /stash/ui/
|
||||||
|
# pnpm install with npm
|
||||||
|
RUN npm install -g pnpm
|
||||||
RUN make pre-ui
|
RUN make pre-ui
|
||||||
RUN make generate-ui
|
RUN make generate-ui
|
||||||
ARG GITHASH
|
ARG GITHASH
|
||||||
ARG STASH_VERSION
|
ARG STASH_VERSION
|
||||||
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
|
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui-only
|
||||||
|
|
||||||
# Build Backend
|
# Build Backend
|
||||||
FROM golang:1.19-alpine as backend
|
FROM golang:1.24.3-alpine AS backend
|
||||||
RUN apk add --no-cache make alpine-sdk
|
RUN apk add --no-cache make alpine-sdk
|
||||||
WORKDIR /stash
|
WORKDIR /stash
|
||||||
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
|
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
|
||||||
|
COPY ./graphql /stash/graphql/
|
||||||
COPY ./scripts /stash/scripts/
|
COPY ./scripts /stash/scripts/
|
||||||
COPY ./pkg /stash/pkg/
|
COPY ./pkg /stash/pkg/
|
||||||
COPY ./cmd /stash/cmd
|
COPY ./cmd /stash/cmd/
|
||||||
COPY ./internal /stash/internal
|
COPY ./internal /stash/internal/
|
||||||
|
# needed for generate-login-locale
|
||||||
|
COPY ./ui /stash/ui/
|
||||||
|
RUN make generate-backend generate-login-locale
|
||||||
COPY --from=frontend /stash /stash/
|
COPY --from=frontend /stash /stash/
|
||||||
RUN make generate-backend
|
|
||||||
ARG GITHASH
|
ARG GITHASH
|
||||||
ARG STASH_VERSION
|
ARG STASH_VERSION
|
||||||
RUN make flags-release flags-pie stash
|
RUN make flags-release flags-pie stash
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,62 @@
|
||||||
# This dockerfile should be built with `make docker-cuda-build` from the stash root.
|
# This dockerfile should be built with `make docker-cuda-build` from the stash root.
|
||||||
|
ARG CUDA_VERSION=12.8.0
|
||||||
|
|
||||||
# Build Frontend
|
# Build Frontend
|
||||||
FROM node:alpine as frontend
|
FROM node:20-alpine AS frontend
|
||||||
RUN apk add --no-cache make git
|
RUN apk add --no-cache make git
|
||||||
## cache node_modules separately
|
## cache node_modules separately
|
||||||
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
|
COPY ./ui/v2.5/package.json ./ui/v2.5/pnpm-lock.yaml /stash/ui/v2.5/
|
||||||
WORKDIR /stash
|
WORKDIR /stash
|
||||||
COPY Makefile /stash/
|
COPY Makefile /stash/
|
||||||
COPY ./graphql /stash/graphql/
|
COPY ./graphql /stash/graphql/
|
||||||
COPY ./ui /stash/ui/
|
COPY ./ui /stash/ui/
|
||||||
|
# pnpm install with npm
|
||||||
|
RUN npm install -g pnpm
|
||||||
RUN make pre-ui
|
RUN make pre-ui
|
||||||
RUN make generate-ui
|
RUN make generate-ui
|
||||||
ARG GITHASH
|
ARG GITHASH
|
||||||
ARG STASH_VERSION
|
ARG STASH_VERSION
|
||||||
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
|
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui-only
|
||||||
|
|
||||||
# Build Backend
|
# Build Backend
|
||||||
FROM golang:1.19-bullseye as backend
|
FROM golang:1.24.3-bullseye AS backend
|
||||||
RUN apt update && apt install -y build-essential golang
|
RUN apt update && apt install -y build-essential golang
|
||||||
WORKDIR /stash
|
WORKDIR /stash
|
||||||
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
|
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
|
||||||
|
COPY ./graphql /stash/graphql/
|
||||||
COPY ./scripts /stash/scripts/
|
COPY ./scripts /stash/scripts/
|
||||||
COPY ./pkg /stash/pkg/
|
COPY ./pkg /stash/pkg/
|
||||||
COPY ./cmd /stash/cmd
|
COPY ./cmd /stash/cmd
|
||||||
COPY ./internal /stash/internal
|
COPY ./internal /stash/internal
|
||||||
|
# needed for generate-login-locale
|
||||||
|
COPY ./ui /stash/ui/
|
||||||
|
RUN make generate-backend generate-login-locale
|
||||||
COPY --from=frontend /stash /stash/
|
COPY --from=frontend /stash /stash/
|
||||||
RUN make generate-backend
|
|
||||||
ARG GITHASH
|
ARG GITHASH
|
||||||
ARG STASH_VERSION
|
ARG STASH_VERSION
|
||||||
RUN make flags-release flags-pie stash
|
RUN make flags-release flags-pie stash
|
||||||
|
|
||||||
# Final Runnable Image
|
# Final Runnable Image
|
||||||
FROM nvidia/cuda:12.0.1-base-ubuntu22.04
|
FROM nvidia/cuda:${CUDA_VERSION}-base-ubuntu24.04
|
||||||
RUN apt update && apt upgrade -y && apt install -y ca-certificates libvips-tools ffmpeg wget intel-media-va-driver-non-free vainfo
|
RUN apt update && apt upgrade -y && apt install -y \
|
||||||
RUN rm -rf /var/lib/apt/lists/*
|
# stash dependencies
|
||||||
COPY --from=backend /stash/stash /usr/bin/
|
ca-certificates libvips-tools ffmpeg \
|
||||||
|
# intel dependencies
|
||||||
|
intel-media-va-driver-non-free vainfo \
|
||||||
|
# python tools
|
||||||
|
python3 python3-pip && \
|
||||||
|
# cleanup
|
||||||
|
apt autoremove -y && apt clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY --from=backend --chmod=555 /stash/stash /usr/bin/
|
||||||
|
|
||||||
# NVENC Patch
|
# NVENC Patch
|
||||||
RUN mkdir -p /usr/local/bin /patched-lib
|
RUN mkdir -p /usr/local/bin /patched-lib
|
||||||
RUN wget https://raw.githubusercontent.com/keylase/nvidia-patch/master/patch.sh -O /usr/local/bin/patch.sh
|
ADD --chmod=555 https://raw.githubusercontent.com/keylase/nvidia-patch/master/patch.sh /usr/local/bin/patch.sh
|
||||||
RUN wget https://raw.githubusercontent.com/keylase/nvidia-patch/master/docker-entrypoint.sh -O /usr/local/bin/docker-entrypoint.sh
|
ADD --chmod=555 https://raw.githubusercontent.com/keylase/nvidia-patch/master/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
RUN chmod +x /usr/local/bin/patch.sh /usr/local/bin/docker-entrypoint.sh /usr/bin/stash
|
|
||||||
|
|
||||||
ENV LANG C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
ENV NVIDIA_VISIBLE_DEVICES all
|
ENV NVIDIA_VISIBLE_DEVICES=all
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES=video,utility
|
ENV NVIDIA_DRIVER_CAPABILITIES=video,utility
|
||||||
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
|
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
|
||||||
EXPOSE 9999
|
EXPOSE 9999
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ This dockerfile is used to build a stash docker container using the current sour
|
||||||
|
|
||||||
# Building the docker container
|
# Building the docker container
|
||||||
|
|
||||||
From the top-level directory (should contain `main.go` file):
|
From the top-level directory (should contain `tools.go` file):
|
||||||
|
|
||||||
```
|
```
|
||||||
make docker-build
|
make docker-build
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,17 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then BIN=stash-linux-arm32v6; \
|
||||||
FROM --platform=$TARGETPLATFORM alpine:latest AS app
|
FROM --platform=$TARGETPLATFORM alpine:latest AS app
|
||||||
COPY --from=binary /stash /usr/bin/
|
COPY --from=binary /stash /usr/bin/
|
||||||
|
|
||||||
# vips version 8.15.0-r0 breaks thumbnail generation on arm32v6
|
RUN apk add --no-cache ca-certificates python3 py3-requests py3-requests-toolbelt py3-lxml py3-pip ffmpeg tzdata vips vips-tools \
|
||||||
# need to use 8.14.3-r0 from alpine 3.18 instead
|
&& pip install --break-system-packages mechanicalsoup cloudscraper stashapp-tools
|
||||||
|
|
||||||
RUN apk add --no-cache --virtual .build-deps gcc python3-dev musl-dev \
|
|
||||||
&& apk add --no-cache ca-certificates python3 py3-requests py3-requests-toolbelt py3-lxml py3-pip ffmpeg ruby tzdata \
|
|
||||||
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/v3.18/community vips=8.14.3-r0 vips-tools=8.14.3-r0 \
|
|
||||||
&& pip install --user --break-system-packages mechanicalsoup cloudscraper bencoder.pyx stashapp-tools \
|
|
||||||
&& gem install faraday \
|
|
||||||
&& apk del .build-deps
|
|
||||||
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
|
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
|
||||||
|
|
||||||
|
# Basic build-time metadata as defined at https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
|
||||||
|
LABEL org.opencontainers.image.title="Stash" \
|
||||||
|
org.opencontainers.image.description="An organizer for your porn, written in Go." \
|
||||||
|
org.opencontainers.image.url="https://stashapp.cc" \
|
||||||
|
org.opencontainers.image.documentation="https://docs.stashapp.cc" \
|
||||||
|
org.opencontainers.image.source="https://github.com/stashapp/stash" \
|
||||||
|
org.opencontainers.image.licenses="AGPL-3.0"
|
||||||
|
|
||||||
EXPOSE 9999
|
EXPOSE 9999
|
||||||
CMD ["stash"]
|
CMD ["stash"]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.19
|
FROM golang:1.24.3
|
||||||
|
|
||||||
LABEL maintainer="https://discord.gg/2TsNFKt"
|
LABEL maintainer="https://discord.gg/2TsNFKt"
|
||||||
|
|
||||||
|
|
@ -8,15 +8,11 @@ RUN mkdir -p /etc/apt/keyrings
|
||||||
|
|
||||||
ADD https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key nodesource.gpg.key
|
ADD https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key nodesource.gpg.key
|
||||||
RUN cat nodesource.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && rm nodesource.gpg.key
|
RUN cat nodesource.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && rm nodesource.gpg.key
|
||||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||||
|
|
||||||
ADD https://dl.yarnpkg.com/debian/pubkey.gpg yarn.gpg
|
|
||||||
RUN cat yarn.gpg | gpg --dearmor -o /etc/apt/keyrings/yarn.gpg && rm yarn.gpg
|
|
||||||
RUN echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
git make tar bash nodejs yarn zip \
|
git make tar bash nodejs zip \
|
||||||
clang llvm-dev cmake patch libxml2-dev uuid-dev libssl-dev xz-utils \
|
clang llvm-dev cmake patch libxml2-dev uuid-dev libssl-dev xz-utils \
|
||||||
bzip2 gzip sed cpio libbz2-dev zlib1g-dev \
|
bzip2 gzip sed cpio libbz2-dev zlib1g-dev \
|
||||||
gcc-mingw-w64 \
|
gcc-mingw-w64 \
|
||||||
|
|
@ -24,11 +20,14 @@ RUN apt-get update && \
|
||||||
gcc-aarch64-linux-gnu libc-dev-arm64-cross && \
|
gcc-aarch64-linux-gnu libc-dev-arm64-cross && \
|
||||||
rm -rf /var/lib/apt/lists/*;
|
rm -rf /var/lib/apt/lists/*;
|
||||||
|
|
||||||
|
# pnpm install with npm
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
# FreeBSD cross-compilation setup
|
# FreeBSD cross-compilation setup
|
||||||
# https://github.com/smartmontools/docker-build/blob/6b8c92560d17d325310ba02d9f5a4b250cb0764a/Dockerfile#L66
|
# https://github.com/smartmontools/docker-build/blob/6b8c92560d17d325310ba02d9f5a4b250cb0764a/Dockerfile#L66
|
||||||
ENV FREEBSD_VERSION 12.4
|
ENV FREEBSD_VERSION 13.4
|
||||||
ENV FREEBSD_DOWNLOAD_URL http://ftp.plusline.de/FreeBSD/releases/amd64/${FREEBSD_VERSION}-RELEASE/base.txz
|
ENV FREEBSD_DOWNLOAD_URL http://ftp.plusline.de/FreeBSD/releases/amd64/${FREEBSD_VERSION}-RELEASE/base.txz
|
||||||
ENV FREEBSD_SHA 581c7edacfd2fca2bdf5791f667402d22fccd8a5e184635e0cac075564d57aa8
|
ENV FREEBSD_SHA 8e13b0a93daba349b8d28ad246d7beb327659b2ef4fe44d89f447392daec5a7c
|
||||||
|
|
||||||
RUN cd /tmp && \
|
RUN cd /tmp && \
|
||||||
curl -o base.txz $FREEBSD_DOWNLOAD_URL && \
|
curl -o base.txz $FREEBSD_DOWNLOAD_URL && \
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
user=stashapp
|
user=stashapp
|
||||||
repo=compiler
|
repo=compiler
|
||||||
version=8
|
version=12
|
||||||
|
|
||||||
latest:
|
latest:
|
||||||
docker build -t ${user}/${repo}:latest .
|
docker build -t ${user}/${repo}:latest .
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
# Docker Installation (for most 64-bit GNU/Linux systems)
|
# 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.
|
StashApp is supported on most systems that support Docker. Your OS likely ships with or makes available the necessary packages.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
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.
|
Only `docker` is 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.
|
Installation instructions are available below, and if your distributions's repository ships a current version of docker, you may use that.
|
||||||
https://docs.docker.com/engine/install/
|
https://docs.docker.com/engine/install/
|
||||||
|
|
||||||
|
On some distributions, `docker compose` is shipped seperately, usually as `docker-cli-compose`. docker-compose is not recommended.
|
||||||
|
|
||||||
### Get the docker-compose.yml file
|
### Get the docker-compose.yml file
|
||||||
|
|
||||||
Now you can either navigate to the [docker-compose.yml](https://raw.githubusercontent.com/stashapp/stash/develop/docker/production/docker-compose.yml) in the repository, or if you have curl, you can make your Linux console do it for you:
|
Now you can either navigate to the [docker-compose.yml](https://raw.githubusercontent.com/stashapp/stash/develop/docker/production/docker-compose.yml) in the repository, or if you have curl, you can make your Linux console do it for you:
|
||||||
|
|
@ -19,7 +21,7 @@ curl -o docker-compose.yml https://raw.githubusercontent.com/stashapp/stash/deve
|
||||||
Once you have that file where you want it, modify the settings as you please, and then run:
|
Once you have that file where you want it, modify the settings as you please, and then run:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
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
|
||||||
|
|
@ -29,9 +31,9 @@ Good luck and have fun!
|
||||||
### Docker
|
### 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 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.
|
||||||
|
|
||||||
The StashApp docker container ships with everything you need to automatically build and run stash, including ffmpeg.
|
The StashApp docker container ships with everything you need to automatically run stash, including ffmpeg.
|
||||||
|
|
||||||
### docker-compose
|
### 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.
|
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](https://docs.stashapp.cc/guides/reverse-proxy/) (such as NGINX or Traefik) is recommended, but not required.
|
||||||
|
|
||||||
The latest version is always recommended.
|
The latest version is always recommended.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
# APPNICENAME=Stash
|
# APPNICENAME=Stash
|
||||||
# APPDESCRIPTION=An organizer for your porn, written in Go
|
# APPDESCRIPTION=An organizer for your porn, written in Go
|
||||||
version: '3.4'
|
|
||||||
services:
|
services:
|
||||||
stash:
|
stash:
|
||||||
image: stashapp/stash:latest
|
image: stashapp/stash:latest
|
||||||
|
|
@ -27,10 +26,12 @@ services:
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
## Adjust below paths (the left part) to your liking.
|
## Adjust below paths (the left part) to your liking.
|
||||||
## E.g. you can change ./config:/root/.stash to ./stash:/root/.stash
|
## E.g. you can change ./config:/root/.stash to ./stash:/root/.stash
|
||||||
|
## The left part is the path on your host, the right part is the path in the stash container.
|
||||||
|
|
||||||
## Keep configs, scrapers, and plugins here.
|
## Keep configs, scrapers, and plugins here.
|
||||||
- ./config:/root/.stash
|
- ./config:/root/.stash
|
||||||
## Point this at your collection.
|
## Point this at your collection.
|
||||||
|
## The left side is where your collection is on your host, the right side is where it will be in stash.
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
## This is where your stash's metadata lives
|
## This is where your stash's metadata lives
|
||||||
- ./metadata:/metadata
|
- ./metadata:/metadata
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,24 @@
|
||||||
|
## Goals and design vision
|
||||||
|
|
||||||
|
The goal of stash is to be:
|
||||||
|
- an application for organising and viewing adult content - currently this is videos and images, in future this will be extended to include audio and text content
|
||||||
|
- organising includes scraping of metadata from websites and metadata repositories
|
||||||
|
- free and open-source
|
||||||
|
- portable and offline - can be run on a USB stick without needing to install dependencies (with the exception of ffmpeg)
|
||||||
|
- minimal, but highly extensible. The core feature set should be the minimum required to achieve the primary goal, while being extensible enough to extend via plugins
|
||||||
|
- easy to learn and use, with minimal technical knowledge required
|
||||||
|
|
||||||
|
The core stash system is not intended for:
|
||||||
|
- managing downloading of content
|
||||||
|
- managing content on external websites
|
||||||
|
- publically sharing content
|
||||||
|
|
||||||
|
Other requirements:
|
||||||
|
- support as many video and image formats as possible
|
||||||
|
- interfaces with external systems (for example stash-box) should be made as generic as possible.
|
||||||
|
|
||||||
|
Design considerations:
|
||||||
|
- features are easy to add and difficult to remove. Large superfluous features should be scrutinised and avoided where possible (eg DLNA, filename parser). Such features should be considered for third-party plugins instead.
|
||||||
|
|
||||||
## Technical Debt
|
## Technical Debt
|
||||||
Please be sure to consider how heavily your contribution impacts the maintainability of the project long term, sometimes less is more. We don't want to merge collossal pull requests with hundreds of dependencies by a driveby contributor.
|
Please be sure to consider how heavily your contribution impacts the maintainability of the project long term, sometimes less is more. We don't want to merge collossal pull requests with hundreds of dependencies by a driveby contributor.
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
|
|
||||||
* [Go](https://golang.org/dl/)
|
* [Go](https://golang.org/dl/)
|
||||||
* [GolangCI](https://golangci-lint.run/) - A meta-linter which runs several linters in parallel
|
* [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)
|
* To install, follow the [local installation instructions](https://golangci-lint.run/welcome/install/#local-installation)
|
||||||
* [Yarn](https://yarnpkg.com/en/docs/install) - Yarn package manager
|
* [nodejs](https://nodejs.org/en/download) - nodejs runtime
|
||||||
|
* corepack/[pnpm](https://pnpm.io/installation) - nodejs package manager (included with nodejs)
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
|
|
@ -22,32 +23,22 @@ NOTE: The `make` command in Windows will be `mingw32-make` with MinGW. For examp
|
||||||
### macOS
|
### macOS
|
||||||
|
|
||||||
1. If you don't have it already, install the [Homebrew package manager](https://brew.sh).
|
1. If you don't have it already, install the [Homebrew package manager](https://brew.sh).
|
||||||
2. Install dependencies: `brew install go git yarn gcc make node ffmpeg`
|
2. Install dependencies: `brew install go git gcc make node ffmpeg`
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
#### Arch Linux
|
#### Arch Linux
|
||||||
|
|
||||||
1. Install dependencies: `sudo pacman -S go git yarn gcc make nodejs ffmpeg --needed`
|
1. Install dependencies: `sudo pacman -S go git gcc make nodejs ffmpeg --needed`
|
||||||
|
|
||||||
#### Ubuntu
|
#### Ubuntu
|
||||||
|
|
||||||
1. Install dependencies: `sudo apt-get install golang git yarnpkg gcc nodejs ffmpeg -y`
|
1. Install dependencies: `sudo apt-get install golang git gcc nodejs ffmpeg -y`
|
||||||
|
|
||||||
### OpenBSD
|
### OpenBSD
|
||||||
|
|
||||||
1. Install dependencies `doas pkg_add gmake go git yarn node cmake`
|
1. Install dependencies `doas pkg_add gmake go git node cmake ffmpeg`
|
||||||
2. Compile a custom ffmpeg from ports. The default ffmpeg in OpenBSD's packages is not compiled with WebP support, which is required by Stash.
|
2. Follow the instructions below to build a release, but replace the final step `make build-release` with `gmake flags-release stash`, to [avoid the PIE buildmode](https://github.com/golang/go/issues/59866).
|
||||||
- If you've already installed ffmpeg, uninstall it: `doas pkg_delete ffmpeg`
|
|
||||||
- If you haven't already, [fetch the ports tree and verify](https://www.openbsd.org/faq/ports/ports.html#PortsFetch).
|
|
||||||
- Find the ffmpeg port in `/usr/ports/graphics/ffmpeg`, and patch the Makefile to include libwebp
|
|
||||||
- Add `webp` to `WANTLIB`
|
|
||||||
- Add `graphics/libwebp` to the list in `LIB_DEPENDS`
|
|
||||||
- Add `-lwebp -lwebpdecoder -lwebpdemux -lwebpmux` to `LIBavcodec_EXTRALIBS`
|
|
||||||
- Add `--enable-libweb` to the list in `CONFIGURE_ARGS`
|
|
||||||
- If you've already built ffmpeg from ports before, you may need to also increment `REVISION`
|
|
||||||
- Run `doas make install`
|
|
||||||
- Follow the instructions below to build a release, but replace the final step `make build-release` with `gmake flags-release stash`, to [avoid the PIE buildmode](https://github.com/golang/go/issues/59866).
|
|
||||||
|
|
||||||
NOTE: The `make` command in OpenBSD will be `gmake`. For example, `make pre-ui` will be `gmake pre-ui`.
|
NOTE: The `make` command in OpenBSD will be `gmake`. For example, `make pre-ui` will be `gmake pre-ui`.
|
||||||
|
|
||||||
|
|
@ -69,6 +60,9 @@ NOTE: The `make` command in OpenBSD will be `gmake`. For example, `make pre-ui`
|
||||||
* `make it` - Runs all unit and integration tests
|
* `make it` - Runs all unit and integration tests
|
||||||
* `make fmt` - Formats the Go source code
|
* `make fmt` - Formats the Go source code
|
||||||
* `make fmt-ui` - Formats the UI source code
|
* `make fmt-ui` - Formats the UI source code
|
||||||
|
* `make validate-ui` - Runs tests and checks for the UI only
|
||||||
|
* `make fmt-ui-quick` - (experimental) Formats only changed UI source code
|
||||||
|
* `make validate-ui-quick` - (experimental) Runs tests and checks of changed UI code
|
||||||
* `make server-start` - Runs a development stash server in the `.local` directory
|
* `make server-start` - Runs a development stash server in the `.local` directory
|
||||||
* `make server-clean` - Removes the `.local` directory and all of its contents
|
* `make server-clean` - Removes the `.local` directory and all of its contents
|
||||||
* `make ui-start` - Runs the UI in development mode. Requires a running Stash server to connect to - the server URL can be changed from the default of `http://localhost:9999` using the environment variable `VITE_APP_PLATFORM_URL`, but keep in mind that authentication cannot be used since the session authorization cookie cannot be sent cross-origin. The UI runs on port `3000` or the next available port.
|
* `make ui-start` - Runs the UI in development mode. Requires a running Stash server to connect to - the server URL can be changed from the default of `http://localhost:9999` using the environment variable `VITE_APP_PLATFORM_URL`, but keep in mind that authentication cannot be used since the session authorization cookie cannot be sent cross-origin. The UI runs on port `3000` or the next available port.
|
||||||
|
|
|
||||||
80
go.mod
80
go.mod
|
|
@ -1,11 +1,11 @@
|
||||||
module github.com/stashapp/stash
|
module github.com/stashapp/stash
|
||||||
|
|
||||||
go 1.19
|
go 1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.17.2
|
github.com/99designs/gqlgen v0.17.73
|
||||||
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552
|
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552
|
||||||
github.com/Yamashou/gqlgenc v0.0.6
|
github.com/Yamashou/gqlgenc v0.32.1
|
||||||
github.com/anacrolix/dms v1.2.2
|
github.com/anacrolix/dms v1.2.2
|
||||||
github.com/antchfx/htmlquery v1.3.0
|
github.com/antchfx/htmlquery v1.3.0
|
||||||
github.com/asticode/go-astisub v0.25.1
|
github.com/asticode/go-astisub v0.25.1
|
||||||
|
|
@ -15,80 +15,89 @@ require (
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
||||||
github.com/doug-martin/goqu/v9 v9.18.0
|
github.com/doug-martin/goqu/v9 v9.18.0
|
||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/httplog v0.3.1
|
github.com/go-chi/httplog v0.3.1
|
||||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
|
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
|
||||||
github.com/gofrs/uuid/v5 v5.0.0
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
|
||||||
github.com/golang-migrate/migrate/v4 v4.16.2
|
github.com/golang-migrate/migrate/v4 v4.16.2
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/securecookie v1.1.1
|
github.com/gorilla/securecookie v1.1.1
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.6
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
|
github.com/hasura/go-graphql-client v0.13.1
|
||||||
github.com/jinzhu/copier v0.4.0
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/kermieisinthehouse/gosx-notifier v0.1.2
|
github.com/kermieisinthehouse/gosx-notifier v0.1.2
|
||||||
github.com/kermieisinthehouse/systray v1.2.4
|
github.com/kermieisinthehouse/systray v1.2.4
|
||||||
github.com/knadh/koanf v1.5.0
|
github.com/knadh/koanf/parsers/yaml v1.1.0
|
||||||
|
github.com/knadh/koanf/providers/env v1.1.0
|
||||||
|
github.com/knadh/koanf/providers/file v1.2.0
|
||||||
|
github.com/knadh/koanf/providers/posflag v1.0.1
|
||||||
|
github.com/knadh/koanf/v2 v2.2.1
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
|
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||||
github.com/remeh/sizedwaitgroup v1.0.0
|
github.com/remeh/sizedwaitgroup v1.0.0
|
||||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cast v1.5.1
|
github.com/spf13/cast v1.6.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.6
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tidwall/gjson v1.16.0
|
github.com/tidwall/gjson v1.16.0
|
||||||
github.com/vearutop/statigz v1.4.0
|
github.com/vearutop/statigz v1.4.0
|
||||||
github.com/vektah/dataloaden v0.3.0
|
github.com/vektah/dataloaden v0.3.0
|
||||||
github.com/vektah/gqlparser/v2 v2.4.2
|
github.com/vektah/gqlparser/v2 v2.5.27
|
||||||
github.com/vektra/mockery/v2 v2.10.0
|
github.com/vektra/mockery/v2 v2.10.0
|
||||||
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e
|
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e
|
||||||
github.com/zencoder/go-dash/v3 v3.0.2
|
github.com/zencoder/go-dash/v3 v3.0.2
|
||||||
golang.org/x/crypto v0.21.0
|
golang.org/x/crypto v0.45.0
|
||||||
golang.org/x/image v0.12.0
|
golang.org/x/image v0.18.0
|
||||||
golang.org/x/net v0.23.0
|
golang.org/x/net v0.47.0
|
||||||
golang.org/x/sys v0.18.0
|
golang.org/x/sys v0.38.0
|
||||||
golang.org/x/term v0.18.0
|
golang.org/x/term v0.37.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.31.0
|
||||||
|
golang.org/x/time v0.10.0
|
||||||
gopkg.in/guregu/null.v4 v4.0.0
|
gopkg.in/guregu/null.v4 v4.0.0
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/agnivade/levenshtein v1.1.1 // indirect
|
github.com/agnivade/levenshtein v1.2.1 // indirect
|
||||||
github.com/antchfx/xpath v1.2.3 // indirect
|
github.com/antchfx/xpath v1.2.3 // indirect
|
||||||
github.com/asticode/go-astikit v0.20.0 // indirect
|
github.com/asticode/go-astikit v0.20.0 // indirect
|
||||||
github.com/asticode/go-astits v1.8.0 // indirect
|
github.com/asticode/go-astits v1.8.0 // indirect
|
||||||
github.com/chromedp/sysutil v1.0.0 // indirect
|
github.com/chromedp/sysutil v1.0.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/coder/websocket v1.8.12 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/ws v1.3.0 // indirect
|
github.com/gobwas/ws v1.3.0 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/matryer/moq v0.2.3 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
|
@ -99,19 +108,22 @@ require (
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rs/zerolog v1.30.0 // indirect
|
github.com/rs/zerolog v1.30.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/sosodev/duration v1.3.1 // indirect
|
||||||
github.com/spf13/afero v1.9.5 // indirect
|
github.com/spf13/afero v1.9.5 // indirect
|
||||||
github.com/spf13/cobra v1.7.0 // indirect
|
github.com/spf13/cobra v1.7.0 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/viper v1.16.0 // indirect
|
github.com/spf13/viper v1.16.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/urfave/cli/v2 v2.8.1 // indirect
|
github.com/urfave/cli/v2 v2.27.6 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.12.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.3 // indirect
|
||||||
golang.org/x/tools v0.13.0 // indirect
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
287
go.sum
287
go.sum
|
|
@ -49,28 +49,29 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/99designs/gqlgen v0.17.2 h1:yczvlwMsfcVu/JtejqfrLwXuSP0yZFhmcss3caEvHw8=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
github.com/99designs/gqlgen v0.17.2/go.mod h1:K5fzLKwtph+FFgh9j7nFbRUdBKvTcGnsta51fsMTn3o=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/99designs/gqlgen v0.17.73 h1:A3Ki+rHWqKbAOlg5fxiZBnz6OjW3nwupDHEG15gEsrg=
|
||||||
|
github.com/99designs/gqlgen v0.17.73/go.mod h1:2RyGWjy2k7W9jxrs8MOQthXGkD3L3oGr0jXW3Pu8lGg=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||||
|
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||||
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
||||||
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552 h1:eukVk+mGmbSZppLw8WJGpEUgMC570eb32y7FOsPW4Kc=
|
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552 h1:eukVk+mGmbSZppLw8WJGpEUgMC570eb32y7FOsPW4Kc=
|
||||||
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552/go.mod h1:LKbO1i6L1lSlwWx4NHWVECxubHNKFz2YQoEMGXAFVy8=
|
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552/go.mod h1:LKbO1i6L1lSlwWx4NHWVECxubHNKFz2YQoEMGXAFVy8=
|
||||||
github.com/Yamashou/gqlgenc v0.0.6 h1:wfMTtuVSrX2N1z5/ssecxx+E7l1fa0FOq5mwFW47oY4=
|
github.com/Yamashou/gqlgenc v0.32.1 h1:EHs9//xQxXlyltkSFXM+fhO2rTXcWNw6FPKRJ6t+iQQ=
|
||||||
github.com/Yamashou/gqlgenc v0.0.6/go.mod h1:WOXjogecRGpD1WKgxnnyHJo0/Dxn44p/LNRoE6mtFQo=
|
github.com/Yamashou/gqlgenc v0.32.1/go.mod h1:o5SxKt9d3+oUZ2i0V3CW8lHFyunfLR+KcKHubS4zf5E=
|
||||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
|
||||||
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
|
||||||
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
|
||||||
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
|
||||||
github.com/anacrolix/dms v1.2.2 h1:0mk2/DXNqa5KDDbaLgFPf3oMV6VCGdFNh3d/gt4oafM=
|
github.com/anacrolix/dms v1.2.2 h1:0mk2/DXNqa5KDDbaLgFPf3oMV6VCGdFNh3d/gt4oafM=
|
||||||
github.com/anacrolix/dms v1.2.2/go.mod h1:msPKAoppoNRfrYplJqx63FZ+VipDZ4Xsj3KzIQxyU7k=
|
github.com/anacrolix/dms v1.2.2/go.mod h1:msPKAoppoNRfrYplJqx63FZ+VipDZ4Xsj3KzIQxyU7k=
|
||||||
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||||
|
|
@ -81,6 +82,9 @@ github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||||
|
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||||
github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E=
|
github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E=
|
||||||
github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8=
|
github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8=
|
||||||
github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes=
|
github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes=
|
||||||
|
|
@ -99,21 +103,12 @@ github.com/asticode/go-astisub v0.25.1 h1:RZMGfZPp7CXOkI6g+zCU7DRLuciGPGup921uKZ
|
||||||
github.com/asticode/go-astisub v0.25.1/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
|
github.com/asticode/go-astisub v0.25.1/go.mod h1:WTkuSzFB+Bp7wezuSf2Oxulj5A8zu2zLRVFf6bIFQK8=
|
||||||
github.com/asticode/go-astits v1.8.0 h1:rf6aiiGn/QhlFjNON1n5plqF3Fs025XLUwiQ0NB6oZg=
|
github.com/asticode/go-astits v1.8.0 h1:rf6aiiGn/QhlFjNON1n5plqF3Fs025XLUwiQ0NB6oZg=
|
||||||
github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ=
|
github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
|
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
|
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
|
|
||||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bool64/dev v0.2.28 h1:6ayDfrB/jnNr2iQAZHI+uT3Qi6rErSbJYQs1y8rSrwM=
|
github.com/bool64/dev v0.2.28 h1:6ayDfrB/jnNr2iQAZHI+uT3Qi6rErSbJYQs1y8rSrwM=
|
||||||
|
github.com/bool64/dev v0.2.28/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
|
||||||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
|
@ -147,22 +142,24 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
|
||||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
|
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||||
|
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
|
github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
|
||||||
github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
|
github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
|
@ -177,7 +174,6 @@ github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8
|
||||||
github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY=
|
github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY=
|
||||||
github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ=
|
github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ=
|
||||||
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
|
@ -192,18 +188,17 @@ github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||||
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-chi/httplog v0.3.1 h1:uC3IUWCZagtbCinb3ypFh36SEcgd6StWw2Bu0XSXRtg=
|
github.com/go-chi/httplog v0.3.1 h1:uC3IUWCZagtbCinb3ypFh36SEcgd6StWw2Bu0XSXRtg=
|
||||||
|
|
@ -213,19 +208,18 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
|
||||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
|
||||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
|
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE=
|
||||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
|
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
|
|
@ -233,13 +227,13 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
||||||
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
github.com/gobwas/ws v1.3.0 h1:sbeU3Y4Qzlb+MOzIe6mQGf7QR4Hkv6ZD0qhGkBFL2O0=
|
github.com/gobwas/ws v1.3.0 h1:sbeU3Y4Qzlb+MOzIe6mQGf7QR4Hkv6ZD0qhGkBFL2O0=
|
||||||
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
github.com/gobwas/ws v1.3.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
|
||||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA=
|
github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA=
|
||||||
github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o=
|
github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o=
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
|
|
@ -276,7 +270,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
|
@ -293,8 +286,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
|
@ -319,6 +312,8 @@ github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE
|
||||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||||
|
|
@ -329,14 +324,11 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||||
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
|
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
|
||||||
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
|
||||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
|
@ -344,8 +336,6 @@ github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
|
||||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
|
||||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||||
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
|
@ -355,23 +345,17 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
|
@ -381,12 +365,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
|
||||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||||
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
|
github.com/hasura/go-graphql-client v0.13.1 h1:kKbjhxhpwz58usVl+Xvgah/TDha5K2akNTRQdsEHN6U=
|
||||||
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
github.com/hasura/go-graphql-client v0.13.1/go.mod h1:k7FF7h53C+hSNFRG3++DdVZWIuHdCaTbI7siTJ//zGQ=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
|
||||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
|
||||||
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
|
|
||||||
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
|
|
||||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
|
@ -397,18 +377,12 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
|
@ -416,18 +390,25 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
||||||
github.com/kermieisinthehouse/gosx-notifier v0.1.2 h1:KV0KBeKK2B24kIHY7iK0jgS64Q05f4oB+hUZmsPodxQ=
|
github.com/kermieisinthehouse/gosx-notifier v0.1.2 h1:KV0KBeKK2B24kIHY7iK0jgS64Q05f4oB+hUZmsPodxQ=
|
||||||
github.com/kermieisinthehouse/gosx-notifier v0.1.2/go.mod h1:xyWT07azFtUOcHl96qMVvKhvKzsMcS7rKTHQyv8WTho=
|
github.com/kermieisinthehouse/gosx-notifier v0.1.2/go.mod h1:xyWT07azFtUOcHl96qMVvKhvKzsMcS7rKTHQyv8WTho=
|
||||||
github.com/kermieisinthehouse/systray v1.2.4 h1:pdH5vnl+KKjRrVCRU4g/2W1/0HVzuuJ6WXHlPPHYY6s=
|
github.com/kermieisinthehouse/systray v1.2.4 h1:pdH5vnl+KKjRrVCRU4g/2W1/0HVzuuJ6WXHlPPHYY6s=
|
||||||
github.com/kermieisinthehouse/systray v1.2.4/go.mod h1:axh6C/jNuSyC0QGtidZJURc9h+h41HNoMySoLVrhVR4=
|
github.com/kermieisinthehouse/systray v1.2.4/go.mod h1:axh6C/jNuSyC0QGtidZJURc9h+h41HNoMySoLVrhVR4=
|
||||||
github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||||
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||||
|
github.com/knadh/koanf/parsers/yaml v1.1.0 h1:3ltfm9ljprAHt4jxgeYLlFPmUaunuCgu1yILuTXRdM4=
|
||||||
|
github.com/knadh/koanf/parsers/yaml v1.1.0/go.mod h1:HHmcHXUrp9cOPcuC+2wrr44GTUB0EC+PyfN3HZD9tFg=
|
||||||
|
github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc=
|
||||||
|
github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
|
||||||
|
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
|
||||||
|
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||||
|
github.com/knadh/koanf/providers/posflag v1.0.1 h1:EnMxHSrPkYCFnKgBUl5KBgrjed8gVFrcXDzaW4l/C6Y=
|
||||||
|
github.com/knadh/koanf/providers/posflag v1.0.1/go.mod h1:3Wn3+YG3f4ljzRyCUgIwH7G0sZ1pMjCOsNBovrbKmAk=
|
||||||
|
github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE=
|
||||||
|
github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
|
@ -435,16 +416,16 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|
||||||
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||||
|
|
@ -453,15 +434,14 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/matryer/moq v0.2.3 h1:Q06vEqnBYjjfx5KKgHfYRKE/lvlRu+Nj+xodG4YdHnU=
|
|
||||||
github.com/matryer/moq v0.2.3/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE=
|
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
|
|
@ -469,33 +449,27 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
|
||||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
|
@ -507,26 +481,20 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007 h1:Ohgj9L0EYOgXxkDp+bczlMBiulwmqYzQpvQNUdtt3oc=
|
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007 h1:Ohgj9L0EYOgXxkDp+bczlMBiulwmqYzQpvQNUdtt3oc=
|
||||||
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007/go.mod h1:wKCOWMb6iNlvKiOToY2cNuaovSXvIiv1zDi9QDR7aGQ=
|
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007/go.mod h1:wKCOWMb6iNlvKiOToY2cNuaovSXvIiv1zDi9QDR7aGQ=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
|
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
|
||||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
|
||||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
|
||||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
|
@ -543,28 +511,22 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
|
||||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
|
||||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
|
||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
|
||||||
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
|
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
|
||||||
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
||||||
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
|
@ -572,28 +534,25 @@ github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+
|
||||||
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
||||||
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
|
||||||
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||||
|
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
|
||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||||
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
|
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
|
|
||||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
|
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||||
|
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
|
|
@ -601,15 +560,16 @@ github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfA
|
||||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||||
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
|
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
|
||||||
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
|
||||||
|
|
@ -617,8 +577,9 @@ github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1Fof
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
|
@ -628,8 +589,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
|
@ -642,24 +604,21 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||||
github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
|
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||||
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
|
|
||||||
github.com/vearutop/statigz v1.4.0 h1:RQL0KG3j/uyA/PFpHeZ/L6l2ta920/MxlOAIGEOuwmU=
|
github.com/vearutop/statigz v1.4.0 h1:RQL0KG3j/uyA/PFpHeZ/L6l2ta920/MxlOAIGEOuwmU=
|
||||||
github.com/vearutop/statigz v1.4.0/go.mod h1:LYTolBLiz9oJISwiVKnOQoIwhO1LWX1A7OECawGS8XE=
|
github.com/vearutop/statigz v1.4.0/go.mod h1:LYTolBLiz9oJISwiVKnOQoIwhO1LWX1A7OECawGS8XE=
|
||||||
github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
|
github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
|
||||||
github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.0/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.1/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||||
github.com/vektah/gqlparser/v2 v2.4.2 h1:29TGc6QmhEUq5fll+2FPoTmhUhR65WEKN4VK/jo0OlM=
|
|
||||||
github.com/vektah/gqlparser/v2 v2.4.2/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0=
|
|
||||||
github.com/vektra/mockery/v2 v2.10.0 h1:MiiQWxwdq7/ET6dCXLaJzSGEN17k758H7JHS9kOdiks=
|
github.com/vektra/mockery/v2 v2.10.0 h1:MiiQWxwdq7/ET6dCXLaJzSGEN17k758H7JHS9kOdiks=
|
||||||
github.com/vektra/mockery/v2 v2.10.0/go.mod h1:m/WO2UzWzqgVX3nvqpRQq70I4Z7jbSCRhdmkgtp+Ab4=
|
github.com/vektra/mockery/v2 v2.10.0/go.mod h1:m/WO2UzWzqgVX3nvqpRQq70I4Z7jbSCRhdmkgtp+Ab4=
|
||||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||||
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e h1:GruPsb+44XvYAzuAgJW1d1WHqmcI73L2XSjsbx/eJZw=
|
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e h1:GruPsb+44XvYAzuAgJW1d1WHqmcI73L2XSjsbx/eJZw=
|
||||||
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e/go.mod h1:wA8kQ8WFipMciY9WcWzqQgZordm/P7l8IZdvx1crwmc=
|
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e/go.mod h1:wA8kQ8WFipMciY9WcWzqQgZordm/P7l8IZdvx1crwmc=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
@ -671,11 +630,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||||
github.com/zencoder/go-dash/v3 v3.0.2 h1:oP1+dOh+Gp57PkvdCyMfbHtrHaxfl3w4kR3KBBbuqQE=
|
github.com/zencoder/go-dash/v3 v3.0.2 h1:oP1+dOh+Gp57PkvdCyMfbHtrHaxfl3w4kR3KBBbuqQE=
|
||||||
github.com/zencoder/go-dash/v3 v3.0.2/go.mod h1:30R5bKy1aUYY45yesjtZ9l8trNc2TwNqbS17WVQmCzk=
|
github.com/zencoder/go-dash/v3 v3.0.2/go.mod h1:30R5bKy1aUYY45yesjtZ9l8trNc2TwNqbS17WVQmCzk=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
|
||||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
|
@ -689,6 +645,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
|
@ -706,8 +664,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
|
||||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
|
@ -721,8 +679,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||||
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
|
@ -748,11 +706,9 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
@ -802,9 +758,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
|
@ -834,18 +789,16 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190415145633-3fd5a3612ccd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190415145633-3fd5a3612ccd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -857,12 +810,10 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -877,8 +828,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -886,7 +835,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
@ -899,7 +847,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
@ -918,28 +865,23 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
|
@ -948,13 +890,13 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||||
|
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
|
@ -997,7 +939,6 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
|
@ -1014,12 +955,9 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
@ -1066,7 +1004,6 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
|
@ -1130,11 +1067,9 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
|
||||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
|
@ -1175,7 +1110,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
@ -1188,14 +1122,14 @@ gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.66.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
@ -1212,4 +1146,3 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
|
||||||
|
|
|
||||||
21
gqlgen.yml
21
gqlgen.yml
|
|
@ -7,9 +7,6 @@ exec:
|
||||||
filename: internal/api/generated_exec.go
|
filename: internal/api/generated_exec.go
|
||||||
model:
|
model:
|
||||||
filename: internal/api/generated_models.go
|
filename: internal/api/generated_models.go
|
||||||
resolver:
|
|
||||||
filename: internal/api/resolver.go
|
|
||||||
type: Resolver
|
|
||||||
|
|
||||||
struct_tag: gqlgen
|
struct_tag: gqlgen
|
||||||
|
|
||||||
|
|
@ -20,7 +17,7 @@ autobind:
|
||||||
- github.com/stashapp/stash/pkg/scraper
|
- github.com/stashapp/stash/pkg/scraper
|
||||||
- github.com/stashapp/stash/internal/identify
|
- github.com/stashapp/stash/internal/identify
|
||||||
- github.com/stashapp/stash/internal/dlna
|
- github.com/stashapp/stash/internal/dlna
|
||||||
- github.com/stashapp/stash/pkg/scraper/stashbox
|
- github.com/stashapp/stash/pkg/stashbox
|
||||||
|
|
||||||
models:
|
models:
|
||||||
# Scalars
|
# Scalars
|
||||||
|
|
@ -38,12 +35,8 @@ models:
|
||||||
model: github.com/stashapp/stash/internal/api.BoolMap
|
model: github.com/stashapp/stash/internal/api.BoolMap
|
||||||
PluginConfigMap:
|
PluginConfigMap:
|
||||||
model: github.com/stashapp/stash/internal/api.PluginConfigMap
|
model: github.com/stashapp/stash/internal/api.PluginConfigMap
|
||||||
# define to force resolvers
|
File:
|
||||||
Image:
|
model: github.com/stashapp/stash/internal/api.File
|
||||||
model: github.com/stashapp/stash/pkg/models.Image
|
|
||||||
fields:
|
|
||||||
title:
|
|
||||||
resolver: true
|
|
||||||
VideoFile:
|
VideoFile:
|
||||||
fields:
|
fields:
|
||||||
# override float fields - #1572
|
# override float fields - #1572
|
||||||
|
|
@ -51,6 +44,11 @@ models:
|
||||||
fieldName: DurationFinite
|
fieldName: DurationFinite
|
||||||
frame_rate:
|
frame_rate:
|
||||||
fieldName: FrameRateFinite
|
fieldName: FrameRateFinite
|
||||||
|
# movie is group under the hood
|
||||||
|
Movie:
|
||||||
|
model: github.com/stashapp/stash/pkg/models.Group
|
||||||
|
MovieFilterType:
|
||||||
|
model: github.com/stashapp/stash/pkg/models.GroupFilterType
|
||||||
# autobind on config causes generation issues
|
# autobind on config causes generation issues
|
||||||
BlobsStorageType:
|
BlobsStorageType:
|
||||||
model: github.com/stashapp/stash/internal/manager/config.BlobsStorageType
|
model: github.com/stashapp/stash/internal/manager/config.BlobsStorageType
|
||||||
|
|
@ -127,9 +125,6 @@ models:
|
||||||
model: github.com/stashapp/stash/internal/identify.FieldStrategy
|
model: github.com/stashapp/stash/internal/identify.FieldStrategy
|
||||||
ScraperSource:
|
ScraperSource:
|
||||||
model: github.com/stashapp/stash/pkg/scraper.Source
|
model: github.com/stashapp/stash/pkg/scraper.Source
|
||||||
# rebind inputs to types
|
|
||||||
StashIDInput:
|
|
||||||
model: github.com/stashapp/stash/pkg/models.StashID
|
|
||||||
IdentifySourceInput:
|
IdentifySourceInput:
|
||||||
model: github.com/stashapp/stash/internal/identify.Source
|
model: github.com/stashapp/stash/internal/identify.Source
|
||||||
IdentifyFieldOptionsInput:
|
IdentifyFieldOptionsInput:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,27 @@ type Query {
|
||||||
findSavedFilter(id: ID!): SavedFilter
|
findSavedFilter(id: ID!): SavedFilter
|
||||||
findSavedFilters(mode: FilterMode): [SavedFilter!]!
|
findSavedFilters(mode: FilterMode): [SavedFilter!]!
|
||||||
findDefaultFilter(mode: FilterMode!): SavedFilter
|
findDefaultFilter(mode: FilterMode!): SavedFilter
|
||||||
|
@deprecated(reason: "default filter now stored in UI config")
|
||||||
|
|
||||||
|
"Find a file by its id or path"
|
||||||
|
findFile(id: ID, path: String): BaseFile!
|
||||||
|
|
||||||
|
"Queries for Files"
|
||||||
|
findFiles(
|
||||||
|
file_filter: FileFilterType
|
||||||
|
filter: FindFilterType
|
||||||
|
ids: [ID!]
|
||||||
|
): FindFilesResultType!
|
||||||
|
|
||||||
|
"Find a file by its id or path"
|
||||||
|
findFolder(id: ID, path: String): Folder!
|
||||||
|
|
||||||
|
"Queries for Files"
|
||||||
|
findFolders(
|
||||||
|
folder_filter: FolderFilterType
|
||||||
|
filter: FindFilterType
|
||||||
|
ids: [ID!]
|
||||||
|
): FindFoldersResultType!
|
||||||
|
|
||||||
"Find a scene by ID or Checksum"
|
"Find a scene by ID or Checksum"
|
||||||
findScene(id: ID, checksum: String): Scene
|
findScene(id: ID, checksum: String): Scene
|
||||||
|
|
@ -44,6 +65,7 @@ type Query {
|
||||||
findSceneMarkers(
|
findSceneMarkers(
|
||||||
scene_marker_filter: SceneMarkerFilterType
|
scene_marker_filter: SceneMarkerFilterType
|
||||||
filter: FindFilterType
|
filter: FindFilterType
|
||||||
|
ids: [ID!]
|
||||||
): FindSceneMarkersResultType!
|
): FindSceneMarkersResultType!
|
||||||
|
|
||||||
findImage(id: ID, checksum: String): Image
|
findImage(id: ID, checksum: String): Image
|
||||||
|
|
@ -76,13 +98,22 @@ type Query {
|
||||||
): FindStudiosResultType!
|
): FindStudiosResultType!
|
||||||
|
|
||||||
"Find a movie by ID"
|
"Find a movie by ID"
|
||||||
findMovie(id: ID!): Movie
|
findMovie(id: ID!): Movie @deprecated(reason: "Use findGroup instead")
|
||||||
"A function which queries Movie objects"
|
"A function which queries Movie objects"
|
||||||
findMovies(
|
findMovies(
|
||||||
movie_filter: MovieFilterType
|
movie_filter: MovieFilterType
|
||||||
filter: FindFilterType
|
filter: FindFilterType
|
||||||
ids: [ID!]
|
ids: [ID!]
|
||||||
): FindMoviesResultType!
|
): FindMoviesResultType! @deprecated(reason: "Use findGroups instead")
|
||||||
|
|
||||||
|
"Find a group by ID"
|
||||||
|
findGroup(id: ID!): Group
|
||||||
|
"A function which queries Group objects"
|
||||||
|
findGroups(
|
||||||
|
group_filter: GroupFilterType
|
||||||
|
filter: FindFilterType
|
||||||
|
ids: [ID!]
|
||||||
|
): FindGroupsResultType!
|
||||||
|
|
||||||
findGallery(id: ID!): Gallery
|
findGallery(id: ID!): Gallery
|
||||||
findGalleries(
|
findGalleries(
|
||||||
|
|
@ -134,6 +165,12 @@ type Query {
|
||||||
input: ScrapeSingleStudioInput!
|
input: ScrapeSingleStudioInput!
|
||||||
): [ScrapedStudio!]!
|
): [ScrapedStudio!]!
|
||||||
|
|
||||||
|
"Scrape for a single tag"
|
||||||
|
scrapeSingleTag(
|
||||||
|
source: ScraperSourceInput!
|
||||||
|
input: ScrapeSingleTagInput!
|
||||||
|
): [ScrapedTag!]!
|
||||||
|
|
||||||
"Scrape for a single performer"
|
"Scrape for a single performer"
|
||||||
scrapeSinglePerformer(
|
scrapeSinglePerformer(
|
||||||
source: ScraperSourceInput!
|
source: ScraperSourceInput!
|
||||||
|
|
@ -155,7 +192,19 @@ type Query {
|
||||||
scrapeSingleMovie(
|
scrapeSingleMovie(
|
||||||
source: ScraperSourceInput!
|
source: ScraperSourceInput!
|
||||||
input: ScrapeSingleMovieInput!
|
input: ScrapeSingleMovieInput!
|
||||||
): [ScrapedMovie!]!
|
): [ScrapedMovie!]! @deprecated(reason: "Use scrapeSingleGroup instead")
|
||||||
|
|
||||||
|
"Scrape for a single group"
|
||||||
|
scrapeSingleGroup(
|
||||||
|
source: ScraperSourceInput!
|
||||||
|
input: ScrapeSingleGroupInput!
|
||||||
|
): [ScrapedGroup!]!
|
||||||
|
|
||||||
|
"Scrape for a single image"
|
||||||
|
scrapeSingleImage(
|
||||||
|
source: ScraperSourceInput!
|
||||||
|
input: ScrapeSingleImageInput!
|
||||||
|
): [ScrapedImage!]!
|
||||||
|
|
||||||
"Scrapes content based on a URL"
|
"Scrapes content based on a URL"
|
||||||
scrapeURL(url: String!, ty: ScrapeContentType!): ScrapedContent
|
scrapeURL(url: String!, ty: ScrapeContentType!): ScrapedContent
|
||||||
|
|
@ -166,8 +215,13 @@ type Query {
|
||||||
scrapeSceneURL(url: String!): ScrapedScene
|
scrapeSceneURL(url: String!): ScrapedScene
|
||||||
"Scrapes a complete gallery record based on a URL"
|
"Scrapes a complete gallery record based on a URL"
|
||||||
scrapeGalleryURL(url: String!): ScrapedGallery
|
scrapeGalleryURL(url: String!): ScrapedGallery
|
||||||
|
"Scrapes a complete image record based on a URL"
|
||||||
|
scrapeImageURL(url: String!): ScrapedImage
|
||||||
"Scrapes a complete movie record based on a URL"
|
"Scrapes a complete movie record based on a URL"
|
||||||
scrapeMovieURL(url: String!): ScrapedMovie
|
scrapeMovieURL(url: String!): ScrapedMovie
|
||||||
|
@deprecated(reason: "Use scrapeGroupURL instead")
|
||||||
|
"Scrapes a complete group record based on a URL"
|
||||||
|
scrapeGroupURL(url: String!): ScrapedGroup
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
"List loaded plugins"
|
"List loaded plugins"
|
||||||
|
|
@ -213,7 +267,7 @@ type Query {
|
||||||
allPerformers: [Performer!]!
|
allPerformers: [Performer!]!
|
||||||
allTags: [Tag!]! @deprecated(reason: "Use findTags instead")
|
allTags: [Tag!]! @deprecated(reason: "Use findTags instead")
|
||||||
allStudios: [Studio!]! @deprecated(reason: "Use findStudios instead")
|
allStudios: [Studio!]! @deprecated(reason: "Use findStudios instead")
|
||||||
allMovies: [Movie!]! @deprecated(reason: "Use findMovies instead")
|
allMovies: [Movie!]! @deprecated(reason: "Use findGroups instead")
|
||||||
|
|
||||||
# Get everything with minimal metadata
|
# Get everything with minimal metadata
|
||||||
|
|
||||||
|
|
@ -257,6 +311,13 @@ type Mutation {
|
||||||
"Sets the resume time point (if provided) and adds the provided duration to the scene's play duration"
|
"Sets the resume time point (if provided) and adds the provided duration to the scene's play duration"
|
||||||
sceneSaveActivity(id: ID!, resume_time: Float, playDuration: Float): Boolean!
|
sceneSaveActivity(id: ID!, resume_time: Float, playDuration: Float): Boolean!
|
||||||
|
|
||||||
|
"Resets the resume time point and play duration"
|
||||||
|
sceneResetActivity(
|
||||||
|
id: ID!
|
||||||
|
reset_resume: Boolean
|
||||||
|
reset_duration: Boolean
|
||||||
|
): Boolean!
|
||||||
|
|
||||||
"Increments the play count for the scene. Returns the new play count value."
|
"Increments the play count for the scene. Returns the new play count value."
|
||||||
sceneIncrementPlayCount(id: ID!): Int!
|
sceneIncrementPlayCount(id: ID!): Int!
|
||||||
@deprecated(reason: "Use sceneAddPlay instead")
|
@deprecated(reason: "Use sceneAddPlay instead")
|
||||||
|
|
@ -273,7 +334,9 @@ type Mutation {
|
||||||
|
|
||||||
sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
|
sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
|
||||||
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
|
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
|
||||||
|
bulkSceneMarkerUpdate(input: BulkSceneMarkerUpdateInput!): [SceneMarker!]
|
||||||
sceneMarkerDestroy(id: ID!): Boolean!
|
sceneMarkerDestroy(id: ID!): Boolean!
|
||||||
|
sceneMarkersDestroy(ids: [ID!]!): Boolean!
|
||||||
|
|
||||||
sceneAssignFile(input: AssignSceneFileInput!): Boolean!
|
sceneAssignFile(input: AssignSceneFileInput!): Boolean!
|
||||||
|
|
||||||
|
|
@ -298,6 +361,8 @@ type Mutation {
|
||||||
|
|
||||||
addGalleryImages(input: GalleryAddInput!): Boolean!
|
addGalleryImages(input: GalleryAddInput!): Boolean!
|
||||||
removeGalleryImages(input: GalleryRemoveInput!): Boolean!
|
removeGalleryImages(input: GalleryRemoveInput!): Boolean!
|
||||||
|
setGalleryCover(input: GallerySetCoverInput!): Boolean!
|
||||||
|
resetGalleryCover(input: GalleryResetCoverInput!): Boolean!
|
||||||
|
|
||||||
galleryChapterCreate(input: GalleryChapterCreateInput!): GalleryChapter
|
galleryChapterCreate(input: GalleryChapterCreateInput!): GalleryChapter
|
||||||
galleryChapterUpdate(input: GalleryChapterUpdateInput!): GalleryChapter
|
galleryChapterUpdate(input: GalleryChapterUpdateInput!): GalleryChapter
|
||||||
|
|
@ -313,18 +378,37 @@ type Mutation {
|
||||||
studioUpdate(input: StudioUpdateInput!): Studio
|
studioUpdate(input: StudioUpdateInput!): Studio
|
||||||
studioDestroy(input: StudioDestroyInput!): Boolean!
|
studioDestroy(input: StudioDestroyInput!): Boolean!
|
||||||
studiosDestroy(ids: [ID!]!): Boolean!
|
studiosDestroy(ids: [ID!]!): Boolean!
|
||||||
|
bulkStudioUpdate(input: BulkStudioUpdateInput!): [Studio!]
|
||||||
|
|
||||||
movieCreate(input: MovieCreateInput!): Movie
|
movieCreate(input: MovieCreateInput!): Movie
|
||||||
|
@deprecated(reason: "Use groupCreate instead")
|
||||||
movieUpdate(input: MovieUpdateInput!): Movie
|
movieUpdate(input: MovieUpdateInput!): Movie
|
||||||
|
@deprecated(reason: "Use groupUpdate instead")
|
||||||
movieDestroy(input: MovieDestroyInput!): Boolean!
|
movieDestroy(input: MovieDestroyInput!): Boolean!
|
||||||
|
@deprecated(reason: "Use groupDestroy instead")
|
||||||
moviesDestroy(ids: [ID!]!): Boolean!
|
moviesDestroy(ids: [ID!]!): Boolean!
|
||||||
|
@deprecated(reason: "Use groupsDestroy instead")
|
||||||
bulkMovieUpdate(input: BulkMovieUpdateInput!): [Movie!]
|
bulkMovieUpdate(input: BulkMovieUpdateInput!): [Movie!]
|
||||||
|
@deprecated(reason: "Use bulkGroupUpdate instead")
|
||||||
|
|
||||||
|
groupCreate(input: GroupCreateInput!): Group
|
||||||
|
groupUpdate(input: GroupUpdateInput!): Group
|
||||||
|
groupDestroy(input: GroupDestroyInput!): Boolean!
|
||||||
|
groupsDestroy(ids: [ID!]!): Boolean!
|
||||||
|
bulkGroupUpdate(input: BulkGroupUpdateInput!): [Group!]
|
||||||
|
|
||||||
|
addGroupSubGroups(input: GroupSubGroupAddInput!): Boolean!
|
||||||
|
removeGroupSubGroups(input: GroupSubGroupRemoveInput!): Boolean!
|
||||||
|
|
||||||
|
"Reorder sub groups within a group. Returns true if successful."
|
||||||
|
reorderSubGroups(input: ReorderSubGroupsInput!): Boolean!
|
||||||
|
|
||||||
tagCreate(input: TagCreateInput!): Tag
|
tagCreate(input: TagCreateInput!): Tag
|
||||||
tagUpdate(input: TagUpdateInput!): Tag
|
tagUpdate(input: TagUpdateInput!): Tag
|
||||||
tagDestroy(input: TagDestroyInput!): Boolean!
|
tagDestroy(input: TagDestroyInput!): Boolean!
|
||||||
tagsDestroy(ids: [ID!]!): Boolean!
|
tagsDestroy(ids: [ID!]!): Boolean!
|
||||||
tagsMerge(input: TagsMergeInput!): Tag
|
tagsMerge(input: TagsMergeInput!): Tag
|
||||||
|
bulkTagUpdate(input: BulkTagUpdateInput!): [Tag!]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Moves the given files to the given destination. Returns true if successful.
|
Moves the given files to the given destination. Returns true if successful.
|
||||||
|
|
@ -344,6 +428,7 @@ type Mutation {
|
||||||
saveFilter(input: SaveFilterInput!): SavedFilter!
|
saveFilter(input: SaveFilterInput!): SavedFilter!
|
||||||
destroySavedFilter(input: DestroyFilterInput!): Boolean!
|
destroySavedFilter(input: DestroyFilterInput!): Boolean!
|
||||||
setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
|
setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
|
||||||
|
@deprecated(reason: "now uses UI config")
|
||||||
|
|
||||||
"Change general configuration options"
|
"Change general configuration options"
|
||||||
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
|
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ input SetupInput {
|
||||||
"Empty to indicate $HOME/.stash/config.yml default"
|
"Empty to indicate $HOME/.stash/config.yml default"
|
||||||
configLocation: String!
|
configLocation: String!
|
||||||
stashes: [StashConfigInput!]!
|
stashes: [StashConfigInput!]!
|
||||||
|
"True if SFW content mode is enabled"
|
||||||
|
sfwContentMode: Boolean
|
||||||
"Empty to indicate default"
|
"Empty to indicate default"
|
||||||
databaseFile: String!
|
databaseFile: String!
|
||||||
"Empty to indicate default"
|
"Empty to indicate default"
|
||||||
|
|
@ -67,6 +69,8 @@ input ConfigGeneralInput {
|
||||||
databasePath: String
|
databasePath: String
|
||||||
"Path to backup directory"
|
"Path to backup directory"
|
||||||
backupDirectoryPath: String
|
backupDirectoryPath: String
|
||||||
|
"Path to trash directory - if set, deleted files will be moved here instead of being permanently deleted"
|
||||||
|
deleteTrashPath: String
|
||||||
"Path to generated files"
|
"Path to generated files"
|
||||||
generatedPath: String
|
generatedPath: String
|
||||||
"Path to import/export files"
|
"Path to import/export files"
|
||||||
|
|
@ -153,6 +157,8 @@ input ConfigGeneralInput {
|
||||||
logLevel: String
|
logLevel: String
|
||||||
"Whether to log http access"
|
"Whether to log http access"
|
||||||
logAccess: Boolean
|
logAccess: Boolean
|
||||||
|
"Maximum log size"
|
||||||
|
logFileMaxSize: Int
|
||||||
"True if galleries should be created from folders with images"
|
"True if galleries should be created from folders with images"
|
||||||
createGalleriesFromFolders: Boolean
|
createGalleriesFromFolders: Boolean
|
||||||
"Regex used to identify images as gallery covers"
|
"Regex used to identify images as gallery covers"
|
||||||
|
|
@ -187,6 +193,8 @@ type ConfigGeneralResult {
|
||||||
databasePath: String!
|
databasePath: String!
|
||||||
"Path to backup directory"
|
"Path to backup directory"
|
||||||
backupDirectoryPath: String!
|
backupDirectoryPath: String!
|
||||||
|
"Path to trash directory - if set, deleted files will be moved here instead of being permanently deleted"
|
||||||
|
deleteTrashPath: String!
|
||||||
"Path to generated files"
|
"Path to generated files"
|
||||||
generatedPath: String!
|
generatedPath: String!
|
||||||
"Path to import/export files"
|
"Path to import/export files"
|
||||||
|
|
@ -277,6 +285,8 @@ type ConfigGeneralResult {
|
||||||
logLevel: String!
|
logLevel: String!
|
||||||
"Whether to log http access"
|
"Whether to log http access"
|
||||||
logAccess: Boolean!
|
logAccess: Boolean!
|
||||||
|
"Maximum log size"
|
||||||
|
logFileMaxSize: Int!
|
||||||
"Array of video file extensions"
|
"Array of video file extensions"
|
||||||
videoExtensions: [String!]!
|
videoExtensions: [String!]!
|
||||||
"Array of image file extensions"
|
"Array of image file extensions"
|
||||||
|
|
@ -329,6 +339,7 @@ input ConfigImageLightboxInput {
|
||||||
resetZoomOnNav: Boolean
|
resetZoomOnNav: Boolean
|
||||||
scrollMode: ImageLightboxScrollMode
|
scrollMode: ImageLightboxScrollMode
|
||||||
scrollAttemptsBeforeChange: Int
|
scrollAttemptsBeforeChange: Int
|
||||||
|
disableAnimation: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigImageLightboxResult {
|
type ConfigImageLightboxResult {
|
||||||
|
|
@ -338,9 +349,13 @@ type ConfigImageLightboxResult {
|
||||||
resetZoomOnNav: Boolean
|
resetZoomOnNav: Boolean
|
||||||
scrollMode: ImageLightboxScrollMode
|
scrollMode: ImageLightboxScrollMode
|
||||||
scrollAttemptsBeforeChange: Int!
|
scrollAttemptsBeforeChange: Int!
|
||||||
|
disableAnimation: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input ConfigInterfaceInput {
|
input ConfigInterfaceInput {
|
||||||
|
"True if SFW content mode is enabled"
|
||||||
|
sfwContentMode: Boolean
|
||||||
|
|
||||||
"Ordered list of items that should be shown in the menu"
|
"Ordered list of items that should be shown in the menu"
|
||||||
menuItems: [String!]
|
menuItems: [String!]
|
||||||
|
|
||||||
|
|
@ -407,6 +422,9 @@ type ConfigDisableDropdownCreate {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigInterfaceResult {
|
type ConfigInterfaceResult {
|
||||||
|
"True if SFW content mode is enabled"
|
||||||
|
sfwContentMode: Boolean!
|
||||||
|
|
||||||
"Ordered list of items that should be shown in the menu"
|
"Ordered list of items that should be shown in the menu"
|
||||||
menuItems: [String!]
|
menuItems: [String!]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,11 @@ type Folder {
|
||||||
id: ID!
|
id: ID!
|
||||||
path: String!
|
path: String!
|
||||||
|
|
||||||
parent_folder_id: ID
|
parent_folder_id: ID @deprecated(reason: "Use parent_folder instead")
|
||||||
zip_file_id: ID
|
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
|
||||||
|
|
||||||
|
parent_folder: Folder
|
||||||
|
zip_file: BasicFile
|
||||||
|
|
||||||
mod_time: Time!
|
mod_time: Time!
|
||||||
|
|
||||||
|
|
@ -21,8 +24,32 @@ interface BaseFile {
|
||||||
path: String!
|
path: String!
|
||||||
basename: String!
|
basename: String!
|
||||||
|
|
||||||
parent_folder_id: ID!
|
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
|
||||||
zip_file_id: ID
|
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
|
||||||
|
|
||||||
|
parent_folder: Folder!
|
||||||
|
zip_file: BasicFile
|
||||||
|
|
||||||
|
mod_time: Time!
|
||||||
|
size: Int64!
|
||||||
|
|
||||||
|
fingerprint(type: String!): String
|
||||||
|
fingerprints: [Fingerprint!]!
|
||||||
|
|
||||||
|
created_at: Time!
|
||||||
|
updated_at: Time!
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasicFile implements BaseFile {
|
||||||
|
id: ID!
|
||||||
|
path: String!
|
||||||
|
basename: String!
|
||||||
|
|
||||||
|
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
|
||||||
|
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
|
||||||
|
|
||||||
|
parent_folder: Folder!
|
||||||
|
zip_file: BasicFile
|
||||||
|
|
||||||
mod_time: Time!
|
mod_time: Time!
|
||||||
size: Int64!
|
size: Int64!
|
||||||
|
|
@ -39,8 +66,11 @@ type VideoFile implements BaseFile {
|
||||||
path: String!
|
path: String!
|
||||||
basename: String!
|
basename: String!
|
||||||
|
|
||||||
parent_folder_id: ID!
|
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
|
||||||
zip_file_id: ID
|
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
|
||||||
|
|
||||||
|
parent_folder: Folder!
|
||||||
|
zip_file: BasicFile
|
||||||
|
|
||||||
mod_time: Time!
|
mod_time: Time!
|
||||||
size: Int64!
|
size: Int64!
|
||||||
|
|
@ -66,8 +96,11 @@ type ImageFile implements BaseFile {
|
||||||
path: String!
|
path: String!
|
||||||
basename: String!
|
basename: String!
|
||||||
|
|
||||||
parent_folder_id: ID!
|
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
|
||||||
zip_file_id: ID
|
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
|
||||||
|
|
||||||
|
parent_folder: Folder!
|
||||||
|
zip_file: BasicFile
|
||||||
|
|
||||||
mod_time: Time!
|
mod_time: Time!
|
||||||
size: Int64!
|
size: Int64!
|
||||||
|
|
@ -75,6 +108,7 @@ type ImageFile implements BaseFile {
|
||||||
fingerprint(type: String!): String
|
fingerprint(type: String!): String
|
||||||
fingerprints: [Fingerprint!]!
|
fingerprints: [Fingerprint!]!
|
||||||
|
|
||||||
|
format: String!
|
||||||
width: Int!
|
width: Int!
|
||||||
height: Int!
|
height: Int!
|
||||||
|
|
||||||
|
|
@ -89,8 +123,11 @@ type GalleryFile implements BaseFile {
|
||||||
path: String!
|
path: String!
|
||||||
basename: String!
|
basename: String!
|
||||||
|
|
||||||
parent_folder_id: ID!
|
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
|
||||||
zip_file_id: ID
|
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
|
||||||
|
|
||||||
|
parent_folder: Folder!
|
||||||
|
zip_file: BasicFile
|
||||||
|
|
||||||
mod_time: Time!
|
mod_time: Time!
|
||||||
size: Int64!
|
size: Int64!
|
||||||
|
|
@ -125,3 +162,22 @@ input FileSetFingerprintsInput {
|
||||||
"only supplied fingerprint types will be modified"
|
"only supplied fingerprint types will be modified"
|
||||||
fingerprints: [SetFingerprintsInput!]!
|
fingerprints: [SetFingerprintsInput!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FindFilesResultType {
|
||||||
|
count: Int!
|
||||||
|
|
||||||
|
"Total megapixels of any image files"
|
||||||
|
megapixels: Float!
|
||||||
|
"Total duration in seconds of any video files"
|
||||||
|
duration: Float!
|
||||||
|
|
||||||
|
"Total file size in bytes"
|
||||||
|
size: Int!
|
||||||
|
|
||||||
|
files: [BaseFile!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type FindFoldersResultType {
|
||||||
|
count: Int!
|
||||||
|
folders: [Folder!]!
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,12 @@ input StashIDCriterionInput {
|
||||||
modifier: CriterionModifier!
|
modifier: CriterionModifier!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CustomFieldCriterionInput {
|
||||||
|
field: String!
|
||||||
|
value: [Any!]
|
||||||
|
modifier: CriterionModifier!
|
||||||
|
}
|
||||||
|
|
||||||
input PerformerFilterType {
|
input PerformerFilterType {
|
||||||
AND: PerformerFilterType
|
AND: PerformerFilterType
|
||||||
OR: PerformerFilterType
|
OR: PerformerFilterType
|
||||||
|
|
@ -162,6 +168,8 @@ input PerformerFilterType {
|
||||||
death_year: IntCriterionInput
|
death_year: IntCriterionInput
|
||||||
"Filter by studios where performer appears in scene/image/gallery"
|
"Filter by studios where performer appears in scene/image/gallery"
|
||||||
studios: HierarchicalMultiCriterionInput
|
studios: HierarchicalMultiCriterionInput
|
||||||
|
"Filter by groups where performer appears in scene"
|
||||||
|
groups: HierarchicalMultiCriterionInput
|
||||||
"Filter by performers where performer appears with another performer in scene/image/gallery"
|
"Filter by performers where performer appears with another performer in scene/image/gallery"
|
||||||
performers: MultiCriterionInput
|
performers: MultiCriterionInput
|
||||||
"Filter by autotag ignore value"
|
"Filter by autotag ignore value"
|
||||||
|
|
@ -170,10 +178,20 @@ input PerformerFilterType {
|
||||||
birthdate: DateCriterionInput
|
birthdate: DateCriterionInput
|
||||||
"Filter by death date"
|
"Filter by death date"
|
||||||
death_date: DateCriterionInput
|
death_date: DateCriterionInput
|
||||||
|
"Filter by related scenes that meet this criteria"
|
||||||
|
scenes_filter: SceneFilterType
|
||||||
|
"Filter by related images that meet this criteria"
|
||||||
|
images_filter: ImageFilterType
|
||||||
|
"Filter by related galleries that meet this criteria"
|
||||||
|
galleries_filter: GalleryFilterType
|
||||||
|
"Filter by related tags that meet this criteria"
|
||||||
|
tags_filter: TagFilterType
|
||||||
"Filter by creation time"
|
"Filter by creation time"
|
||||||
created_at: TimestampCriterionInput
|
created_at: TimestampCriterionInput
|
||||||
"Filter by last update time"
|
"Filter by last update time"
|
||||||
updated_at: TimestampCriterionInput
|
updated_at: TimestampCriterionInput
|
||||||
|
|
||||||
|
custom_fields: [CustomFieldCriterionInput!]
|
||||||
}
|
}
|
||||||
|
|
||||||
input SceneMarkerFilterType {
|
input SceneMarkerFilterType {
|
||||||
|
|
@ -183,6 +201,10 @@ input SceneMarkerFilterType {
|
||||||
scene_tags: HierarchicalMultiCriterionInput
|
scene_tags: HierarchicalMultiCriterionInput
|
||||||
"Filter to only include scene markers with these performers"
|
"Filter to only include scene markers with these performers"
|
||||||
performers: MultiCriterionInput
|
performers: MultiCriterionInput
|
||||||
|
"Filter to only include scene markers from these scenes"
|
||||||
|
scenes: MultiCriterionInput
|
||||||
|
"Filter by duration (in seconds)"
|
||||||
|
duration: FloatCriterionInput
|
||||||
"Filter by creation time"
|
"Filter by creation time"
|
||||||
created_at: TimestampCriterionInput
|
created_at: TimestampCriterionInput
|
||||||
"Filter by last update time"
|
"Filter by last update time"
|
||||||
|
|
@ -193,6 +215,8 @@ input SceneMarkerFilterType {
|
||||||
scene_created_at: TimestampCriterionInput
|
scene_created_at: TimestampCriterionInput
|
||||||
"Filter by lscene ast update time"
|
"Filter by lscene ast update time"
|
||||||
scene_updated_at: TimestampCriterionInput
|
scene_updated_at: TimestampCriterionInput
|
||||||
|
"Filter by related scenes that meet this criteria"
|
||||||
|
scene_filter: SceneFilterType
|
||||||
}
|
}
|
||||||
|
|
||||||
input SceneFilterType {
|
input SceneFilterType {
|
||||||
|
|
@ -247,7 +271,9 @@ input SceneFilterType {
|
||||||
"Filter to only include scenes with this studio"
|
"Filter to only include scenes with this studio"
|
||||||
studios: HierarchicalMultiCriterionInput
|
studios: HierarchicalMultiCriterionInput
|
||||||
"Filter to only include scenes with this movie"
|
"Filter to only include scenes with this movie"
|
||||||
movies: MultiCriterionInput
|
movies: MultiCriterionInput @deprecated(reason: "use groups instead")
|
||||||
|
"Filter to only include scenes with this group"
|
||||||
|
groups: HierarchicalMultiCriterionInput
|
||||||
"Filter to only include scenes with this gallery"
|
"Filter to only include scenes with this gallery"
|
||||||
galleries: MultiCriterionInput
|
galleries: MultiCriterionInput
|
||||||
"Filter to only include scenes with these tags"
|
"Filter to only include scenes with these tags"
|
||||||
|
|
@ -288,9 +314,31 @@ input SceneFilterType {
|
||||||
created_at: TimestampCriterionInput
|
created_at: TimestampCriterionInput
|
||||||
"Filter by last update time"
|
"Filter by last update time"
|
||||||
updated_at: TimestampCriterionInput
|
updated_at: TimestampCriterionInput
|
||||||
|
|
||||||
|
"Filter by related galleries that meet this criteria"
|
||||||
|
galleries_filter: GalleryFilterType
|
||||||
|
"Filter by related performers that meet this criteria"
|
||||||
|
performers_filter: PerformerFilterType
|
||||||
|
"Filter by related studios that meet this criteria"
|
||||||
|
studios_filter: StudioFilterType
|
||||||
|
"Filter by related tags that meet this criteria"
|
||||||
|
tags_filter: TagFilterType
|
||||||
|
"Filter by related movies that meet this criteria"
|
||||||
|
movies_filter: MovieFilterType
|
||||||
|
@deprecated(reason: "use groups_filter instead")
|
||||||
|
"Filter by related groups that meet this criteria"
|
||||||
|
groups_filter: GroupFilterType
|
||||||
|
"Filter by related markers that meet this criteria"
|
||||||
|
markers_filter: SceneMarkerFilterType
|
||||||
|
"Filter by related files that meet this criteria"
|
||||||
|
files_filter: FileFilterType
|
||||||
}
|
}
|
||||||
|
|
||||||
input MovieFilterType {
|
input MovieFilterType {
|
||||||
|
AND: MovieFilterType
|
||||||
|
OR: MovieFilterType
|
||||||
|
NOT: MovieFilterType
|
||||||
|
|
||||||
name: StringCriterionInput
|
name: StringCriterionInput
|
||||||
director: StringCriterionInput
|
director: StringCriterionInput
|
||||||
synopsis: StringCriterionInput
|
synopsis: StringCriterionInput
|
||||||
|
|
@ -307,12 +355,70 @@ input MovieFilterType {
|
||||||
url: StringCriterionInput
|
url: StringCriterionInput
|
||||||
"Filter to only include movies where performer appears in a scene"
|
"Filter to only include movies where performer appears in a scene"
|
||||||
performers: MultiCriterionInput
|
performers: MultiCriterionInput
|
||||||
|
"Filter to only include movies with these tags"
|
||||||
|
tags: HierarchicalMultiCriterionInput
|
||||||
|
"Filter by tag count"
|
||||||
|
tag_count: IntCriterionInput
|
||||||
"Filter by date"
|
"Filter by date"
|
||||||
date: DateCriterionInput
|
date: DateCriterionInput
|
||||||
"Filter by creation time"
|
"Filter by creation time"
|
||||||
created_at: TimestampCriterionInput
|
created_at: TimestampCriterionInput
|
||||||
"Filter by last update time"
|
"Filter by last update time"
|
||||||
updated_at: TimestampCriterionInput
|
updated_at: TimestampCriterionInput
|
||||||
|
|
||||||
|
"Filter by related scenes that meet this criteria"
|
||||||
|
scenes_filter: SceneFilterType
|
||||||
|
"Filter by related studios that meet this criteria"
|
||||||
|
studios_filter: StudioFilterType
|
||||||
|
}
|
||||||
|
|
||||||
|
input GroupFilterType {
|
||||||
|
AND: GroupFilterType
|
||||||
|
OR: GroupFilterType
|
||||||
|
NOT: GroupFilterType
|
||||||
|
|
||||||
|
name: StringCriterionInput
|
||||||
|
director: StringCriterionInput
|
||||||
|
synopsis: StringCriterionInput
|
||||||
|
|
||||||
|
"Filter by duration (in seconds)"
|
||||||
|
duration: IntCriterionInput
|
||||||
|
# rating expressed as 1-100
|
||||||
|
rating100: IntCriterionInput
|
||||||
|
"Filter to only include groups with this studio"
|
||||||
|
studios: HierarchicalMultiCriterionInput
|
||||||
|
"Filter to only include groups missing this property"
|
||||||
|
is_missing: String
|
||||||
|
"Filter by url"
|
||||||
|
url: StringCriterionInput
|
||||||
|
"Filter to only include groups where performer appears in a scene"
|
||||||
|
performers: MultiCriterionInput
|
||||||
|
"Filter to only include groups with these tags"
|
||||||
|
tags: HierarchicalMultiCriterionInput
|
||||||
|
"Filter by tag count"
|
||||||
|
tag_count: IntCriterionInput
|
||||||
|
"Filter by date"
|
||||||
|
date: DateCriterionInput
|
||||||
|
"Filter by creation time"
|
||||||
|
created_at: TimestampCriterionInput
|
||||||
|
"Filter by last update time"
|
||||||
|
updated_at: TimestampCriterionInput
|
||||||
|
"Filter by o-counter"
|
||||||
|
o_counter: IntCriterionInput
|
||||||
|
|
||||||
|
"Filter by containing groups"
|
||||||
|
containing_groups: HierarchicalMultiCriterionInput
|
||||||
|
"Filter by sub groups"
|
||||||
|
sub_groups: HierarchicalMultiCriterionInput
|
||||||
|
"Filter by number of containing groups the group has"
|
||||||
|
containing_group_count: IntCriterionInput
|
||||||
|
"Filter by number of sub-groups the group has"
|
||||||
|
sub_group_count: IntCriterionInput
|
||||||
|
|
||||||
|
"Filter by related scenes that meet this criteria"
|
||||||
|
scenes_filter: SceneFilterType
|
||||||
|
"Filter by related studios that meet this criteria"
|
||||||
|
studios_filter: StudioFilterType
|
||||||
}
|
}
|
||||||
|
|
||||||
input StudioFilterType {
|
input StudioFilterType {
|
||||||
|
|
@ -326,6 +432,8 @@ input StudioFilterType {
|
||||||
parents: MultiCriterionInput
|
parents: MultiCriterionInput
|
||||||
"Filter by StashID"
|
"Filter by StashID"
|
||||||
stash_id_endpoint: StashIDCriterionInput
|
stash_id_endpoint: StashIDCriterionInput
|
||||||
|
"Filter to only include studios with these tags"
|
||||||
|
tags: HierarchicalMultiCriterionInput
|
||||||
"Filter to only include studios missing this property"
|
"Filter to only include studios missing this property"
|
||||||
is_missing: String
|
is_missing: String
|
||||||
# rating expressed as 1-100
|
# rating expressed as 1-100
|
||||||
|
|
@ -338,6 +446,8 @@ input StudioFilterType {
|
||||||
image_count: IntCriterionInput
|
image_count: IntCriterionInput
|
||||||
"Filter by gallery count"
|
"Filter by gallery count"
|
||||||
gallery_count: IntCriterionInput
|
gallery_count: IntCriterionInput
|
||||||
|
"Filter by tag count"
|
||||||
|
tag_count: IntCriterionInput
|
||||||
"Filter by url"
|
"Filter by url"
|
||||||
url: StringCriterionInput
|
url: StringCriterionInput
|
||||||
"Filter by studio aliases"
|
"Filter by studio aliases"
|
||||||
|
|
@ -346,6 +456,12 @@ input StudioFilterType {
|
||||||
child_count: IntCriterionInput
|
child_count: IntCriterionInput
|
||||||
"Filter by autotag ignore value"
|
"Filter by autotag ignore value"
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
|
"Filter by related scenes that meet this criteria"
|
||||||
|
scenes_filter: SceneFilterType
|
||||||
|
"Filter by related images that meet this criteria"
|
||||||
|
images_filter: ImageFilterType
|
||||||
|
"Filter by related galleries that meet this criteria"
|
||||||
|
galleries_filter: GalleryFilterType
|
||||||
"Filter by creation time"
|
"Filter by creation time"
|
||||||
created_at: TimestampCriterionInput
|
created_at: TimestampCriterionInput
|
||||||
"Filter by last update time"
|
"Filter by last update time"
|
||||||
|
|
@ -411,6 +527,21 @@ input GalleryFilterType {
|
||||||
code: StringCriterionInput
|
code: StringCriterionInput
|
||||||
"Filter by photographer"
|
"Filter by photographer"
|
||||||
photographer: StringCriterionInput
|
photographer: StringCriterionInput
|
||||||
|
|
||||||
|
"Filter by related scenes that meet this criteria"
|
||||||
|
scenes_filter: SceneFilterType
|
||||||
|
"Filter by related images that meet this criteria"
|
||||||
|
images_filter: ImageFilterType
|
||||||
|
"Filter by related performers that meet this criteria"
|
||||||
|
performers_filter: PerformerFilterType
|
||||||
|
"Filter by related studios that meet this criteria"
|
||||||
|
studios_filter: StudioFilterType
|
||||||
|
"Filter by related tags that meet this criteria"
|
||||||
|
tags_filter: TagFilterType
|
||||||
|
"Filter by related files that meet this criteria"
|
||||||
|
files_filter: FileFilterType
|
||||||
|
"Filter by related folders that meet this criteria"
|
||||||
|
folders_filter: FolderFilterType
|
||||||
}
|
}
|
||||||
|
|
||||||
input TagFilterType {
|
input TagFilterType {
|
||||||
|
|
@ -421,6 +552,9 @@ input TagFilterType {
|
||||||
"Filter by tag name"
|
"Filter by tag name"
|
||||||
name: StringCriterionInput
|
name: StringCriterionInput
|
||||||
|
|
||||||
|
"Filter by tag sort_name"
|
||||||
|
sort_name: StringCriterionInput
|
||||||
|
|
||||||
"Filter by tag aliases"
|
"Filter by tag aliases"
|
||||||
aliases: StringCriterionInput
|
aliases: StringCriterionInput
|
||||||
|
|
||||||
|
|
@ -445,6 +579,15 @@ input TagFilterType {
|
||||||
"Filter by number of performers with this tag"
|
"Filter by number of performers with this tag"
|
||||||
performer_count: IntCriterionInput
|
performer_count: IntCriterionInput
|
||||||
|
|
||||||
|
"Filter by number of studios with this tag"
|
||||||
|
studio_count: IntCriterionInput
|
||||||
|
|
||||||
|
"Filter by number of movies with this tag"
|
||||||
|
movie_count: IntCriterionInput
|
||||||
|
|
||||||
|
"Filter by number of group with this tag"
|
||||||
|
group_count: IntCriterionInput
|
||||||
|
|
||||||
"Filter by number of markers with this tag"
|
"Filter by number of markers with this tag"
|
||||||
marker_count: IntCriterionInput
|
marker_count: IntCriterionInput
|
||||||
|
|
||||||
|
|
@ -463,6 +606,13 @@ input TagFilterType {
|
||||||
"Filter by autotag ignore value"
|
"Filter by autotag ignore value"
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
|
|
||||||
|
"Filter by related scenes that meet this criteria"
|
||||||
|
scenes_filter: SceneFilterType
|
||||||
|
"Filter by related images that meet this criteria"
|
||||||
|
images_filter: ImageFilterType
|
||||||
|
"Filter by related galleries that meet this criteria"
|
||||||
|
galleries_filter: GalleryFilterType
|
||||||
|
|
||||||
"Filter by creation time"
|
"Filter by creation time"
|
||||||
created_at: TimestampCriterionInput
|
created_at: TimestampCriterionInput
|
||||||
|
|
||||||
|
|
@ -528,6 +678,115 @@ input ImageFilterType {
|
||||||
code: StringCriterionInput
|
code: StringCriterionInput
|
||||||
"Filter by photographer"
|
"Filter by photographer"
|
||||||
photographer: StringCriterionInput
|
photographer: StringCriterionInput
|
||||||
|
|
||||||
|
"Filter by related galleries that meet this criteria"
|
||||||
|
galleries_filter: GalleryFilterType
|
||||||
|
"Filter by related performers that meet this criteria"
|
||||||
|
performers_filter: PerformerFilterType
|
||||||
|
"Filter by related studios that meet this criteria"
|
||||||
|
studios_filter: StudioFilterType
|
||||||
|
"Filter by related tags that meet this criteria"
|
||||||
|
tags_filter: TagFilterType
|
||||||
|
"Filter by related files that meet this criteria"
|
||||||
|
files_filter: FileFilterType
|
||||||
|
}
|
||||||
|
|
||||||
|
input FileFilterType {
|
||||||
|
AND: FileFilterType
|
||||||
|
OR: FileFilterType
|
||||||
|
NOT: FileFilterType
|
||||||
|
|
||||||
|
path: StringCriterionInput
|
||||||
|
basename: StringCriterionInput
|
||||||
|
dir: StringCriterionInput
|
||||||
|
|
||||||
|
parent_folder: HierarchicalMultiCriterionInput
|
||||||
|
zip_file: MultiCriterionInput
|
||||||
|
|
||||||
|
"Filter by modification time"
|
||||||
|
mod_time: TimestampCriterionInput
|
||||||
|
|
||||||
|
"Filter files that have an exact match available"
|
||||||
|
duplicated: PHashDuplicationCriterionInput
|
||||||
|
|
||||||
|
"find files based on hash"
|
||||||
|
hashes: [FingerprintFilterInput!]
|
||||||
|
|
||||||
|
video_file_filter: VideoFileFilterInput
|
||||||
|
image_file_filter: ImageFileFilterInput
|
||||||
|
|
||||||
|
scene_count: IntCriterionInput
|
||||||
|
image_count: IntCriterionInput
|
||||||
|
gallery_count: IntCriterionInput
|
||||||
|
|
||||||
|
"Filter by related scenes that meet this criteria"
|
||||||
|
scenes_filter: SceneFilterType
|
||||||
|
"Filter by related images that meet this criteria"
|
||||||
|
images_filter: ImageFilterType
|
||||||
|
"Filter by related galleries that meet this criteria"
|
||||||
|
galleries_filter: GalleryFilterType
|
||||||
|
|
||||||
|
"Filter by creation time"
|
||||||
|
created_at: TimestampCriterionInput
|
||||||
|
"Filter by last update time"
|
||||||
|
updated_at: TimestampCriterionInput
|
||||||
|
}
|
||||||
|
|
||||||
|
input FolderFilterType {
|
||||||
|
AND: FolderFilterType
|
||||||
|
OR: FolderFilterType
|
||||||
|
NOT: FolderFilterType
|
||||||
|
|
||||||
|
path: StringCriterionInput
|
||||||
|
|
||||||
|
parent_folder: HierarchicalMultiCriterionInput
|
||||||
|
zip_file: MultiCriterionInput
|
||||||
|
|
||||||
|
"Filter by modification time"
|
||||||
|
mod_time: TimestampCriterionInput
|
||||||
|
|
||||||
|
gallery_count: IntCriterionInput
|
||||||
|
|
||||||
|
"Filter by files that meet this criteria"
|
||||||
|
files_filter: FileFilterType
|
||||||
|
"Filter by related galleries that meet this criteria"
|
||||||
|
galleries_filter: GalleryFilterType
|
||||||
|
|
||||||
|
"Filter by creation time"
|
||||||
|
created_at: TimestampCriterionInput
|
||||||
|
"Filter by last update time"
|
||||||
|
updated_at: TimestampCriterionInput
|
||||||
|
}
|
||||||
|
|
||||||
|
input VideoFileFilterInput {
|
||||||
|
resolution: ResolutionCriterionInput
|
||||||
|
orientation: OrientationCriterionInput
|
||||||
|
framerate: IntCriterionInput
|
||||||
|
bitrate: IntCriterionInput
|
||||||
|
format: StringCriterionInput
|
||||||
|
video_codec: StringCriterionInput
|
||||||
|
audio_codec: StringCriterionInput
|
||||||
|
|
||||||
|
"in seconds"
|
||||||
|
duration: IntCriterionInput
|
||||||
|
|
||||||
|
captions: StringCriterionInput
|
||||||
|
|
||||||
|
interactive: Boolean
|
||||||
|
interactive_speed: IntCriterionInput
|
||||||
|
}
|
||||||
|
|
||||||
|
input ImageFileFilterInput {
|
||||||
|
format: StringCriterionInput
|
||||||
|
resolution: ResolutionCriterionInput
|
||||||
|
orientation: OrientationCriterionInput
|
||||||
|
}
|
||||||
|
|
||||||
|
input FingerprintFilterInput {
|
||||||
|
type: String!
|
||||||
|
value: String!
|
||||||
|
"Hamming distance - defaults to 0"
|
||||||
|
distance: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CriterionModifier {
|
enum CriterionModifier {
|
||||||
|
|
@ -623,6 +882,7 @@ enum FilterMode {
|
||||||
GALLERIES
|
GALLERIES
|
||||||
SCENE_MARKERS
|
SCENE_MARKERS
|
||||||
MOVIES
|
MOVIES
|
||||||
|
GROUPS
|
||||||
TAGS
|
TAGS
|
||||||
IMAGES
|
IMAGES
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
type GalleryPathsType {
|
||||||
|
cover: String!
|
||||||
|
preview: String! # Resolver
|
||||||
|
}
|
||||||
|
|
||||||
"Gallery type"
|
"Gallery type"
|
||||||
type Gallery {
|
type Gallery {
|
||||||
id: ID!
|
id: ID!
|
||||||
|
|
@ -25,6 +30,9 @@ type Gallery {
|
||||||
performers: [Performer!]!
|
performers: [Performer!]!
|
||||||
|
|
||||||
cover: Image
|
cover: Image
|
||||||
|
|
||||||
|
paths: GalleryPathsType! # Resolver
|
||||||
|
image(index: Int!): Image!
|
||||||
}
|
}
|
||||||
|
|
||||||
input GalleryCreateInput {
|
input GalleryCreateInput {
|
||||||
|
|
@ -108,3 +116,12 @@ input GalleryRemoveInput {
|
||||||
gallery_id: ID!
|
gallery_id: ID!
|
||||||
image_ids: [ID!]!
|
image_ids: [ID!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input GallerySetCoverInput {
|
||||||
|
gallery_id: ID!
|
||||||
|
cover_image_id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
input GalleryResetCoverInput {
|
||||||
|
gallery_id: ID!
|
||||||
|
}
|
||||||
|
|
|
||||||
139
graphql/schema/types/group.graphql
Normal file
139
graphql/schema/types/group.graphql
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
"GroupDescription represents a relationship to a group with a description of the relationship"
|
||||||
|
type GroupDescription {
|
||||||
|
group: Group!
|
||||||
|
description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
aliases: String
|
||||||
|
"Duration in seconds"
|
||||||
|
duration: Int
|
||||||
|
date: String
|
||||||
|
# rating expressed as 1-100
|
||||||
|
rating100: Int
|
||||||
|
studio: Studio
|
||||||
|
director: String
|
||||||
|
synopsis: String
|
||||||
|
urls: [String!]!
|
||||||
|
tags: [Tag!]!
|
||||||
|
created_at: Time!
|
||||||
|
updated_at: Time!
|
||||||
|
|
||||||
|
containing_groups: [GroupDescription!]!
|
||||||
|
sub_groups: [GroupDescription!]!
|
||||||
|
|
||||||
|
front_image_path: String # Resolver
|
||||||
|
back_image_path: String # Resolver
|
||||||
|
scene_count(depth: Int): Int! # Resolver
|
||||||
|
performer_count(depth: Int): Int! # Resolver
|
||||||
|
sub_group_count(depth: Int): Int! # Resolver
|
||||||
|
scenes: [Scene!]!
|
||||||
|
o_counter: Int # Resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
input GroupDescriptionInput {
|
||||||
|
group_id: ID!
|
||||||
|
description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input GroupCreateInput {
|
||||||
|
name: String!
|
||||||
|
aliases: String
|
||||||
|
"Duration in seconds"
|
||||||
|
duration: Int
|
||||||
|
date: String
|
||||||
|
# rating expressed as 1-100
|
||||||
|
rating100: Int
|
||||||
|
studio_id: ID
|
||||||
|
director: String
|
||||||
|
synopsis: String
|
||||||
|
urls: [String!]
|
||||||
|
tag_ids: [ID!]
|
||||||
|
|
||||||
|
containing_groups: [GroupDescriptionInput!]
|
||||||
|
sub_groups: [GroupDescriptionInput!]
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
input GroupUpdateInput {
|
||||||
|
id: ID!
|
||||||
|
name: String
|
||||||
|
aliases: String
|
||||||
|
duration: Int
|
||||||
|
date: String
|
||||||
|
# rating expressed as 1-100
|
||||||
|
rating100: Int
|
||||||
|
studio_id: ID
|
||||||
|
director: String
|
||||||
|
synopsis: String
|
||||||
|
urls: [String!]
|
||||||
|
tag_ids: [ID!]
|
||||||
|
|
||||||
|
containing_groups: [GroupDescriptionInput!]
|
||||||
|
sub_groups: [GroupDescriptionInput!]
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
input BulkUpdateGroupDescriptionsInput {
|
||||||
|
groups: [GroupDescriptionInput!]!
|
||||||
|
mode: BulkUpdateIdMode!
|
||||||
|
}
|
||||||
|
|
||||||
|
input BulkGroupUpdateInput {
|
||||||
|
clientMutationId: String
|
||||||
|
ids: [ID!]
|
||||||
|
# rating expressed as 1-100
|
||||||
|
rating100: Int
|
||||||
|
studio_id: ID
|
||||||
|
director: String
|
||||||
|
urls: BulkUpdateStrings
|
||||||
|
tag_ids: BulkUpdateIds
|
||||||
|
|
||||||
|
containing_groups: BulkUpdateGroupDescriptionsInput
|
||||||
|
sub_groups: BulkUpdateGroupDescriptionsInput
|
||||||
|
}
|
||||||
|
|
||||||
|
input GroupDestroyInput {
|
||||||
|
id: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ReorderSubGroupsInput {
|
||||||
|
"ID of the group to reorder sub groups for"
|
||||||
|
group_id: ID!
|
||||||
|
"""
|
||||||
|
IDs of the sub groups to reorder. These must be a subset of the current sub groups.
|
||||||
|
Sub groups will be inserted in this order at the insert_index
|
||||||
|
"""
|
||||||
|
sub_group_ids: [ID!]!
|
||||||
|
"The sub-group ID at which to insert the sub groups"
|
||||||
|
insert_at_id: ID!
|
||||||
|
"If true, the sub groups will be inserted after the insert_index, otherwise they will be inserted before"
|
||||||
|
insert_after: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type FindGroupsResultType {
|
||||||
|
count: Int!
|
||||||
|
groups: [Group!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input GroupSubGroupAddInput {
|
||||||
|
containing_group_id: ID!
|
||||||
|
sub_groups: [GroupDescriptionInput!]!
|
||||||
|
"The index at which to insert the sub groups. If not provided, the sub groups will be appended to the end"
|
||||||
|
insert_index: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
input GroupSubGroupRemoveInput {
|
||||||
|
containing_group_id: ID!
|
||||||
|
sub_group_ids: [ID!]!
|
||||||
|
}
|
||||||
|
|
@ -284,7 +284,8 @@ input ExportObjectsInput {
|
||||||
studios: ExportObjectTypeInput
|
studios: ExportObjectTypeInput
|
||||||
performers: ExportObjectTypeInput
|
performers: ExportObjectTypeInput
|
||||||
tags: ExportObjectTypeInput
|
tags: ExportObjectTypeInput
|
||||||
movies: ExportObjectTypeInput
|
groups: ExportObjectTypeInput
|
||||||
|
movies: ExportObjectTypeInput @deprecated(reason: "Use groups instead")
|
||||||
galleries: ExportObjectTypeInput
|
galleries: ExportObjectTypeInput
|
||||||
includeDependencies: Boolean
|
includeDependencies: Boolean
|
||||||
}
|
}
|
||||||
|
|
@ -337,3 +338,12 @@ type SystemStatus {
|
||||||
input MigrateInput {
|
input MigrateInput {
|
||||||
backupPath: String!
|
backupPath: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CustomFieldsInput {
|
||||||
|
"If populated, the entire custom fields map will be replaced with this value"
|
||||||
|
full: Map
|
||||||
|
"If populated, only the keys in this map will be updated"
|
||||||
|
partial: Map
|
||||||
|
"Remove any keys in this list"
|
||||||
|
remove: [String!]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,15 @@ type Movie {
|
||||||
studio: Studio
|
studio: Studio
|
||||||
director: String
|
director: String
|
||||||
synopsis: String
|
synopsis: String
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]!
|
||||||
|
tags: [Tag!]!
|
||||||
created_at: Time!
|
created_at: Time!
|
||||||
updated_at: Time!
|
updated_at: Time!
|
||||||
|
|
||||||
front_image_path: String # Resolver
|
front_image_path: String # Resolver
|
||||||
back_image_path: String # Resolver
|
back_image_path: String # Resolver
|
||||||
scene_count: Int! # Resolver
|
scene_count(depth: Int): Int! # Resolver
|
||||||
scenes: [Scene!]!
|
scenes: [Scene!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,7 +33,9 @@ input MovieCreateInput {
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
director: String
|
director: String
|
||||||
synopsis: String
|
synopsis: String
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]
|
||||||
|
tag_ids: [ID!]
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
front_image: String
|
front_image: String
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
|
|
@ -49,7 +53,9 @@ input MovieUpdateInput {
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
director: String
|
director: String
|
||||||
synopsis: String
|
synopsis: String
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]
|
||||||
|
tag_ids: [ID!]
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
front_image: String
|
front_image: String
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
|
|
@ -63,6 +69,8 @@ input BulkMovieUpdateInput {
|
||||||
rating100: Int
|
rating100: Int
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
director: String
|
director: String
|
||||||
|
urls: BulkUpdateStrings
|
||||||
|
tag_ids: BulkUpdateIds
|
||||||
}
|
}
|
||||||
|
|
||||||
input MovieDestroyInput {
|
input MovieDestroyInput {
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,11 @@ type Performer {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
disambiguation: String
|
disambiguation: String
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]
|
||||||
gender: GenderEnum
|
gender: GenderEnum
|
||||||
twitter: String
|
twitter: String @deprecated(reason: "Use urls")
|
||||||
instagram: String
|
instagram: String @deprecated(reason: "Use urls")
|
||||||
birthdate: String
|
birthdate: String
|
||||||
ethnicity: String
|
ethnicity: String
|
||||||
country: String
|
country: String
|
||||||
|
|
@ -41,7 +42,8 @@ type Performer {
|
||||||
scene_count: Int! # Resolver
|
scene_count: Int! # Resolver
|
||||||
image_count: Int! # Resolver
|
image_count: Int! # Resolver
|
||||||
gallery_count: Int! # Resolver
|
gallery_count: Int! # Resolver
|
||||||
movie_count: Int! # Resolver
|
group_count: Int! # Resolver
|
||||||
|
movie_count: Int! @deprecated(reason: "use group_count instead") # Resolver
|
||||||
performer_count: Int! # Resolver
|
performer_count: Int! # Resolver
|
||||||
o_counter: Int # Resolver
|
o_counter: Int # Resolver
|
||||||
scenes: [Scene!]!
|
scenes: [Scene!]!
|
||||||
|
|
@ -54,13 +56,17 @@ type Performer {
|
||||||
weight: Int
|
weight: Int
|
||||||
created_at: Time!
|
created_at: Time!
|
||||||
updated_at: Time!
|
updated_at: Time!
|
||||||
movies: [Movie!]!
|
groups: [Group!]!
|
||||||
|
movies: [Movie!]! @deprecated(reason: "use groups instead")
|
||||||
|
|
||||||
|
custom_fields: Map!
|
||||||
}
|
}
|
||||||
|
|
||||||
input PerformerCreateInput {
|
input PerformerCreateInput {
|
||||||
name: String!
|
name: String!
|
||||||
disambiguation: String
|
disambiguation: String
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]
|
||||||
gender: GenderEnum
|
gender: GenderEnum
|
||||||
birthdate: String
|
birthdate: String
|
||||||
ethnicity: String
|
ethnicity: String
|
||||||
|
|
@ -75,8 +81,8 @@ input PerformerCreateInput {
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
alias_list: [String!]
|
alias_list: [String!]
|
||||||
twitter: String
|
twitter: String @deprecated(reason: "Use urls")
|
||||||
instagram: String
|
instagram: String @deprecated(reason: "Use urls")
|
||||||
favorite: Boolean
|
favorite: Boolean
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
|
|
@ -89,13 +95,16 @@ input PerformerCreateInput {
|
||||||
hair_color: String
|
hair_color: String
|
||||||
weight: Int
|
weight: Int
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
|
|
||||||
|
custom_fields: Map
|
||||||
}
|
}
|
||||||
|
|
||||||
input PerformerUpdateInput {
|
input PerformerUpdateInput {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
disambiguation: String
|
disambiguation: String
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]
|
||||||
gender: GenderEnum
|
gender: GenderEnum
|
||||||
birthdate: String
|
birthdate: String
|
||||||
ethnicity: String
|
ethnicity: String
|
||||||
|
|
@ -110,8 +119,8 @@ input PerformerUpdateInput {
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
alias_list: [String!]
|
alias_list: [String!]
|
||||||
twitter: String
|
twitter: String @deprecated(reason: "Use urls")
|
||||||
instagram: String
|
instagram: String @deprecated(reason: "Use urls")
|
||||||
favorite: Boolean
|
favorite: Boolean
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
|
|
@ -124,6 +133,8 @@ input PerformerUpdateInput {
|
||||||
hair_color: String
|
hair_color: String
|
||||||
weight: Int
|
weight: Int
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
|
|
||||||
|
custom_fields: CustomFieldsInput
|
||||||
}
|
}
|
||||||
|
|
||||||
input BulkUpdateStrings {
|
input BulkUpdateStrings {
|
||||||
|
|
@ -135,7 +146,8 @@ input BulkPerformerUpdateInput {
|
||||||
clientMutationId: String
|
clientMutationId: String
|
||||||
ids: [ID!]
|
ids: [ID!]
|
||||||
disambiguation: String
|
disambiguation: String
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: BulkUpdateStrings
|
||||||
gender: GenderEnum
|
gender: GenderEnum
|
||||||
birthdate: String
|
birthdate: String
|
||||||
ethnicity: String
|
ethnicity: String
|
||||||
|
|
@ -150,8 +162,8 @@ input BulkPerformerUpdateInput {
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
alias_list: BulkUpdateStrings
|
alias_list: BulkUpdateStrings
|
||||||
twitter: String
|
twitter: String @deprecated(reason: "Use urls")
|
||||||
instagram: String
|
instagram: String @deprecated(reason: "Use urls")
|
||||||
favorite: Boolean
|
favorite: Boolean
|
||||||
tag_ids: BulkUpdateIds
|
tag_ids: BulkUpdateIds
|
||||||
# rating expressed as 1-100
|
# rating expressed as 1-100
|
||||||
|
|
@ -161,6 +173,8 @@ input BulkPerformerUpdateInput {
|
||||||
hair_color: String
|
hair_color: String
|
||||||
weight: Int
|
weight: Int
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
|
|
||||||
|
custom_fields: CustomFieldsInput
|
||||||
}
|
}
|
||||||
|
|
||||||
input PerformerDestroyInput {
|
input PerformerDestroyInput {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ type SceneMarker {
|
||||||
id: ID!
|
id: ID!
|
||||||
scene: Scene!
|
scene: Scene!
|
||||||
title: String!
|
title: String!
|
||||||
|
"The required start time of the marker (in seconds). Supports decimals."
|
||||||
seconds: Float!
|
seconds: Float!
|
||||||
|
"The optional end time of the marker (in seconds). Supports decimals."
|
||||||
|
end_seconds: Float
|
||||||
primary_tag: Tag!
|
primary_tag: Tag!
|
||||||
tags: [Tag!]!
|
tags: [Tag!]!
|
||||||
created_at: Time!
|
created_at: Time!
|
||||||
|
|
@ -18,7 +21,10 @@ type SceneMarker {
|
||||||
|
|
||||||
input SceneMarkerCreateInput {
|
input SceneMarkerCreateInput {
|
||||||
title: String!
|
title: String!
|
||||||
|
"The required start time of the marker (in seconds). Supports decimals."
|
||||||
seconds: Float!
|
seconds: Float!
|
||||||
|
"The optional end time of the marker (in seconds). Supports decimals."
|
||||||
|
end_seconds: Float
|
||||||
scene_id: ID!
|
scene_id: ID!
|
||||||
primary_tag_id: ID!
|
primary_tag_id: ID!
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
|
|
@ -27,12 +33,22 @@ input SceneMarkerCreateInput {
|
||||||
input SceneMarkerUpdateInput {
|
input SceneMarkerUpdateInput {
|
||||||
id: ID!
|
id: ID!
|
||||||
title: String
|
title: String
|
||||||
|
"The start time of the marker (in seconds). Supports decimals."
|
||||||
seconds: Float
|
seconds: Float
|
||||||
|
"The end time of the marker (in seconds). Supports decimals."
|
||||||
|
end_seconds: Float
|
||||||
scene_id: ID
|
scene_id: ID
|
||||||
primary_tag_id: ID
|
primary_tag_id: ID
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input BulkSceneMarkerUpdateInput {
|
||||||
|
ids: [ID!]
|
||||||
|
title: String
|
||||||
|
primary_tag_id: ID
|
||||||
|
tag_ids: BulkUpdateIds
|
||||||
|
}
|
||||||
|
|
||||||
type FindSceneMarkersResultType {
|
type FindSceneMarkersResultType {
|
||||||
count: Int!
|
count: Int!
|
||||||
scene_markers: [SceneMarker!]!
|
scene_markers: [SceneMarker!]!
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,11 @@ type SceneMovie {
|
||||||
scene_index: Int
|
scene_index: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SceneGroup {
|
||||||
|
group: Group!
|
||||||
|
scene_index: Int
|
||||||
|
}
|
||||||
|
|
||||||
type VideoCaption {
|
type VideoCaption {
|
||||||
language_code: String!
|
language_code: String!
|
||||||
caption_type: String!
|
caption_type: String!
|
||||||
|
|
@ -68,7 +73,8 @@ type Scene {
|
||||||
scene_markers: [SceneMarker!]!
|
scene_markers: [SceneMarker!]!
|
||||||
galleries: [Gallery!]!
|
galleries: [Gallery!]!
|
||||||
studio: Studio
|
studio: Studio
|
||||||
movies: [SceneMovie!]!
|
groups: [SceneGroup!]!
|
||||||
|
movies: [SceneMovie!]! @deprecated(reason: "Use groups")
|
||||||
tags: [Tag!]!
|
tags: [Tag!]!
|
||||||
performers: [Performer!]!
|
performers: [Performer!]!
|
||||||
stash_ids: [StashID!]!
|
stash_ids: [StashID!]!
|
||||||
|
|
@ -82,6 +88,11 @@ input SceneMovieInput {
|
||||||
scene_index: Int
|
scene_index: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input SceneGroupInput {
|
||||||
|
group_id: ID!
|
||||||
|
scene_index: Int
|
||||||
|
}
|
||||||
|
|
||||||
input SceneCreateInput {
|
input SceneCreateInput {
|
||||||
title: String
|
title: String
|
||||||
code: String
|
code: String
|
||||||
|
|
@ -96,7 +107,8 @@ input SceneCreateInput {
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
gallery_ids: [ID!]
|
gallery_ids: [ID!]
|
||||||
performer_ids: [ID!]
|
performer_ids: [ID!]
|
||||||
movies: [SceneMovieInput!]
|
groups: [SceneGroupInput!]
|
||||||
|
movies: [SceneMovieInput!] @deprecated(reason: "Use groups")
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
cover_image: String
|
cover_image: String
|
||||||
|
|
@ -128,7 +140,8 @@ input SceneUpdateInput {
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
gallery_ids: [ID!]
|
gallery_ids: [ID!]
|
||||||
performer_ids: [ID!]
|
performer_ids: [ID!]
|
||||||
movies: [SceneMovieInput!]
|
groups: [SceneGroupInput!]
|
||||||
|
movies: [SceneMovieInput!] @deprecated(reason: "Use groups")
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
cover_image: String
|
cover_image: String
|
||||||
|
|
@ -175,7 +188,8 @@ input BulkSceneUpdateInput {
|
||||||
gallery_ids: BulkUpdateIds
|
gallery_ids: BulkUpdateIds
|
||||||
performer_ids: BulkUpdateIds
|
performer_ids: BulkUpdateIds
|
||||||
tag_ids: BulkUpdateIds
|
tag_ids: BulkUpdateIds
|
||||||
movie_ids: BulkUpdateIds
|
group_ids: BulkUpdateIds
|
||||||
|
movie_ids: BulkUpdateIds @deprecated(reason: "Use group_ids")
|
||||||
}
|
}
|
||||||
|
|
||||||
input SceneDestroyInput {
|
input SceneDestroyInput {
|
||||||
|
|
|
||||||
65
graphql/schema/types/scraped-group.graphql
Normal file
65
graphql/schema/types/scraped-group.graphql
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
"A movie from a scraping operation..."
|
||||||
|
type ScrapedMovie {
|
||||||
|
stored_id: ID
|
||||||
|
name: String
|
||||||
|
aliases: String
|
||||||
|
duration: String
|
||||||
|
date: String
|
||||||
|
rating: String
|
||||||
|
director: String
|
||||||
|
url: String @deprecated(reason: "use urls")
|
||||||
|
urls: [String!]
|
||||||
|
synopsis: String
|
||||||
|
studio: ScrapedStudio
|
||||||
|
tags: [ScrapedTag!]
|
||||||
|
|
||||||
|
"This should be a base64 encoded data URL"
|
||||||
|
front_image: String
|
||||||
|
"This should be a base64 encoded data URL"
|
||||||
|
back_image: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input ScrapedMovieInput {
|
||||||
|
name: String
|
||||||
|
aliases: String
|
||||||
|
duration: String
|
||||||
|
date: String
|
||||||
|
rating: String
|
||||||
|
director: String
|
||||||
|
url: String @deprecated(reason: "use urls")
|
||||||
|
urls: [String!]
|
||||||
|
synopsis: String
|
||||||
|
# not including tags for the input
|
||||||
|
}
|
||||||
|
|
||||||
|
"A group from a scraping operation..."
|
||||||
|
type ScrapedGroup {
|
||||||
|
stored_id: ID
|
||||||
|
name: String
|
||||||
|
aliases: String
|
||||||
|
duration: String
|
||||||
|
date: String
|
||||||
|
rating: String
|
||||||
|
director: String
|
||||||
|
urls: [String!]
|
||||||
|
synopsis: String
|
||||||
|
studio: ScrapedStudio
|
||||||
|
tags: [ScrapedTag!]
|
||||||
|
|
||||||
|
"This should be a base64 encoded data URL"
|
||||||
|
front_image: String
|
||||||
|
"This should be a base64 encoded data URL"
|
||||||
|
back_image: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input ScrapedGroupInput {
|
||||||
|
name: String
|
||||||
|
aliases: String
|
||||||
|
duration: String
|
||||||
|
date: String
|
||||||
|
rating: String
|
||||||
|
director: String
|
||||||
|
urls: [String!]
|
||||||
|
synopsis: String
|
||||||
|
# not including tags for the input
|
||||||
|
}
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
"A movie from a scraping operation..."
|
|
||||||
type ScrapedMovie {
|
|
||||||
stored_id: ID
|
|
||||||
name: String
|
|
||||||
aliases: String
|
|
||||||
duration: String
|
|
||||||
date: String
|
|
||||||
rating: String
|
|
||||||
director: String
|
|
||||||
url: String
|
|
||||||
synopsis: String
|
|
||||||
studio: ScrapedStudio
|
|
||||||
|
|
||||||
"This should be a base64 encoded data URL"
|
|
||||||
front_image: String
|
|
||||||
"This should be a base64 encoded data URL"
|
|
||||||
back_image: String
|
|
||||||
}
|
|
||||||
|
|
||||||
input ScrapedMovieInput {
|
|
||||||
name: String
|
|
||||||
aliases: String
|
|
||||||
duration: String
|
|
||||||
date: String
|
|
||||||
rating: String
|
|
||||||
director: String
|
|
||||||
url: String
|
|
||||||
synopsis: String
|
|
||||||
}
|
|
||||||
|
|
@ -5,9 +5,10 @@ type ScrapedPerformer {
|
||||||
name: String
|
name: String
|
||||||
disambiguation: String
|
disambiguation: String
|
||||||
gender: String
|
gender: String
|
||||||
url: String
|
url: String @deprecated(reason: "use urls")
|
||||||
twitter: String
|
urls: [String!]
|
||||||
instagram: String
|
twitter: String @deprecated(reason: "use urls")
|
||||||
|
instagram: String @deprecated(reason: "use urls")
|
||||||
birthdate: String
|
birthdate: String
|
||||||
ethnicity: String
|
ethnicity: String
|
||||||
country: String
|
country: String
|
||||||
|
|
@ -40,9 +41,10 @@ input ScrapedPerformerInput {
|
||||||
name: String
|
name: String
|
||||||
disambiguation: String
|
disambiguation: String
|
||||||
gender: String
|
gender: String
|
||||||
url: String
|
url: String @deprecated(reason: "use urls")
|
||||||
twitter: String
|
urls: [String!]
|
||||||
instagram: String
|
twitter: String @deprecated(reason: "use urls")
|
||||||
|
instagram: String @deprecated(reason: "use urls")
|
||||||
birthdate: String
|
birthdate: String
|
||||||
ethnicity: String
|
ethnicity: String
|
||||||
country: String
|
country: String
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ enum ScrapeType {
|
||||||
"Type of the content a scraper generates"
|
"Type of the content a scraper generates"
|
||||||
enum ScrapeContentType {
|
enum ScrapeContentType {
|
||||||
GALLERY
|
GALLERY
|
||||||
|
IMAGE
|
||||||
MOVIE
|
MOVIE
|
||||||
|
GROUP
|
||||||
PERFORMER
|
PERFORMER
|
||||||
SCENE
|
SCENE
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +23,9 @@ union ScrapedContent =
|
||||||
| ScrapedTag
|
| ScrapedTag
|
||||||
| ScrapedScene
|
| ScrapedScene
|
||||||
| ScrapedGallery
|
| ScrapedGallery
|
||||||
|
| ScrapedImage
|
||||||
| ScrapedMovie
|
| ScrapedMovie
|
||||||
|
| ScrapedGroup
|
||||||
| ScrapedPerformer
|
| ScrapedPerformer
|
||||||
|
|
||||||
type ScraperSpec {
|
type ScraperSpec {
|
||||||
|
|
@ -39,17 +43,26 @@ type Scraper {
|
||||||
scene: ScraperSpec
|
scene: ScraperSpec
|
||||||
"Details for gallery scraper"
|
"Details for gallery scraper"
|
||||||
gallery: ScraperSpec
|
gallery: ScraperSpec
|
||||||
|
"Details for image scraper"
|
||||||
|
image: ScraperSpec
|
||||||
"Details for movie scraper"
|
"Details for movie scraper"
|
||||||
movie: ScraperSpec
|
movie: ScraperSpec @deprecated(reason: "use group")
|
||||||
|
"Details for group scraper"
|
||||||
|
group: ScraperSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScrapedStudio {
|
type ScrapedStudio {
|
||||||
"Set if studio matched"
|
"Set if studio matched"
|
||||||
stored_id: ID
|
stored_id: ID
|
||||||
name: String!
|
name: String!
|
||||||
url: String
|
url: String @deprecated(reason: "use urls")
|
||||||
|
urls: [String!]
|
||||||
parent: ScrapedStudio
|
parent: ScrapedStudio
|
||||||
image: String
|
image: String
|
||||||
|
details: String
|
||||||
|
"Aliases must be comma-delimited to be parsed correctly"
|
||||||
|
aliases: String
|
||||||
|
tags: [ScrapedTag!]
|
||||||
|
|
||||||
remote_site_id: String
|
remote_site_id: String
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +71,8 @@ type ScrapedTag {
|
||||||
"Set if tag matched"
|
"Set if tag matched"
|
||||||
stored_id: ID
|
stored_id: ID
|
||||||
name: String!
|
name: String!
|
||||||
|
"Remote site ID, if applicable"
|
||||||
|
remote_site_id: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScrapedScene {
|
type ScrapedScene {
|
||||||
|
|
@ -76,7 +91,8 @@ type ScrapedScene {
|
||||||
studio: ScrapedStudio
|
studio: ScrapedStudio
|
||||||
tags: [ScrapedTag!]
|
tags: [ScrapedTag!]
|
||||||
performers: [ScrapedPerformer!]
|
performers: [ScrapedPerformer!]
|
||||||
movies: [ScrapedMovie!]
|
movies: [ScrapedMovie!] @deprecated(reason: "use groups")
|
||||||
|
groups: [ScrapedGroup!]
|
||||||
|
|
||||||
remote_site_id: String
|
remote_site_id: String
|
||||||
duration: Int
|
duration: Int
|
||||||
|
|
@ -123,12 +139,32 @@ input ScrapedGalleryInput {
|
||||||
# no studio, tags or performers
|
# no studio, tags or performers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScrapedImage {
|
||||||
|
title: String
|
||||||
|
code: String
|
||||||
|
details: String
|
||||||
|
photographer: String
|
||||||
|
urls: [String!]
|
||||||
|
date: String
|
||||||
|
studio: ScrapedStudio
|
||||||
|
tags: [ScrapedTag!]
|
||||||
|
performers: [ScrapedPerformer!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input ScrapedImageInput {
|
||||||
|
title: String
|
||||||
|
code: String
|
||||||
|
details: String
|
||||||
|
urls: [String!]
|
||||||
|
date: String
|
||||||
|
}
|
||||||
|
|
||||||
input ScraperSourceInput {
|
input ScraperSourceInput {
|
||||||
"Index of the configured stash-box instance to use. Should be unset if scraper_id is set"
|
"Index of the configured stash-box instance to use. Should be unset if scraper_id is set"
|
||||||
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
|
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
|
||||||
"Stash-box endpoint"
|
"Stash-box endpoint"
|
||||||
stash_box_endpoint: String
|
stash_box_endpoint: String
|
||||||
"Scraper ID to scrape with. Should be unset if stash_box_index is set"
|
"Scraper ID to scrape with. Should be unset if stash_box_endpoint/stash_box_index is set"
|
||||||
scraper_id: ID
|
scraper_id: ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +173,7 @@ type ScraperSource {
|
||||||
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
|
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
|
||||||
"Stash-box endpoint"
|
"Stash-box endpoint"
|
||||||
stash_box_endpoint: String
|
stash_box_endpoint: String
|
||||||
"Scraper ID to scrape with. Should be unset if stash_box_index is set"
|
"Scraper ID to scrape with. Should be unset if stash_box_endpoint/stash_box_index is set"
|
||||||
scraper_id: ID
|
scraper_id: ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,6 +198,13 @@ input ScrapeSingleStudioInput {
|
||||||
query: String
|
query: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input ScrapeSingleTagInput {
|
||||||
|
"""
|
||||||
|
Query can be either a name or a Stash ID
|
||||||
|
"""
|
||||||
|
query: String
|
||||||
|
}
|
||||||
|
|
||||||
input ScrapeSinglePerformerInput {
|
input ScrapeSinglePerformerInput {
|
||||||
"Instructs to query by string"
|
"Instructs to query by string"
|
||||||
query: String
|
query: String
|
||||||
|
|
@ -185,18 +228,38 @@ input ScrapeSingleGalleryInput {
|
||||||
gallery_input: ScrapedGalleryInput
|
gallery_input: ScrapedGalleryInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input ScrapeSingleImageInput {
|
||||||
|
"Instructs to query by string"
|
||||||
|
query: String
|
||||||
|
"Instructs to query by image id"
|
||||||
|
image_id: ID
|
||||||
|
"Instructs to query by image fragment"
|
||||||
|
image_input: ScrapedImageInput
|
||||||
|
}
|
||||||
|
|
||||||
input ScrapeSingleMovieInput {
|
input ScrapeSingleMovieInput {
|
||||||
"Instructs to query by string"
|
"Instructs to query by string"
|
||||||
query: String
|
query: String
|
||||||
"Instructs to query by movie id"
|
"Instructs to query by movie id"
|
||||||
movie_id: ID
|
movie_id: ID
|
||||||
"Instructs to query by gallery fragment"
|
"Instructs to query by movie fragment"
|
||||||
movie_input: ScrapedMovieInput
|
movie_input: ScrapedMovieInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input ScrapeSingleGroupInput {
|
||||||
|
"Instructs to query by string"
|
||||||
|
query: String
|
||||||
|
"Instructs to query by group id"
|
||||||
|
group_id: ID
|
||||||
|
"Instructs to query by group fragment"
|
||||||
|
group_input: ScrapedGroupInput
|
||||||
|
}
|
||||||
|
|
||||||
input StashBoxSceneQueryInput {
|
input StashBoxSceneQueryInput {
|
||||||
"Index of the configured stash-box instance to use"
|
"Index of the configured stash-box instance to use"
|
||||||
stash_box_index: Int!
|
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
|
||||||
|
"Endpoint of the stash-box instance to use"
|
||||||
|
stash_box_endpoint: String
|
||||||
"Instructs query by scene fingerprints"
|
"Instructs query by scene fingerprints"
|
||||||
scene_ids: [ID!]
|
scene_ids: [ID!]
|
||||||
"Query by query string"
|
"Query by query string"
|
||||||
|
|
@ -205,7 +268,9 @@ input StashBoxSceneQueryInput {
|
||||||
|
|
||||||
input StashBoxPerformerQueryInput {
|
input StashBoxPerformerQueryInput {
|
||||||
"Index of the configured stash-box instance to use"
|
"Index of the configured stash-box instance to use"
|
||||||
stash_box_index: Int!
|
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
|
||||||
|
"Endpoint of the stash-box instance to use"
|
||||||
|
stash_box_endpoint: String
|
||||||
"Instructs query by scene fingerprints"
|
"Instructs query by scene fingerprints"
|
||||||
performer_ids: [ID!]
|
performer_ids: [ID!]
|
||||||
"Query by query string"
|
"Query by query string"
|
||||||
|
|
@ -223,22 +288,32 @@ type StashBoxFingerprint {
|
||||||
duration: Int!
|
duration: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
"If neither ids nor names are set, tag all items"
|
"""
|
||||||
|
Accepts either ids, or a combination of names and stash_ids.
|
||||||
|
If none are set, then all existing items will be tagged.
|
||||||
|
"""
|
||||||
input StashBoxBatchTagInput {
|
input StashBoxBatchTagInput {
|
||||||
"Stash endpoint to use for the tagging"
|
"Stash endpoint to use for the tagging"
|
||||||
endpoint: Int!
|
endpoint: Int @deprecated(reason: "use stash_box_endpoint")
|
||||||
|
"Endpoint of the stash-box instance to use"
|
||||||
|
stash_box_endpoint: String
|
||||||
"Fields to exclude when executing the tagging"
|
"Fields to exclude when executing the tagging"
|
||||||
exclude_fields: [String!]
|
exclude_fields: [String!]
|
||||||
"Refresh items already tagged by StashBox if true. Only tag items with no StashBox tagging if false"
|
"Refresh items already tagged by StashBox if true. Only tag items with no StashBox tagging if false"
|
||||||
refresh: Boolean!
|
refresh: Boolean!
|
||||||
"If batch adding studios, should their parent studios also be created?"
|
"If batch adding studios, should their parent studios also be created?"
|
||||||
createParent: Boolean!
|
createParent: Boolean!
|
||||||
"If set, only tag these ids"
|
"""
|
||||||
|
IDs in stash of the items to update.
|
||||||
|
If set, names and stash_ids fields will be ignored.
|
||||||
|
"""
|
||||||
ids: [ID!]
|
ids: [ID!]
|
||||||
"If set, only tag these names"
|
"Names of the items in the stash-box instance to search for and create"
|
||||||
names: [String!]
|
names: [String!]
|
||||||
"If set, only tag these performer ids"
|
"Stash IDs of the items in the stash-box instance to search for and create"
|
||||||
|
stash_ids: [String!]
|
||||||
|
"IDs in stash of the performers to update"
|
||||||
performer_ids: [ID!] @deprecated(reason: "use ids")
|
performer_ids: [ID!] @deprecated(reason: "use ids")
|
||||||
"If set, only tag these performer names"
|
"Names of the performers in the stash-box instance to search for and create"
|
||||||
performer_names: [String!] @deprecated(reason: "use names")
|
performer_names: [String!] @deprecated(reason: "use names")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,30 +2,37 @@ type StashBox {
|
||||||
endpoint: String!
|
endpoint: String!
|
||||||
api_key: String!
|
api_key: String!
|
||||||
name: String!
|
name: String!
|
||||||
|
max_requests_per_minute: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
input StashBoxInput {
|
input StashBoxInput {
|
||||||
endpoint: String!
|
endpoint: String!
|
||||||
api_key: String!
|
api_key: String!
|
||||||
name: String!
|
name: String!
|
||||||
|
# defaults to 240
|
||||||
|
max_requests_per_minute: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type StashID {
|
type StashID {
|
||||||
endpoint: String!
|
endpoint: String!
|
||||||
stash_id: String!
|
stash_id: String!
|
||||||
|
updated_at: Time!
|
||||||
}
|
}
|
||||||
|
|
||||||
input StashIDInput {
|
input StashIDInput {
|
||||||
endpoint: String!
|
endpoint: String!
|
||||||
stash_id: String!
|
stash_id: String!
|
||||||
|
updated_at: Time
|
||||||
}
|
}
|
||||||
|
|
||||||
input StashBoxFingerprintSubmissionInput {
|
input StashBoxFingerprintSubmissionInput {
|
||||||
scene_ids: [String!]!
|
scene_ids: [String!]!
|
||||||
stash_box_index: Int!
|
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
|
||||||
|
stash_box_endpoint: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input StashBoxDraftSubmissionInput {
|
input StashBoxDraftSubmissionInput {
|
||||||
id: String!
|
id: String!
|
||||||
stash_box_index: Int!
|
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
|
||||||
|
stash_box_endpoint: String
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ type StatsResultType {
|
||||||
gallery_count: Int!
|
gallery_count: Int!
|
||||||
performer_count: Int!
|
performer_count: Int!
|
||||||
studio_count: Int!
|
studio_count: Int!
|
||||||
movie_count: Int!
|
group_count: Int!
|
||||||
|
movie_count: Int! @deprecated(reason: "use group_count instead")
|
||||||
tag_count: Int!
|
tag_count: Int!
|
||||||
total_o_count: Int!
|
total_o_count: Int!
|
||||||
total_play_duration: Float!
|
total_play_duration: Float!
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
type Studio {
|
type Studio {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]!
|
||||||
parent_studio: Studio
|
parent_studio: Studio
|
||||||
child_studios: [Studio!]!
|
child_studios: [Studio!]!
|
||||||
aliases: [String!]!
|
aliases: [String!]!
|
||||||
|
tags: [Tag!]!
|
||||||
ignore_auto_tag: Boolean!
|
ignore_auto_tag: Boolean!
|
||||||
|
|
||||||
image_path: String # Resolver
|
image_path: String # Resolver
|
||||||
|
|
@ -12,7 +14,8 @@ type Studio {
|
||||||
image_count(depth: Int): Int! # Resolver
|
image_count(depth: Int): Int! # Resolver
|
||||||
gallery_count(depth: Int): Int! # Resolver
|
gallery_count(depth: Int): Int! # Resolver
|
||||||
performer_count(depth: Int): Int! # Resolver
|
performer_count(depth: Int): Int! # Resolver
|
||||||
movie_count(depth: Int): Int! # Resolver
|
group_count(depth: Int): Int! # Resolver
|
||||||
|
movie_count(depth: Int): Int! @deprecated(reason: "use group_count instead") # Resolver
|
||||||
stash_ids: [StashID!]!
|
stash_ids: [StashID!]!
|
||||||
# rating expressed as 1-100
|
# rating expressed as 1-100
|
||||||
rating100: Int
|
rating100: Int
|
||||||
|
|
@ -20,12 +23,15 @@ type Studio {
|
||||||
details: String
|
details: String
|
||||||
created_at: Time!
|
created_at: Time!
|
||||||
updated_at: Time!
|
updated_at: Time!
|
||||||
movies: [Movie!]!
|
groups: [Group!]!
|
||||||
|
movies: [Movie!]! @deprecated(reason: "use groups instead")
|
||||||
|
o_counter: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
input StudioCreateInput {
|
input StudioCreateInput {
|
||||||
name: String!
|
name: String!
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]
|
||||||
parent_id: ID
|
parent_id: ID
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
image: String
|
image: String
|
||||||
|
|
@ -35,13 +41,15 @@ input StudioCreateInput {
|
||||||
favorite: Boolean
|
favorite: Boolean
|
||||||
details: String
|
details: String
|
||||||
aliases: [String!]
|
aliases: [String!]
|
||||||
|
tag_ids: [ID!]
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
input StudioUpdateInput {
|
input StudioUpdateInput {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
url: String
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: [String!]
|
||||||
parent_id: ID
|
parent_id: ID
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
image: String
|
image: String
|
||||||
|
|
@ -51,6 +59,20 @@ input StudioUpdateInput {
|
||||||
favorite: Boolean
|
favorite: Boolean
|
||||||
details: String
|
details: String
|
||||||
aliases: [String!]
|
aliases: [String!]
|
||||||
|
tag_ids: [ID!]
|
||||||
|
ignore_auto_tag: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input BulkStudioUpdateInput {
|
||||||
|
ids: [ID!]!
|
||||||
|
url: String @deprecated(reason: "Use urls")
|
||||||
|
urls: BulkUpdateStrings
|
||||||
|
parent_id: ID
|
||||||
|
# rating expressed as 1-100
|
||||||
|
rating100: Int
|
||||||
|
favorite: Boolean
|
||||||
|
details: String
|
||||||
|
tag_ids: BulkUpdateIds
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
type Tag {
|
type Tag {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
|
"Value that does not appear in the UI but overrides name for sorting"
|
||||||
|
sort_name: String
|
||||||
description: String
|
description: String
|
||||||
aliases: [String!]!
|
aliases: [String!]!
|
||||||
ignore_auto_tag: Boolean!
|
ignore_auto_tag: Boolean!
|
||||||
created_at: Time!
|
created_at: Time!
|
||||||
updated_at: Time!
|
updated_at: Time!
|
||||||
favorite: Boolean!
|
favorite: Boolean!
|
||||||
|
stash_ids: [StashID!]!
|
||||||
image_path: String # Resolver
|
image_path: String # Resolver
|
||||||
scene_count(depth: Int): Int! # Resolver
|
scene_count(depth: Int): Int! # Resolver
|
||||||
scene_marker_count(depth: Int): Int! # Resolver
|
scene_marker_count(depth: Int): Int! # Resolver
|
||||||
image_count(depth: Int): Int! # Resolver
|
image_count(depth: Int): Int! # Resolver
|
||||||
gallery_count(depth: Int): Int! # Resolver
|
gallery_count(depth: Int): Int! # Resolver
|
||||||
performer_count(depth: Int): Int! # Resolver
|
performer_count(depth: Int): Int! # Resolver
|
||||||
|
studio_count(depth: Int): Int! # Resolver
|
||||||
|
group_count(depth: Int): Int! # Resolver
|
||||||
|
movie_count(depth: Int): Int! @deprecated(reason: "use group_count instead") # Resolver
|
||||||
parents: [Tag!]!
|
parents: [Tag!]!
|
||||||
children: [Tag!]!
|
children: [Tag!]!
|
||||||
|
|
||||||
|
|
@ -22,12 +28,15 @@ type Tag {
|
||||||
|
|
||||||
input TagCreateInput {
|
input TagCreateInput {
|
||||||
name: String!
|
name: String!
|
||||||
|
"Value that does not appear in the UI but overrides name for sorting"
|
||||||
|
sort_name: String
|
||||||
description: String
|
description: String
|
||||||
aliases: [String!]
|
aliases: [String!]
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
favorite: Boolean
|
favorite: Boolean
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
image: String
|
image: String
|
||||||
|
stash_ids: [StashIDInput!]
|
||||||
|
|
||||||
parent_ids: [ID!]
|
parent_ids: [ID!]
|
||||||
child_ids: [ID!]
|
child_ids: [ID!]
|
||||||
|
|
@ -36,12 +45,15 @@ input TagCreateInput {
|
||||||
input TagUpdateInput {
|
input TagUpdateInput {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
|
"Value that does not appear in the UI but overrides name for sorting"
|
||||||
|
sort_name: String
|
||||||
description: String
|
description: String
|
||||||
aliases: [String!]
|
aliases: [String!]
|
||||||
ignore_auto_tag: Boolean
|
ignore_auto_tag: Boolean
|
||||||
favorite: Boolean
|
favorite: Boolean
|
||||||
"This should be a URL or a base64 encoded data URL"
|
"This should be a URL or a base64 encoded data URL"
|
||||||
image: String
|
image: String
|
||||||
|
stash_ids: [StashIDInput!]
|
||||||
|
|
||||||
parent_ids: [ID!]
|
parent_ids: [ID!]
|
||||||
child_ids: [ID!]
|
child_ids: [ID!]
|
||||||
|
|
@ -60,3 +72,14 @@ input TagsMergeInput {
|
||||||
source: [ID!]!
|
source: [ID!]!
|
||||||
destination: ID!
|
destination: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input BulkTagUpdateInput {
|
||||||
|
ids: [ID!]
|
||||||
|
description: String
|
||||||
|
aliases: BulkUpdateStrings
|
||||||
|
ignore_auto_tag: Boolean
|
||||||
|
favorite: Boolean
|
||||||
|
|
||||||
|
parent_ids: BulkUpdateIds
|
||||||
|
child_ids: BulkUpdateIds
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ fragment ImageFragment on Image {
|
||||||
fragment StudioFragment on Studio {
|
fragment StudioFragment on Studio {
|
||||||
name
|
name
|
||||||
id
|
id
|
||||||
|
aliases
|
||||||
urls {
|
urls {
|
||||||
...URLFragment
|
...URLFragment
|
||||||
}
|
}
|
||||||
|
|
@ -30,11 +31,6 @@ fragment TagFragment on Tag {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment FuzzyDateFragment on FuzzyDate {
|
|
||||||
date
|
|
||||||
accuracy
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment MeasurementsFragment on Measurements {
|
fragment MeasurementsFragment on Measurements {
|
||||||
band_size
|
band_size
|
||||||
cup_size
|
cup_size
|
||||||
|
|
@ -54,15 +50,16 @@ fragment PerformerFragment on Performer {
|
||||||
aliases
|
aliases
|
||||||
gender
|
gender
|
||||||
merged_ids
|
merged_ids
|
||||||
|
deleted
|
||||||
|
merged_into_id
|
||||||
urls {
|
urls {
|
||||||
...URLFragment
|
...URLFragment
|
||||||
}
|
}
|
||||||
images {
|
images {
|
||||||
...ImageFragment
|
...ImageFragment
|
||||||
}
|
}
|
||||||
birthdate {
|
birth_date
|
||||||
...FuzzyDateFragment
|
death_date
|
||||||
}
|
|
||||||
ethnicity
|
ethnicity
|
||||||
country
|
country
|
||||||
eye_color
|
eye_color
|
||||||
|
|
@ -173,6 +170,21 @@ query FindStudio($id: ID, $name: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query FindTag($id: ID, $name: String) {
|
||||||
|
findTag(id: $id, name: $name) {
|
||||||
|
...TagFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query QueryTags($input: TagQueryInput!) {
|
||||||
|
queryTags(input: $input) {
|
||||||
|
count
|
||||||
|
tags {
|
||||||
|
...TagFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutation SubmitFingerprint($input: FingerprintSubmission!) {
|
mutation SubmitFingerprint($input: FingerprintSubmission!) {
|
||||||
submitFingerprint(input: $input)
|
submitFingerprint(input: $input)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tripwireActivatedErrMsg = "Stash is exposed to the public internet without authentication, and is not serving any more content to protect your privacy. " +
|
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://docs.stashapp.cc/networking/authentication-required-when-accessing-stash-from-the-internet"
|
"More information and fixes are available at https://discourse.stashapp.cc/t/-/1658"
|
||||||
|
|
||||||
externalAccessErrMsg = "You have attempted to access Stash over the internet, and authentication is not enabled. " +
|
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! " +
|
"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. " +
|
"Stash is not answering any other requests to protect your privacy. " +
|
||||||
"Please read the log entry or visit https://docs.stashapp.cc/networking/authentication-required-when-accessing-stash-from-the-internet"
|
"Please read the log entry or visit https://discourse.stashapp.cc/t/-/1658"
|
||||||
)
|
)
|
||||||
|
|
||||||
func allowUnauthenticated(r *http.Request) bool {
|
func allowUnauthenticated(r *http.Request) bool {
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ func (t changesetTranslator) string(value *string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return *value
|
return strings.TrimSpace(*value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t changesetTranslator) optionalString(value *string, field string) models.OptionalString {
|
func (t changesetTranslator) optionalString(value *string, field string) models.OptionalString {
|
||||||
|
|
@ -106,7 +106,12 @@ func (t changesetTranslator) optionalString(value *string, field string) models.
|
||||||
return models.OptionalString{}
|
return models.OptionalString{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return models.NewOptionalStringPtr(value)
|
if value == nil {
|
||||||
|
return models.NewOptionalStringPtr(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmed := strings.TrimSpace(*value)
|
||||||
|
return models.NewOptionalString(trimmed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t changesetTranslator) optionalDate(value *string, field string) (models.OptionalDate, error) {
|
func (t changesetTranslator) optionalDate(value *string, field string) (models.OptionalDate, error) {
|
||||||
|
|
@ -318,8 +323,14 @@ func (t changesetTranslator) updateStrings(value []string, field string) *models
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trim whitespace from each string
|
||||||
|
trimmedValues := make([]string, len(value))
|
||||||
|
for i, v := range value {
|
||||||
|
trimmedValues[i] = strings.TrimSpace(v)
|
||||||
|
}
|
||||||
|
|
||||||
return &models.UpdateStrings{
|
return &models.UpdateStrings{
|
||||||
Values: value,
|
Values: trimmedValues,
|
||||||
Mode: models.RelationshipUpdateModeSet,
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -329,49 +340,98 @@ func (t changesetTranslator) updateStringsBulk(value *BulkUpdateStrings, field s
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trim whitespace from each string
|
||||||
|
trimmedValues := make([]string, len(value.Values))
|
||||||
|
for i, v := range value.Values {
|
||||||
|
trimmedValues[i] = strings.TrimSpace(v)
|
||||||
|
}
|
||||||
|
|
||||||
return &models.UpdateStrings{
|
return &models.UpdateStrings{
|
||||||
Values: value.Values,
|
Values: trimmedValues,
|
||||||
Mode: value.Mode,
|
Mode: value.Mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t changesetTranslator) updateStashIDs(value []models.StashID, field string) *models.UpdateStashIDs {
|
func (t changesetTranslator) updateStashIDs(value models.StashIDInputs, field string) *models.UpdateStashIDs {
|
||||||
if !t.hasField(field) {
|
if !t.hasField(field) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &models.UpdateStashIDs{
|
return &models.UpdateStashIDs{
|
||||||
StashIDs: value,
|
StashIDs: value.ToStashIDs(),
|
||||||
Mode: models.RelationshipUpdateModeSet,
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t changesetTranslator) relatedMovies(value []models.SceneMovieInput) (models.RelatedMovies, error) {
|
func (t changesetTranslator) relatedGroupsFromMovies(value []models.SceneMovieInput) (models.RelatedGroups, error) {
|
||||||
moviesScenes, err := models.MoviesScenesFromInput(value)
|
groupsScenes, err := models.GroupsScenesFromInput(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return models.RelatedMovies{}, err
|
return models.RelatedGroups{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return models.NewRelatedMovies(moviesScenes), nil
|
return models.NewRelatedGroups(groupsScenes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t changesetTranslator) updateMovieIDs(value []models.SceneMovieInput, field string) (*models.UpdateMovieIDs, error) {
|
func groupsScenesFromGroupInput(input []models.SceneGroupInput) ([]models.GroupsScenes, error) {
|
||||||
|
ret := make([]models.GroupsScenes, len(input))
|
||||||
|
|
||||||
|
for i, v := range input {
|
||||||
|
mID, err := strconv.Atoi(v.GroupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid group ID: %s", v.GroupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret[i] = models.GroupsScenes{
|
||||||
|
GroupID: mID,
|
||||||
|
SceneIndex: v.SceneIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t changesetTranslator) relatedGroups(value []models.SceneGroupInput) (models.RelatedGroups, error) {
|
||||||
|
groupsScenes, err := groupsScenesFromGroupInput(value)
|
||||||
|
if err != nil {
|
||||||
|
return models.RelatedGroups{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.NewRelatedGroups(groupsScenes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t changesetTranslator) updateGroupIDsFromMovies(value []models.SceneMovieInput, field string) (*models.UpdateGroupIDs, error) {
|
||||||
if !t.hasField(field) {
|
if !t.hasField(field) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
moviesScenes, err := models.MoviesScenesFromInput(value)
|
groupsScenes, err := models.GroupsScenesFromInput(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &models.UpdateMovieIDs{
|
return &models.UpdateGroupIDs{
|
||||||
Movies: moviesScenes,
|
Groups: groupsScenes,
|
||||||
Mode: models.RelationshipUpdateModeSet,
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t changesetTranslator) updateMovieIDsBulk(value *BulkUpdateIds, field string) (*models.UpdateMovieIDs, error) {
|
func (t changesetTranslator) updateGroupIDs(value []models.SceneGroupInput, field string) (*models.UpdateGroupIDs, error) {
|
||||||
|
if !t.hasField(field) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsScenes, err := groupsScenesFromGroupInput(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.UpdateGroupIDs{
|
||||||
|
Groups: groupsScenes,
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t changesetTranslator) updateGroupIDsBulk(value *BulkUpdateIds, field string) (*models.UpdateGroupIDs, error) {
|
||||||
if !t.hasField(field) || value == nil {
|
if !t.hasField(field) || value == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -381,13 +441,74 @@ func (t changesetTranslator) updateMovieIDsBulk(value *BulkUpdateIds, field stri
|
||||||
return nil, fmt.Errorf("converting ids [%v]: %w", value.Ids, err)
|
return nil, fmt.Errorf("converting ids [%v]: %w", value.Ids, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
movies := make([]models.MoviesScenes, len(ids))
|
groups := make([]models.GroupsScenes, len(ids))
|
||||||
for i, id := range ids {
|
for i, id := range ids {
|
||||||
movies[i] = models.MoviesScenes{MovieID: id}
|
groups[i] = models.GroupsScenes{GroupID: id}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &models.UpdateMovieIDs{
|
return &models.UpdateGroupIDs{
|
||||||
Movies: movies,
|
Groups: groups,
|
||||||
|
Mode: value.Mode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupsDescriptionsFromGroupInput(input []*GroupDescriptionInput) ([]models.GroupIDDescription, error) {
|
||||||
|
ret := make([]models.GroupIDDescription, len(input))
|
||||||
|
|
||||||
|
for i, v := range input {
|
||||||
|
gID, err := strconv.Atoi(v.GroupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid group ID: %s", v.GroupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret[i] = models.GroupIDDescription{
|
||||||
|
GroupID: gID,
|
||||||
|
}
|
||||||
|
if v.Description != nil {
|
||||||
|
ret[i].Description = strings.TrimSpace(*v.Description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t changesetTranslator) groupIDDescriptions(value []*GroupDescriptionInput) (models.RelatedGroupDescriptions, error) {
|
||||||
|
groupsScenes, err := groupsDescriptionsFromGroupInput(value)
|
||||||
|
if err != nil {
|
||||||
|
return models.RelatedGroupDescriptions{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return models.NewRelatedGroupDescriptions(groupsScenes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t changesetTranslator) updateGroupIDDescriptions(value []*GroupDescriptionInput, field string) (*models.UpdateGroupDescriptions, error) {
|
||||||
|
if !t.hasField(field) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsScenes, err := groupsDescriptionsFromGroupInput(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.UpdateGroupDescriptions{
|
||||||
|
Groups: groupsScenes,
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t changesetTranslator) updateGroupIDDescriptionsBulk(value *BulkUpdateGroupDescriptionsInput, field string) (*models.UpdateGroupDescriptions, error) {
|
||||||
|
if !t.hasField(field) || value == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, err := groupsDescriptionsFromGroupInput(value.Groups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.UpdateGroupDescriptions{
|
||||||
|
Groups: groups,
|
||||||
Mode: value.Mode,
|
Mode: value.Mode,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/sys/cpu"
|
"golang.org/x/sys/cpu"
|
||||||
|
|
@ -36,6 +38,24 @@ var stashReleases = func() map[string]string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isMacOSBundle checks if the application is running from within a macOS .app bundle
|
||||||
|
func isMacOSBundle() bool {
|
||||||
|
exec, err := os.Executable()
|
||||||
|
return err == nil && strings.Contains(exec, "Stash.app/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWantedRelease determines which release variant to download based on platform and bundle type
|
||||||
|
func getWantedRelease(platform string) string {
|
||||||
|
release := stashReleases()[platform]
|
||||||
|
|
||||||
|
// On macOS, check if running from .app bundle
|
||||||
|
if runtime.GOOS == "darwin" && isMacOSBundle() {
|
||||||
|
return "Stash.app.zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
return release
|
||||||
|
}
|
||||||
|
|
||||||
type githubReleasesResponse struct {
|
type githubReleasesResponse struct {
|
||||||
Url string
|
Url string
|
||||||
Assets_url string
|
Assets_url string
|
||||||
|
|
@ -168,7 +188,7 @@ func GetLatestRelease(ctx context.Context) (*LatestRelease, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
platform := fmt.Sprintf("%s/%s", runtime.GOOS, arch)
|
platform := fmt.Sprintf("%s/%s", runtime.GOOS, arch)
|
||||||
wantedRelease := stashReleases()[platform]
|
wantedRelease := getWantedRelease(platform)
|
||||||
|
|
||||||
url := apiReleases
|
url := apiReleases
|
||||||
if build.IsDevelop() {
|
if build.IsDevelop() {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ package api
|
||||||
type key int
|
type key int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// galleryKey key = 0
|
galleryKey key = 0
|
||||||
performerKey key = iota + 1
|
performerKey
|
||||||
sceneKey
|
sceneKey
|
||||||
studioKey
|
studioKey
|
||||||
movieKey
|
groupKey
|
||||||
tagKey
|
tagKey
|
||||||
downloadKey
|
downloadKey
|
||||||
imageKey
|
imageKey
|
||||||
|
|
|
||||||
12
internal/api/custom_fields.go
Normal file
12
internal/api/custom_fields.go
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import "github.com/stashapp/stash/pkg/models"
|
||||||
|
|
||||||
|
func handleUpdateCustomFields(input models.CustomFieldsInput) models.CustomFieldsInput {
|
||||||
|
ret := input
|
||||||
|
// convert json.Numbers to int/float
|
||||||
|
ret.Full = convertMapJSONNumbers(ret.Full)
|
||||||
|
ret.Partial = convertMapJSONNumbers(ret.Partial)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
2
internal/api/doc.go
Normal file
2
internal/api/doc.go
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package api provides the HTTP and Graphql API for the application.
|
||||||
|
package api
|
||||||
23
internal/api/fields.go
Normal file
23
internal/api/fields.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type queryFields []string
|
||||||
|
|
||||||
|
func collectQueryFields(ctx context.Context) queryFields {
|
||||||
|
fields := graphql.CollectAllFields(ctx)
|
||||||
|
return queryFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f queryFields) Has(field string) bool {
|
||||||
|
for _, v := range f {
|
||||||
|
if v == field {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ var imageBoxExts = []string{
|
||||||
".gif",
|
".gif",
|
||||||
".svg",
|
".svg",
|
||||||
".webp",
|
".webp",
|
||||||
|
".avif",
|
||||||
}
|
}
|
||||||
|
|
||||||
func newImageBox(box fs.FS) (*imageBox, error) {
|
func newImageBox(box fs.FS) (*imageBox, error) {
|
||||||
|
|
@ -101,7 +102,7 @@ func initCustomPerformerImages(customPath string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultPerformerImage(name string, gender *models.GenderEnum) []byte {
|
func getDefaultPerformerImage(name string, gender *models.GenderEnum, sfwMode bool) []byte {
|
||||||
// try the custom box first if we have one
|
// try the custom box first if we have one
|
||||||
if performerBoxCustom != nil {
|
if performerBoxCustom != nil {
|
||||||
ret, err := performerBoxCustom.GetRandomImageByName(name)
|
ret, err := performerBoxCustom.GetRandomImageByName(name)
|
||||||
|
|
@ -111,6 +112,10 @@ func getDefaultPerformerImage(name string, gender *models.GenderEnum) []byte {
|
||||||
logger.Warnf("error loading custom default performer image: %v", err)
|
logger.Warnf("error loading custom default performer image: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sfwMode {
|
||||||
|
return static.ReadAll(static.DefaultSFWPerformerImage)
|
||||||
|
}
|
||||||
|
|
||||||
var g models.GenderEnum
|
var g models.GenderEnum
|
||||||
if gender != nil {
|
if gender != nil {
|
||||||
g = *gender
|
g = *gender
|
||||||
|
|
|
||||||
65
internal/api/json.go
Normal file
65
internal/api/json.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// jsonNumberToNumber converts a JSON number to either a float64 or int64.
|
||||||
|
func jsonNumberToNumber(n json.Number) interface{} {
|
||||||
|
if strings.Contains(string(n), ".") {
|
||||||
|
f, _ := n.Float64()
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
ret, _ := n.Int64()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// anyJSONNumberToNumber converts a JSON number using jsonNumberToNumber, otherwise it returns the existing value
|
||||||
|
func anyJSONNumberToNumber(v any) any {
|
||||||
|
if n, ok := v.(json.Number); ok {
|
||||||
|
return jsonNumberToNumber(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertMapJSONNumbers converts all JSON numbers in a map to either float64 or int64.
|
||||||
|
func convertMapJSONNumbers(m map[string]interface{}) (ret map[string]interface{}) {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = make(map[string]interface{})
|
||||||
|
for k, v := range m {
|
||||||
|
if n, ok := v.(json.Number); ok {
|
||||||
|
ret[k] = jsonNumberToNumber(n)
|
||||||
|
} else if mm, ok := v.(map[string]interface{}); ok {
|
||||||
|
ret[k] = convertMapJSONNumbers(mm)
|
||||||
|
} else {
|
||||||
|
ret[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertCustomFieldCriterionValues(c models.CustomFieldCriterionInput) models.CustomFieldCriterionInput {
|
||||||
|
nv := make([]any, len(c.Value))
|
||||||
|
for i, v := range c.Value {
|
||||||
|
nv[i] = anyJSONNumberToNumber(v)
|
||||||
|
}
|
||||||
|
c.Value = nv
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertCustomFieldCriterionInputJSONNumbers(c []models.CustomFieldCriterionInput) []models.CustomFieldCriterionInput {
|
||||||
|
ret := make([]models.CustomFieldCriterionInput, len(c))
|
||||||
|
for i, v := range c {
|
||||||
|
ret[i] = convertCustomFieldCriterionValues(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
60
internal/api/json_test.go
Normal file
60
internal/api/json_test.go
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConvertMapJSONNumbers(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input map[string]interface{}
|
||||||
|
expected map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Convert JSON numbers to numbers",
|
||||||
|
input: map[string]interface{}{
|
||||||
|
"int": json.Number("12"),
|
||||||
|
"float": json.Number("12.34"),
|
||||||
|
"string": "foo",
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"int": int64(12),
|
||||||
|
"float": 12.34,
|
||||||
|
"string": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Convert JSON numbers to numbers in nested maps",
|
||||||
|
input: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"int": json.Number("56"),
|
||||||
|
"float": json.Number("56.78"),
|
||||||
|
"nested-string": "bar",
|
||||||
|
},
|
||||||
|
"int": json.Number("12"),
|
||||||
|
"float": json.Number("12.34"),
|
||||||
|
"string": "foo",
|
||||||
|
},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"int": int64(56),
|
||||||
|
"float": 56.78,
|
||||||
|
"nested-string": "bar",
|
||||||
|
},
|
||||||
|
"int": int64(12),
|
||||||
|
"float": 12.34,
|
||||||
|
"string": "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := convertMapJSONNumbers(tt.input)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
221
internal/api/loaders/customfieldsloader_gen.go
Normal file
221
internal/api/loaders/customfieldsloader_gen.go
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
|
||||||
|
|
||||||
|
package loaders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CustomFieldsLoaderConfig captures the config to create a new CustomFieldsLoader
|
||||||
|
type CustomFieldsLoaderConfig struct {
|
||||||
|
// Fetch is a method that provides the data for the loader
|
||||||
|
Fetch func(keys []int) ([]models.CustomFieldMap, []error)
|
||||||
|
|
||||||
|
// Wait is how long wait before sending a batch
|
||||||
|
Wait time.Duration
|
||||||
|
|
||||||
|
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
|
||||||
|
MaxBatch int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCustomFieldsLoader creates a new CustomFieldsLoader given a fetch, wait, and maxBatch
|
||||||
|
func NewCustomFieldsLoader(config CustomFieldsLoaderConfig) *CustomFieldsLoader {
|
||||||
|
return &CustomFieldsLoader{
|
||||||
|
fetch: config.Fetch,
|
||||||
|
wait: config.Wait,
|
||||||
|
maxBatch: config.MaxBatch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomFieldsLoader batches and caches requests
|
||||||
|
type CustomFieldsLoader struct {
|
||||||
|
// this method provides the data for the loader
|
||||||
|
fetch func(keys []int) ([]models.CustomFieldMap, []error)
|
||||||
|
|
||||||
|
// how long to done before sending a batch
|
||||||
|
wait time.Duration
|
||||||
|
|
||||||
|
// this will limit the maximum number of keys to send in one batch, 0 = no limit
|
||||||
|
maxBatch int
|
||||||
|
|
||||||
|
// INTERNAL
|
||||||
|
|
||||||
|
// lazily created cache
|
||||||
|
cache map[int]models.CustomFieldMap
|
||||||
|
|
||||||
|
// the current batch. keys will continue to be collected until timeout is hit,
|
||||||
|
// then everything will be sent to the fetch method and out to the listeners
|
||||||
|
batch *customFieldsLoaderBatch
|
||||||
|
|
||||||
|
// mutex to prevent races
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type customFieldsLoaderBatch struct {
|
||||||
|
keys []int
|
||||||
|
data []models.CustomFieldMap
|
||||||
|
error []error
|
||||||
|
closing bool
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a CustomFieldMap by key, batching and caching will be applied automatically
|
||||||
|
func (l *CustomFieldsLoader) Load(key int) (models.CustomFieldMap, error) {
|
||||||
|
return l.LoadThunk(key)()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadThunk returns a function that when called will block waiting for a CustomFieldMap.
|
||||||
|
// This method should be used if you want one goroutine to make requests to many
|
||||||
|
// different data loaders without blocking until the thunk is called.
|
||||||
|
func (l *CustomFieldsLoader) LoadThunk(key int) func() (models.CustomFieldMap, error) {
|
||||||
|
l.mu.Lock()
|
||||||
|
if it, ok := l.cache[key]; ok {
|
||||||
|
l.mu.Unlock()
|
||||||
|
return func() (models.CustomFieldMap, error) {
|
||||||
|
return it, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.batch == nil {
|
||||||
|
l.batch = &customFieldsLoaderBatch{done: make(chan struct{})}
|
||||||
|
}
|
||||||
|
batch := l.batch
|
||||||
|
pos := batch.keyIndex(l, key)
|
||||||
|
l.mu.Unlock()
|
||||||
|
|
||||||
|
return func() (models.CustomFieldMap, error) {
|
||||||
|
<-batch.done
|
||||||
|
|
||||||
|
var data models.CustomFieldMap
|
||||||
|
if pos < len(batch.data) {
|
||||||
|
data = batch.data[pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// its convenient to be able to return a single error for everything
|
||||||
|
if len(batch.error) == 1 {
|
||||||
|
err = batch.error[0]
|
||||||
|
} else if batch.error != nil {
|
||||||
|
err = batch.error[pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
l.mu.Lock()
|
||||||
|
l.unsafeSet(key, data)
|
||||||
|
l.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAll fetches many keys at once. It will be broken into appropriate sized
|
||||||
|
// sub batches depending on how the loader is configured
|
||||||
|
func (l *CustomFieldsLoader) LoadAll(keys []int) ([]models.CustomFieldMap, []error) {
|
||||||
|
results := make([]func() (models.CustomFieldMap, error), len(keys))
|
||||||
|
|
||||||
|
for i, key := range keys {
|
||||||
|
results[i] = l.LoadThunk(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
customFieldMaps := make([]models.CustomFieldMap, len(keys))
|
||||||
|
errors := make([]error, len(keys))
|
||||||
|
for i, thunk := range results {
|
||||||
|
customFieldMaps[i], errors[i] = thunk()
|
||||||
|
}
|
||||||
|
return customFieldMaps, errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAllThunk returns a function that when called will block waiting for a CustomFieldMaps.
|
||||||
|
// This method should be used if you want one goroutine to make requests to many
|
||||||
|
// different data loaders without blocking until the thunk is called.
|
||||||
|
func (l *CustomFieldsLoader) LoadAllThunk(keys []int) func() ([]models.CustomFieldMap, []error) {
|
||||||
|
results := make([]func() (models.CustomFieldMap, error), len(keys))
|
||||||
|
for i, key := range keys {
|
||||||
|
results[i] = l.LoadThunk(key)
|
||||||
|
}
|
||||||
|
return func() ([]models.CustomFieldMap, []error) {
|
||||||
|
customFieldMaps := make([]models.CustomFieldMap, len(keys))
|
||||||
|
errors := make([]error, len(keys))
|
||||||
|
for i, thunk := range results {
|
||||||
|
customFieldMaps[i], errors[i] = thunk()
|
||||||
|
}
|
||||||
|
return customFieldMaps, errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prime the cache with the provided key and value. If the key already exists, no change is made
|
||||||
|
// and false is returned.
|
||||||
|
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
|
||||||
|
func (l *CustomFieldsLoader) Prime(key int, value models.CustomFieldMap) bool {
|
||||||
|
l.mu.Lock()
|
||||||
|
var found bool
|
||||||
|
if _, found = l.cache[key]; !found {
|
||||||
|
l.unsafeSet(key, value)
|
||||||
|
}
|
||||||
|
l.mu.Unlock()
|
||||||
|
return !found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the value at key from the cache, if it exists
|
||||||
|
func (l *CustomFieldsLoader) Clear(key int) {
|
||||||
|
l.mu.Lock()
|
||||||
|
delete(l.cache, key)
|
||||||
|
l.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *CustomFieldsLoader) unsafeSet(key int, value models.CustomFieldMap) {
|
||||||
|
if l.cache == nil {
|
||||||
|
l.cache = map[int]models.CustomFieldMap{}
|
||||||
|
}
|
||||||
|
l.cache[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyIndex will return the location of the key in the batch, if its not found
|
||||||
|
// it will add the key to the batch
|
||||||
|
func (b *customFieldsLoaderBatch) keyIndex(l *CustomFieldsLoader, key int) int {
|
||||||
|
for i, existingKey := range b.keys {
|
||||||
|
if key == existingKey {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := len(b.keys)
|
||||||
|
b.keys = append(b.keys, key)
|
||||||
|
if pos == 0 {
|
||||||
|
go b.startTimer(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
|
||||||
|
if !b.closing {
|
||||||
|
b.closing = true
|
||||||
|
l.batch = nil
|
||||||
|
go b.end(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *customFieldsLoaderBatch) startTimer(l *CustomFieldsLoader) {
|
||||||
|
time.Sleep(l.wait)
|
||||||
|
l.mu.Lock()
|
||||||
|
|
||||||
|
// we must have hit a batch limit and are already finalizing this batch
|
||||||
|
if b.closing {
|
||||||
|
l.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.batch = nil
|
||||||
|
l.mu.Unlock()
|
||||||
|
|
||||||
|
b.end(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *customFieldsLoaderBatch) end(l *CustomFieldsLoader) {
|
||||||
|
b.data, b.error = l.fetch(b.keys)
|
||||||
|
close(b.done)
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
|
// Package loaders contains the dataloaders used by the resolver in [api].
|
||||||
|
// They are generated with `make generate-dataloaders`.
|
||||||
|
// The dataloaders are used to batch requests to the database.
|
||||||
|
|
||||||
//go:generate go run github.com/vektah/dataloaden SceneLoader int *github.com/stashapp/stash/pkg/models.Scene
|
//go:generate go run github.com/vektah/dataloaden SceneLoader int *github.com/stashapp/stash/pkg/models.Scene
|
||||||
//go:generate go run github.com/vektah/dataloaden GalleryLoader int *github.com/stashapp/stash/pkg/models.Gallery
|
//go:generate go run github.com/vektah/dataloaden GalleryLoader int *github.com/stashapp/stash/pkg/models.Gallery
|
||||||
//go:generate go run github.com/vektah/dataloaden ImageLoader int *github.com/stashapp/stash/pkg/models.Image
|
//go:generate go run github.com/vektah/dataloaden ImageLoader int *github.com/stashapp/stash/pkg/models.Image
|
||||||
//go:generate go run github.com/vektah/dataloaden PerformerLoader int *github.com/stashapp/stash/pkg/models.Performer
|
//go:generate go run github.com/vektah/dataloaden PerformerLoader int *github.com/stashapp/stash/pkg/models.Performer
|
||||||
//go:generate go run github.com/vektah/dataloaden StudioLoader int *github.com/stashapp/stash/pkg/models.Studio
|
//go:generate go run github.com/vektah/dataloaden StudioLoader int *github.com/stashapp/stash/pkg/models.Studio
|
||||||
//go:generate go run github.com/vektah/dataloaden TagLoader int *github.com/stashapp/stash/pkg/models.Tag
|
//go:generate go run github.com/vektah/dataloaden TagLoader int *github.com/stashapp/stash/pkg/models.Tag
|
||||||
//go:generate go run github.com/vektah/dataloaden MovieLoader int *github.com/stashapp/stash/pkg/models.Movie
|
//go:generate go run github.com/vektah/dataloaden GroupLoader int *github.com/stashapp/stash/pkg/models.Group
|
||||||
//go:generate go run github.com/vektah/dataloaden FileLoader github.com/stashapp/stash/pkg/models.FileID github.com/stashapp/stash/pkg/models.File
|
//go:generate go run github.com/vektah/dataloaden FileLoader github.com/stashapp/stash/pkg/models.FileID github.com/stashapp/stash/pkg/models.File
|
||||||
|
//go:generate go run github.com/vektah/dataloaden FolderLoader github.com/stashapp/stash/pkg/models.FolderID *github.com/stashapp/stash/pkg/models.Folder
|
||||||
//go:generate go run github.com/vektah/dataloaden SceneFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
|
//go:generate go run github.com/vektah/dataloaden SceneFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
|
||||||
//go:generate go run github.com/vektah/dataloaden ImageFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
|
//go:generate go run github.com/vektah/dataloaden ImageFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
|
||||||
//go:generate go run github.com/vektah/dataloaden GalleryFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
|
//go:generate go run github.com/vektah/dataloaden GalleryFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
|
||||||
|
//go:generate go run github.com/vektah/dataloaden CustomFieldsLoader int github.com/stashapp/stash/pkg/models.CustomFieldMap
|
||||||
//go:generate go run github.com/vektah/dataloaden SceneOCountLoader int int
|
//go:generate go run github.com/vektah/dataloaden SceneOCountLoader int int
|
||||||
//go:generate go run github.com/vektah/dataloaden ScenePlayCountLoader int int
|
//go:generate go run github.com/vektah/dataloaden ScenePlayCountLoader int int
|
||||||
//go:generate go run github.com/vektah/dataloaden SceneOHistoryLoader int []time.Time
|
//go:generate go run github.com/vektah/dataloaden SceneOHistoryLoader int []time.Time
|
||||||
|
|
@ -47,13 +53,17 @@ type Loaders struct {
|
||||||
ImageFiles *ImageFileIDsLoader
|
ImageFiles *ImageFileIDsLoader
|
||||||
GalleryFiles *GalleryFileIDsLoader
|
GalleryFiles *GalleryFileIDsLoader
|
||||||
|
|
||||||
GalleryByID *GalleryLoader
|
GalleryByID *GalleryLoader
|
||||||
ImageByID *ImageLoader
|
ImageByID *ImageLoader
|
||||||
PerformerByID *PerformerLoader
|
|
||||||
StudioByID *StudioLoader
|
PerformerByID *PerformerLoader
|
||||||
TagByID *TagLoader
|
PerformerCustomFields *CustomFieldsLoader
|
||||||
MovieByID *MovieLoader
|
|
||||||
FileByID *FileLoader
|
StudioByID *StudioLoader
|
||||||
|
TagByID *TagLoader
|
||||||
|
GroupByID *GroupLoader
|
||||||
|
FileByID *FileLoader
|
||||||
|
FolderByID *FolderLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
type Middleware struct {
|
type Middleware struct {
|
||||||
|
|
@ -84,6 +94,11 @@ func (m Middleware) Middleware(next http.Handler) http.Handler {
|
||||||
maxBatch: maxBatch,
|
maxBatch: maxBatch,
|
||||||
fetch: m.fetchPerformers(ctx),
|
fetch: m.fetchPerformers(ctx),
|
||||||
},
|
},
|
||||||
|
PerformerCustomFields: &CustomFieldsLoader{
|
||||||
|
wait: wait,
|
||||||
|
maxBatch: maxBatch,
|
||||||
|
fetch: m.fetchPerformerCustomFields(ctx),
|
||||||
|
},
|
||||||
StudioByID: &StudioLoader{
|
StudioByID: &StudioLoader{
|
||||||
wait: wait,
|
wait: wait,
|
||||||
maxBatch: maxBatch,
|
maxBatch: maxBatch,
|
||||||
|
|
@ -94,16 +109,21 @@ func (m Middleware) Middleware(next http.Handler) http.Handler {
|
||||||
maxBatch: maxBatch,
|
maxBatch: maxBatch,
|
||||||
fetch: m.fetchTags(ctx),
|
fetch: m.fetchTags(ctx),
|
||||||
},
|
},
|
||||||
MovieByID: &MovieLoader{
|
GroupByID: &GroupLoader{
|
||||||
wait: wait,
|
wait: wait,
|
||||||
maxBatch: maxBatch,
|
maxBatch: maxBatch,
|
||||||
fetch: m.fetchMovies(ctx),
|
fetch: m.fetchGroups(ctx),
|
||||||
},
|
},
|
||||||
FileByID: &FileLoader{
|
FileByID: &FileLoader{
|
||||||
wait: wait,
|
wait: wait,
|
||||||
maxBatch: maxBatch,
|
maxBatch: maxBatch,
|
||||||
fetch: m.fetchFiles(ctx),
|
fetch: m.fetchFiles(ctx),
|
||||||
},
|
},
|
||||||
|
FolderByID: &FolderLoader{
|
||||||
|
wait: wait,
|
||||||
|
maxBatch: maxBatch,
|
||||||
|
fetch: m.fetchFolders(ctx),
|
||||||
|
},
|
||||||
SceneFiles: &SceneFileIDsLoader{
|
SceneFiles: &SceneFileIDsLoader{
|
||||||
wait: wait,
|
wait: wait,
|
||||||
maxBatch: maxBatch,
|
maxBatch: maxBatch,
|
||||||
|
|
@ -210,6 +230,18 @@ func (m Middleware) fetchPerformers(ctx context.Context) func(keys []int) ([]*mo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Middleware) fetchPerformerCustomFields(ctx context.Context) func(keys []int) ([]models.CustomFieldMap, []error) {
|
||||||
|
return func(keys []int) (ret []models.CustomFieldMap, errs []error) {
|
||||||
|
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
ret, err = m.Repository.Performer.GetCustomFieldsBulk(ctx, keys)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret, toErrorSlice(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m Middleware) fetchStudios(ctx context.Context) func(keys []int) ([]*models.Studio, []error) {
|
func (m Middleware) fetchStudios(ctx context.Context) func(keys []int) ([]*models.Studio, []error) {
|
||||||
return func(keys []int) (ret []*models.Studio, errs []error) {
|
return func(keys []int) (ret []*models.Studio, errs []error) {
|
||||||
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
|
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
|
||||||
|
|
@ -232,11 +264,11 @@ func (m Middleware) fetchTags(ctx context.Context) func(keys []int) ([]*models.T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Middleware) fetchMovies(ctx context.Context) func(keys []int) ([]*models.Movie, []error) {
|
func (m Middleware) fetchGroups(ctx context.Context) func(keys []int) ([]*models.Group, []error) {
|
||||||
return func(keys []int) (ret []*models.Movie, errs []error) {
|
return func(keys []int) (ret []*models.Group, errs []error) {
|
||||||
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
|
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
ret, err = m.Repository.Movie.FindMany(ctx, keys)
|
ret, err = m.Repository.Group.FindMany(ctx, keys)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
return ret, toErrorSlice(err)
|
return ret, toErrorSlice(err)
|
||||||
|
|
@ -254,6 +286,17 @@ func (m Middleware) fetchFiles(ctx context.Context) func(keys []models.FileID) (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Middleware) fetchFolders(ctx context.Context) func(keys []models.FolderID) ([]*models.Folder, []error) {
|
||||||
|
return func(keys []models.FolderID) (ret []*models.Folder, errs []error) {
|
||||||
|
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
|
||||||
|
var err error
|
||||||
|
ret, err = m.Repository.Folder.FindMany(ctx, keys)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return ret, toErrorSlice(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([][]models.FileID, []error) {
|
func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([][]models.FileID, []error) {
|
||||||
return func(keys []int) (ret [][]models.FileID, errs []error) {
|
return func(keys []int) (ret [][]models.FileID, errs []error) {
|
||||||
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
|
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
|
||||||
|
|
|
||||||
224
internal/api/loaders/folderloader_gen.go
Normal file
224
internal/api/loaders/folderloader_gen.go
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
|
||||||
|
|
||||||
|
package loaders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FolderLoaderConfig captures the config to create a new FolderLoader
|
||||||
|
type FolderLoaderConfig struct {
|
||||||
|
// Fetch is a method that provides the data for the loader
|
||||||
|
Fetch func(keys []models.FolderID) ([]*models.Folder, []error)
|
||||||
|
|
||||||
|
// Wait is how long wait before sending a batch
|
||||||
|
Wait time.Duration
|
||||||
|
|
||||||
|
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
|
||||||
|
MaxBatch int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFolderLoader creates a new FolderLoader given a fetch, wait, and maxBatch
|
||||||
|
func NewFolderLoader(config FolderLoaderConfig) *FolderLoader {
|
||||||
|
return &FolderLoader{
|
||||||
|
fetch: config.Fetch,
|
||||||
|
wait: config.Wait,
|
||||||
|
maxBatch: config.MaxBatch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FolderLoader batches and caches requests
|
||||||
|
type FolderLoader struct {
|
||||||
|
// this method provides the data for the loader
|
||||||
|
fetch func(keys []models.FolderID) ([]*models.Folder, []error)
|
||||||
|
|
||||||
|
// how long to done before sending a batch
|
||||||
|
wait time.Duration
|
||||||
|
|
||||||
|
// this will limit the maximum number of keys to send in one batch, 0 = no limit
|
||||||
|
maxBatch int
|
||||||
|
|
||||||
|
// INTERNAL
|
||||||
|
|
||||||
|
// lazily created cache
|
||||||
|
cache map[models.FolderID]*models.Folder
|
||||||
|
|
||||||
|
// the current batch. keys will continue to be collected until timeout is hit,
|
||||||
|
// then everything will be sent to the fetch method and out to the listeners
|
||||||
|
batch *folderLoaderBatch
|
||||||
|
|
||||||
|
// mutex to prevent races
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type folderLoaderBatch struct {
|
||||||
|
keys []models.FolderID
|
||||||
|
data []*models.Folder
|
||||||
|
error []error
|
||||||
|
closing bool
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a Folder by key, batching and caching will be applied automatically
|
||||||
|
func (l *FolderLoader) Load(key models.FolderID) (*models.Folder, error) {
|
||||||
|
return l.LoadThunk(key)()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadThunk returns a function that when called will block waiting for a Folder.
|
||||||
|
// This method should be used if you want one goroutine to make requests to many
|
||||||
|
// different data loaders without blocking until the thunk is called.
|
||||||
|
func (l *FolderLoader) LoadThunk(key models.FolderID) func() (*models.Folder, error) {
|
||||||
|
l.mu.Lock()
|
||||||
|
if it, ok := l.cache[key]; ok {
|
||||||
|
l.mu.Unlock()
|
||||||
|
return func() (*models.Folder, error) {
|
||||||
|
return it, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.batch == nil {
|
||||||
|
l.batch = &folderLoaderBatch{done: make(chan struct{})}
|
||||||
|
}
|
||||||
|
batch := l.batch
|
||||||
|
pos := batch.keyIndex(l, key)
|
||||||
|
l.mu.Unlock()
|
||||||
|
|
||||||
|
return func() (*models.Folder, error) {
|
||||||
|
<-batch.done
|
||||||
|
|
||||||
|
var data *models.Folder
|
||||||
|
if pos < len(batch.data) {
|
||||||
|
data = batch.data[pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// its convenient to be able to return a single error for everything
|
||||||
|
if len(batch.error) == 1 {
|
||||||
|
err = batch.error[0]
|
||||||
|
} else if batch.error != nil {
|
||||||
|
err = batch.error[pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
l.mu.Lock()
|
||||||
|
l.unsafeSet(key, data)
|
||||||
|
l.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAll fetches many keys at once. It will be broken into appropriate sized
|
||||||
|
// sub batches depending on how the loader is configured
|
||||||
|
func (l *FolderLoader) LoadAll(keys []models.FolderID) ([]*models.Folder, []error) {
|
||||||
|
results := make([]func() (*models.Folder, error), len(keys))
|
||||||
|
|
||||||
|
for i, key := range keys {
|
||||||
|
results[i] = l.LoadThunk(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
folders := make([]*models.Folder, len(keys))
|
||||||
|
errors := make([]error, len(keys))
|
||||||
|
for i, thunk := range results {
|
||||||
|
folders[i], errors[i] = thunk()
|
||||||
|
}
|
||||||
|
return folders, errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAllThunk returns a function that when called will block waiting for a Folders.
|
||||||
|
// This method should be used if you want one goroutine to make requests to many
|
||||||
|
// different data loaders without blocking until the thunk is called.
|
||||||
|
func (l *FolderLoader) LoadAllThunk(keys []models.FolderID) func() ([]*models.Folder, []error) {
|
||||||
|
results := make([]func() (*models.Folder, error), len(keys))
|
||||||
|
for i, key := range keys {
|
||||||
|
results[i] = l.LoadThunk(key)
|
||||||
|
}
|
||||||
|
return func() ([]*models.Folder, []error) {
|
||||||
|
folders := make([]*models.Folder, len(keys))
|
||||||
|
errors := make([]error, len(keys))
|
||||||
|
for i, thunk := range results {
|
||||||
|
folders[i], errors[i] = thunk()
|
||||||
|
}
|
||||||
|
return folders, errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prime the cache with the provided key and value. If the key already exists, no change is made
|
||||||
|
// and false is returned.
|
||||||
|
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
|
||||||
|
func (l *FolderLoader) Prime(key models.FolderID, value *models.Folder) bool {
|
||||||
|
l.mu.Lock()
|
||||||
|
var found bool
|
||||||
|
if _, found = l.cache[key]; !found {
|
||||||
|
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
|
||||||
|
// and end up with the whole cache pointing to the same value.
|
||||||
|
cpy := *value
|
||||||
|
l.unsafeSet(key, &cpy)
|
||||||
|
}
|
||||||
|
l.mu.Unlock()
|
||||||
|
return !found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the value at key from the cache, if it exists
|
||||||
|
func (l *FolderLoader) Clear(key models.FolderID) {
|
||||||
|
l.mu.Lock()
|
||||||
|
delete(l.cache, key)
|
||||||
|
l.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *FolderLoader) unsafeSet(key models.FolderID, value *models.Folder) {
|
||||||
|
if l.cache == nil {
|
||||||
|
l.cache = map[models.FolderID]*models.Folder{}
|
||||||
|
}
|
||||||
|
l.cache[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyIndex will return the location of the key in the batch, if its not found
|
||||||
|
// it will add the key to the batch
|
||||||
|
func (b *folderLoaderBatch) keyIndex(l *FolderLoader, key models.FolderID) int {
|
||||||
|
for i, existingKey := range b.keys {
|
||||||
|
if key == existingKey {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := len(b.keys)
|
||||||
|
b.keys = append(b.keys, key)
|
||||||
|
if pos == 0 {
|
||||||
|
go b.startTimer(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
|
||||||
|
if !b.closing {
|
||||||
|
b.closing = true
|
||||||
|
l.batch = nil
|
||||||
|
go b.end(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *folderLoaderBatch) startTimer(l *FolderLoader) {
|
||||||
|
time.Sleep(l.wait)
|
||||||
|
l.mu.Lock()
|
||||||
|
|
||||||
|
// we must have hit a batch limit and are already finalizing this batch
|
||||||
|
if b.closing {
|
||||||
|
l.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.batch = nil
|
||||||
|
l.mu.Unlock()
|
||||||
|
|
||||||
|
b.end(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *folderLoaderBatch) end(l *FolderLoader) {
|
||||||
|
b.data, b.error = l.fetch(b.keys)
|
||||||
|
close(b.done)
|
||||||
|
}
|
||||||
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MovieLoaderConfig captures the config to create a new MovieLoader
|
// GroupLoaderConfig captures the config to create a new GroupLoader
|
||||||
type MovieLoaderConfig struct {
|
type GroupLoaderConfig struct {
|
||||||
// Fetch is a method that provides the data for the loader
|
// Fetch is a method that provides the data for the loader
|
||||||
Fetch func(keys []int) ([]*models.Movie, []error)
|
Fetch func(keys []int) ([]*models.Group, []error)
|
||||||
|
|
||||||
// Wait is how long wait before sending a batch
|
// Wait is how long wait before sending a batch
|
||||||
Wait time.Duration
|
Wait time.Duration
|
||||||
|
|
@ -21,19 +21,19 @@ type MovieLoaderConfig struct {
|
||||||
MaxBatch int
|
MaxBatch int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMovieLoader creates a new MovieLoader given a fetch, wait, and maxBatch
|
// NewGroupLoader creates a new GroupLoader given a fetch, wait, and maxBatch
|
||||||
func NewMovieLoader(config MovieLoaderConfig) *MovieLoader {
|
func NewGroupLoader(config GroupLoaderConfig) *GroupLoader {
|
||||||
return &MovieLoader{
|
return &GroupLoader{
|
||||||
fetch: config.Fetch,
|
fetch: config.Fetch,
|
||||||
wait: config.Wait,
|
wait: config.Wait,
|
||||||
maxBatch: config.MaxBatch,
|
maxBatch: config.MaxBatch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MovieLoader batches and caches requests
|
// GroupLoader batches and caches requests
|
||||||
type MovieLoader struct {
|
type GroupLoader struct {
|
||||||
// this method provides the data for the loader
|
// this method provides the data for the loader
|
||||||
fetch func(keys []int) ([]*models.Movie, []error)
|
fetch func(keys []int) ([]*models.Group, []error)
|
||||||
|
|
||||||
// how long to done before sending a batch
|
// how long to done before sending a batch
|
||||||
wait time.Duration
|
wait time.Duration
|
||||||
|
|
@ -44,51 +44,51 @@ type MovieLoader struct {
|
||||||
// INTERNAL
|
// INTERNAL
|
||||||
|
|
||||||
// lazily created cache
|
// lazily created cache
|
||||||
cache map[int]*models.Movie
|
cache map[int]*models.Group
|
||||||
|
|
||||||
// the current batch. keys will continue to be collected until timeout is hit,
|
// the current batch. keys will continue to be collected until timeout is hit,
|
||||||
// then everything will be sent to the fetch method and out to the listeners
|
// then everything will be sent to the fetch method and out to the listeners
|
||||||
batch *movieLoaderBatch
|
batch *groupLoaderBatch
|
||||||
|
|
||||||
// mutex to prevent races
|
// mutex to prevent races
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type movieLoaderBatch struct {
|
type groupLoaderBatch struct {
|
||||||
keys []int
|
keys []int
|
||||||
data []*models.Movie
|
data []*models.Group
|
||||||
error []error
|
error []error
|
||||||
closing bool
|
closing bool
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load a Movie by key, batching and caching will be applied automatically
|
// Load a Group by key, batching and caching will be applied automatically
|
||||||
func (l *MovieLoader) Load(key int) (*models.Movie, error) {
|
func (l *GroupLoader) Load(key int) (*models.Group, error) {
|
||||||
return l.LoadThunk(key)()
|
return l.LoadThunk(key)()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadThunk returns a function that when called will block waiting for a Movie.
|
// LoadThunk returns a function that when called will block waiting for a Group.
|
||||||
// This method should be used if you want one goroutine to make requests to many
|
// This method should be used if you want one goroutine to make requests to many
|
||||||
// different data loaders without blocking until the thunk is called.
|
// different data loaders without blocking until the thunk is called.
|
||||||
func (l *MovieLoader) LoadThunk(key int) func() (*models.Movie, error) {
|
func (l *GroupLoader) LoadThunk(key int) func() (*models.Group, error) {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
if it, ok := l.cache[key]; ok {
|
if it, ok := l.cache[key]; ok {
|
||||||
l.mu.Unlock()
|
l.mu.Unlock()
|
||||||
return func() (*models.Movie, error) {
|
return func() (*models.Group, error) {
|
||||||
return it, nil
|
return it, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if l.batch == nil {
|
if l.batch == nil {
|
||||||
l.batch = &movieLoaderBatch{done: make(chan struct{})}
|
l.batch = &groupLoaderBatch{done: make(chan struct{})}
|
||||||
}
|
}
|
||||||
batch := l.batch
|
batch := l.batch
|
||||||
pos := batch.keyIndex(l, key)
|
pos := batch.keyIndex(l, key)
|
||||||
l.mu.Unlock()
|
l.mu.Unlock()
|
||||||
|
|
||||||
return func() (*models.Movie, error) {
|
return func() (*models.Group, error) {
|
||||||
<-batch.done
|
<-batch.done
|
||||||
|
|
||||||
var data *models.Movie
|
var data *models.Group
|
||||||
if pos < len(batch.data) {
|
if pos < len(batch.data) {
|
||||||
data = batch.data[pos]
|
data = batch.data[pos]
|
||||||
}
|
}
|
||||||
|
|
@ -113,43 +113,43 @@ func (l *MovieLoader) LoadThunk(key int) func() (*models.Movie, error) {
|
||||||
|
|
||||||
// LoadAll fetches many keys at once. It will be broken into appropriate sized
|
// LoadAll fetches many keys at once. It will be broken into appropriate sized
|
||||||
// sub batches depending on how the loader is configured
|
// sub batches depending on how the loader is configured
|
||||||
func (l *MovieLoader) LoadAll(keys []int) ([]*models.Movie, []error) {
|
func (l *GroupLoader) LoadAll(keys []int) ([]*models.Group, []error) {
|
||||||
results := make([]func() (*models.Movie, error), len(keys))
|
results := make([]func() (*models.Group, error), len(keys))
|
||||||
|
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
results[i] = l.LoadThunk(key)
|
results[i] = l.LoadThunk(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
movies := make([]*models.Movie, len(keys))
|
groups := make([]*models.Group, len(keys))
|
||||||
errors := make([]error, len(keys))
|
errors := make([]error, len(keys))
|
||||||
for i, thunk := range results {
|
for i, thunk := range results {
|
||||||
movies[i], errors[i] = thunk()
|
groups[i], errors[i] = thunk()
|
||||||
}
|
}
|
||||||
return movies, errors
|
return groups, errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAllThunk returns a function that when called will block waiting for a Movies.
|
// LoadAllThunk returns a function that when called will block waiting for a Groups.
|
||||||
// This method should be used if you want one goroutine to make requests to many
|
// This method should be used if you want one goroutine to make requests to many
|
||||||
// different data loaders without blocking until the thunk is called.
|
// different data loaders without blocking until the thunk is called.
|
||||||
func (l *MovieLoader) LoadAllThunk(keys []int) func() ([]*models.Movie, []error) {
|
func (l *GroupLoader) LoadAllThunk(keys []int) func() ([]*models.Group, []error) {
|
||||||
results := make([]func() (*models.Movie, error), len(keys))
|
results := make([]func() (*models.Group, error), len(keys))
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
results[i] = l.LoadThunk(key)
|
results[i] = l.LoadThunk(key)
|
||||||
}
|
}
|
||||||
return func() ([]*models.Movie, []error) {
|
return func() ([]*models.Group, []error) {
|
||||||
movies := make([]*models.Movie, len(keys))
|
groups := make([]*models.Group, len(keys))
|
||||||
errors := make([]error, len(keys))
|
errors := make([]error, len(keys))
|
||||||
for i, thunk := range results {
|
for i, thunk := range results {
|
||||||
movies[i], errors[i] = thunk()
|
groups[i], errors[i] = thunk()
|
||||||
}
|
}
|
||||||
return movies, errors
|
return groups, errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prime the cache with the provided key and value. If the key already exists, no change is made
|
// Prime the cache with the provided key and value. If the key already exists, no change is made
|
||||||
// and false is returned.
|
// and false is returned.
|
||||||
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
|
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
|
||||||
func (l *MovieLoader) Prime(key int, value *models.Movie) bool {
|
func (l *GroupLoader) Prime(key int, value *models.Group) bool {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
var found bool
|
var found bool
|
||||||
if _, found = l.cache[key]; !found {
|
if _, found = l.cache[key]; !found {
|
||||||
|
|
@ -163,22 +163,22 @@ func (l *MovieLoader) Prime(key int, value *models.Movie) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the value at key from the cache, if it exists
|
// Clear the value at key from the cache, if it exists
|
||||||
func (l *MovieLoader) Clear(key int) {
|
func (l *GroupLoader) Clear(key int) {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
delete(l.cache, key)
|
delete(l.cache, key)
|
||||||
l.mu.Unlock()
|
l.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *MovieLoader) unsafeSet(key int, value *models.Movie) {
|
func (l *GroupLoader) unsafeSet(key int, value *models.Group) {
|
||||||
if l.cache == nil {
|
if l.cache == nil {
|
||||||
l.cache = map[int]*models.Movie{}
|
l.cache = map[int]*models.Group{}
|
||||||
}
|
}
|
||||||
l.cache[key] = value
|
l.cache[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyIndex will return the location of the key in the batch, if its not found
|
// keyIndex will return the location of the key in the batch, if its not found
|
||||||
// it will add the key to the batch
|
// it will add the key to the batch
|
||||||
func (b *movieLoaderBatch) keyIndex(l *MovieLoader, key int) int {
|
func (b *groupLoaderBatch) keyIndex(l *GroupLoader, key int) int {
|
||||||
for i, existingKey := range b.keys {
|
for i, existingKey := range b.keys {
|
||||||
if key == existingKey {
|
if key == existingKey {
|
||||||
return i
|
return i
|
||||||
|
|
@ -202,7 +202,7 @@ func (b *movieLoaderBatch) keyIndex(l *MovieLoader, key int) int {
|
||||||
return pos
|
return pos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *movieLoaderBatch) startTimer(l *MovieLoader) {
|
func (b *groupLoaderBatch) startTimer(l *GroupLoader) {
|
||||||
time.Sleep(l.wait)
|
time.Sleep(l.wait)
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
|
|
||||||
|
|
@ -218,7 +218,7 @@ func (b *movieLoaderBatch) startTimer(l *MovieLoader) {
|
||||||
b.end(l)
|
b.end(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *movieLoaderBatch) end(l *MovieLoader) {
|
func (b *groupLoaderBatch) end(l *GroupLoader) {
|
||||||
b.data, b.error = l.fetch(b.keys)
|
b.data, b.error = l.fetch(b.keys)
|
||||||
close(b.done)
|
close(b.done)
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/sliceutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseFile interface {
|
type BaseFile interface {
|
||||||
|
|
@ -27,6 +28,29 @@ func convertVisualFile(f models.File) (VisualFile, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertBaseFile(f models.File) BaseFile {
|
||||||
|
if f == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f := f.(type) {
|
||||||
|
case BaseFile:
|
||||||
|
return f
|
||||||
|
case *models.VideoFile:
|
||||||
|
return &VideoFile{VideoFile: f}
|
||||||
|
case *models.ImageFile:
|
||||||
|
return &ImageFile{ImageFile: f}
|
||||||
|
case *models.BaseFile:
|
||||||
|
return &BasicFile{BaseFile: f}
|
||||||
|
default:
|
||||||
|
panic("unknown file type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertBaseFiles(files []models.File) []BaseFile {
|
||||||
|
return sliceutil.Map(files, convertBaseFile)
|
||||||
|
}
|
||||||
|
|
||||||
type GalleryFile struct {
|
type GalleryFile struct {
|
||||||
*models.BaseFile
|
*models.BaseFile
|
||||||
}
|
}
|
||||||
|
|
@ -62,3 +86,15 @@ func (ImageFile) IsVisualFile() {}
|
||||||
func (f *ImageFile) Fingerprints() []models.Fingerprint {
|
func (f *ImageFile) Fingerprints() []models.Fingerprint {
|
||||||
return f.ImageFile.Fingerprints
|
return f.ImageFile.Fingerprints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BasicFile struct {
|
||||||
|
*models.BaseFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (BasicFile) IsBaseFile() {}
|
||||||
|
|
||||||
|
func (BasicFile) IsVisualFile() {}
|
||||||
|
|
||||||
|
func (f *BasicFile) Fingerprints() []models.Fingerprint {
|
||||||
|
return f.BaseFile.Fingerprints
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/plugin/hook"
|
"github.com/stashapp/stash/pkg/plugin/hook"
|
||||||
"github.com/stashapp/stash/pkg/scraper"
|
"github.com/stashapp/stash/pkg/scraper"
|
||||||
"github.com/stashapp/stash/pkg/scraper/stashbox"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -37,6 +36,7 @@ type Resolver struct {
|
||||||
sceneService manager.SceneService
|
sceneService manager.SceneService
|
||||||
imageService manager.ImageService
|
imageService manager.ImageService
|
||||||
galleryService manager.GalleryService
|
galleryService manager.GalleryService
|
||||||
|
groupService manager.GroupService
|
||||||
|
|
||||||
hookExecutor hookExecutor
|
hookExecutor hookExecutor
|
||||||
}
|
}
|
||||||
|
|
@ -72,9 +72,14 @@ func (r *Resolver) SceneMarker() SceneMarkerResolver {
|
||||||
func (r *Resolver) Studio() StudioResolver {
|
func (r *Resolver) Studio() StudioResolver {
|
||||||
return &studioResolver{r}
|
return &studioResolver{r}
|
||||||
}
|
}
|
||||||
func (r *Resolver) Movie() MovieResolver {
|
|
||||||
return &movieResolver{r}
|
func (r *Resolver) Group() GroupResolver {
|
||||||
|
return &groupResolver{r}
|
||||||
}
|
}
|
||||||
|
func (r *Resolver) Movie() MovieResolver {
|
||||||
|
return &movieResolver{&groupResolver{r}}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Resolver) Subscription() SubscriptionResolver {
|
func (r *Resolver) Subscription() SubscriptionResolver {
|
||||||
return &subscriptionResolver{r}
|
return &subscriptionResolver{r}
|
||||||
}
|
}
|
||||||
|
|
@ -90,6 +95,12 @@ func (r *Resolver) VideoFile() VideoFileResolver {
|
||||||
func (r *Resolver) ImageFile() ImageFileResolver {
|
func (r *Resolver) ImageFile() ImageFileResolver {
|
||||||
return &imageFileResolver{r}
|
return &imageFileResolver{r}
|
||||||
}
|
}
|
||||||
|
func (r *Resolver) BasicFile() BasicFileResolver {
|
||||||
|
return &basicFileResolver{r}
|
||||||
|
}
|
||||||
|
func (r *Resolver) Folder() FolderResolver {
|
||||||
|
return &folderResolver{r}
|
||||||
|
}
|
||||||
func (r *Resolver) SavedFilter() SavedFilterResolver {
|
func (r *Resolver) SavedFilter() SavedFilterResolver {
|
||||||
return &savedFilterResolver{r}
|
return &savedFilterResolver{r}
|
||||||
}
|
}
|
||||||
|
|
@ -111,11 +122,17 @@ type sceneResolver struct{ *Resolver }
|
||||||
type sceneMarkerResolver struct{ *Resolver }
|
type sceneMarkerResolver struct{ *Resolver }
|
||||||
type imageResolver struct{ *Resolver }
|
type imageResolver struct{ *Resolver }
|
||||||
type studioResolver struct{ *Resolver }
|
type studioResolver struct{ *Resolver }
|
||||||
type movieResolver struct{ *Resolver }
|
|
||||||
|
// movie is group under the hood
|
||||||
|
type groupResolver struct{ *Resolver }
|
||||||
|
type movieResolver struct{ *groupResolver }
|
||||||
|
|
||||||
type tagResolver struct{ *Resolver }
|
type tagResolver struct{ *Resolver }
|
||||||
type galleryFileResolver struct{ *Resolver }
|
type galleryFileResolver struct{ *Resolver }
|
||||||
type videoFileResolver struct{ *Resolver }
|
type videoFileResolver struct{ *Resolver }
|
||||||
type imageFileResolver struct{ *Resolver }
|
type imageFileResolver struct{ *Resolver }
|
||||||
|
type basicFileResolver struct{ *Resolver }
|
||||||
|
type folderResolver struct{ *Resolver }
|
||||||
type savedFilterResolver struct{ *Resolver }
|
type savedFilterResolver struct{ *Resolver }
|
||||||
type pluginResolver struct{ *Resolver }
|
type pluginResolver struct{ *Resolver }
|
||||||
type configResultResolver struct{ *Resolver }
|
type configResultResolver struct{ *Resolver }
|
||||||
|
|
@ -128,10 +145,6 @@ func (r *Resolver) withReadTxn(ctx context.Context, fn func(ctx context.Context)
|
||||||
return r.repository.WithReadTxn(ctx, fn)
|
return r.repository.WithReadTxn(ctx, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) stashboxRepository() stashbox.Repository {
|
|
||||||
return stashbox.NewRepository(r.repository)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*models.SceneMarker, err error) {
|
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*models.SceneMarker, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.SceneMarker.Wall(ctx, q)
|
ret, err = r.repository.SceneMarker.Wall(ctx, q)
|
||||||
|
|
@ -173,7 +186,7 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
|
||||||
galleryQB := repo.Gallery
|
galleryQB := repo.Gallery
|
||||||
studioQB := repo.Studio
|
studioQB := repo.Studio
|
||||||
performerQB := repo.Performer
|
performerQB := repo.Performer
|
||||||
movieQB := repo.Movie
|
movieQB := repo.Group
|
||||||
tagQB := repo.Tag
|
tagQB := repo.Tag
|
||||||
|
|
||||||
// embrace the error
|
// embrace the error
|
||||||
|
|
@ -218,7 +231,7 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
moviesCount, err := movieQB.Count(ctx)
|
groupsCount, err := movieQB.Count(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +275,8 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
|
||||||
GalleryCount: galleryCount,
|
GalleryCount: galleryCount,
|
||||||
PerformerCount: performersCount,
|
PerformerCount: performersCount,
|
||||||
StudioCount: studiosCount,
|
StudioCount: studiosCount,
|
||||||
MovieCount: moviesCount,
|
GroupCount: groupsCount,
|
||||||
|
MovieCount: groupsCount,
|
||||||
TagCount: tagsCount,
|
TagCount: tagsCount,
|
||||||
TotalOCount: totalOCount,
|
TotalOCount: totalOCount,
|
||||||
TotalPlayDuration: totalPlayDuration,
|
TotalPlayDuration: totalPlayDuration,
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,80 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
func (r *galleryFileResolver) Fingerprint(ctx context.Context, obj *GalleryFile, type_ string) (*string, error) {
|
"github.com/stashapp/stash/internal/api/loaders"
|
||||||
fp := obj.BaseFile.Fingerprints.For(type_)
|
"github.com/stashapp/stash/pkg/models"
|
||||||
if fp != nil {
|
)
|
||||||
v := fp.Value()
|
|
||||||
return &v, nil
|
func fingerprintResolver(fp models.Fingerprints, type_ string) (*string, error) {
|
||||||
|
fingerprint := fp.For(type_)
|
||||||
|
if fingerprint != nil {
|
||||||
|
value := fingerprint.Value()
|
||||||
|
return &value, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *galleryFileResolver) Fingerprint(ctx context.Context, obj *GalleryFile, type_ string) (*string, error) {
|
||||||
|
return fingerprintResolver(obj.BaseFile.Fingerprints, type_)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *imageFileResolver) Fingerprint(ctx context.Context, obj *ImageFile, type_ string) (*string, error) {
|
func (r *imageFileResolver) Fingerprint(ctx context.Context, obj *ImageFile, type_ string) (*string, error) {
|
||||||
fp := obj.ImageFile.Fingerprints.For(type_)
|
return fingerprintResolver(obj.ImageFile.Fingerprints, type_)
|
||||||
if fp != nil {
|
|
||||||
v := fp.Value()
|
|
||||||
return &v, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *videoFileResolver) Fingerprint(ctx context.Context, obj *VideoFile, type_ string) (*string, error) {
|
func (r *videoFileResolver) Fingerprint(ctx context.Context, obj *VideoFile, type_ string) (*string, error) {
|
||||||
fp := obj.VideoFile.Fingerprints.For(type_)
|
return fingerprintResolver(obj.VideoFile.Fingerprints, type_)
|
||||||
if fp != nil {
|
}
|
||||||
v := fp.Value()
|
|
||||||
return &v, nil
|
func (r *basicFileResolver) Fingerprint(ctx context.Context, obj *BasicFile, type_ string) (*string, error) {
|
||||||
}
|
return fingerprintResolver(obj.BaseFile.Fingerprints, type_)
|
||||||
return nil, nil
|
}
|
||||||
|
|
||||||
|
func (r *galleryFileResolver) ParentFolder(ctx context.Context, obj *GalleryFile) (*models.Folder, error) {
|
||||||
|
return loaders.From(ctx).FolderByID.Load(obj.ParentFolderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *imageFileResolver) ParentFolder(ctx context.Context, obj *ImageFile) (*models.Folder, error) {
|
||||||
|
return loaders.From(ctx).FolderByID.Load(obj.ParentFolderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *videoFileResolver) ParentFolder(ctx context.Context, obj *VideoFile) (*models.Folder, error) {
|
||||||
|
return loaders.From(ctx).FolderByID.Load(obj.ParentFolderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *basicFileResolver) ParentFolder(ctx context.Context, obj *BasicFile) (*models.Folder, error) {
|
||||||
|
return loaders.From(ctx).FolderByID.Load(obj.ParentFolderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func zipFileResolver(ctx context.Context, zipFileID *models.FileID) (*BasicFile, error) {
|
||||||
|
if zipFileID == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := loaders.From(ctx).FileByID.Load(*zipFileID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BasicFile{
|
||||||
|
BaseFile: f.Base(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *galleryFileResolver) ZipFile(ctx context.Context, obj *GalleryFile) (*BasicFile, error) {
|
||||||
|
return zipFileResolver(ctx, obj.ZipFileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *imageFileResolver) ZipFile(ctx context.Context, obj *ImageFile) (*BasicFile, error) {
|
||||||
|
return zipFileResolver(ctx, obj.ZipFileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *videoFileResolver) ZipFile(ctx context.Context, obj *VideoFile) (*BasicFile, error) {
|
||||||
|
return zipFileResolver(ctx, obj.ZipFileID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *basicFileResolver) ZipFile(ctx context.Context, obj *BasicFile) (*BasicFile, error) {
|
||||||
|
return zipFileResolver(ctx, obj.ZipFileID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
internal/api/resolver_model_folder.go
Normal file
20
internal/api/resolver_model_folder.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/internal/api/loaders"
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *folderResolver) ParentFolder(ctx context.Context, obj *models.Folder) (*models.Folder, error) {
|
||||||
|
if obj.ParentFolderID == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaders.From(ctx).FolderByID.Load(*obj.ParentFolderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *folderResolver) ZipFile(ctx context.Context, obj *models.Folder) (*BasicFile, error) {
|
||||||
|
return zipFileResolver(ctx, obj.ZipFileID)
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,10 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/api/loaders"
|
"github.com/stashapp/stash/internal/api/loaders"
|
||||||
|
"github.com/stashapp/stash/internal/api/urlbuilders"
|
||||||
"github.com/stashapp/stash/internal/manager/config"
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/image"
|
"github.com/stashapp/stash/pkg/image"
|
||||||
|
|
@ -189,3 +191,28 @@ func (r *galleryResolver) Urls(ctx context.Context, obj *models.Gallery) ([]stri
|
||||||
|
|
||||||
return obj.URLs.List(), nil
|
return obj.URLs.List(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *galleryResolver) Paths(ctx context.Context, obj *models.Gallery) (*GalleryPathsType, error) {
|
||||||
|
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||||
|
builder := urlbuilders.NewGalleryURLBuilder(baseURL, obj)
|
||||||
|
|
||||||
|
return &GalleryPathsType{
|
||||||
|
Cover: builder.GetCoverURL(),
|
||||||
|
Preview: builder.GetPreviewURL(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *galleryResolver) Image(ctx context.Context, obj *models.Gallery, index int) (ret *models.Image, err error) {
|
||||||
|
if index < 0 {
|
||||||
|
return nil, fmt.Errorf("index must >= 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
ret, err = r.repository.Image.FindByGalleryIDIndex(ctx, obj.ID, uint(index))
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,6 @@ func (r *imageResolver) getFiles(ctx context.Context, obj *models.Image) ([]mode
|
||||||
return files, firstError(errs)
|
return files, firstError(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *imageResolver) Title(ctx context.Context, obj *models.Image) (*string, error) {
|
|
||||||
ret := obj.GetTitle()
|
|
||||||
return &ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *imageResolver) VisualFiles(ctx context.Context, obj *models.Image) ([]VisualFile, error) {
|
func (r *imageResolver) VisualFiles(ctx context.Context, obj *models.Image) ([]VisualFile, error) {
|
||||||
files, err := r.getFiles(ctx, obj)
|
files, err := r.getFiles(ctx, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,13 @@ import (
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/api/loaders"
|
"github.com/stashapp/stash/internal/api/loaders"
|
||||||
"github.com/stashapp/stash/internal/api/urlbuilders"
|
"github.com/stashapp/stash/internal/api/urlbuilders"
|
||||||
|
"github.com/stashapp/stash/pkg/group"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/performer"
|
||||||
|
"github.com/stashapp/stash/pkg/scene"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *movieResolver) Date(ctx context.Context, obj *models.Movie) (*string, error) {
|
func (r *groupResolver) Date(ctx context.Context, obj *models.Group) (*string, error) {
|
||||||
if obj.Date != nil {
|
if obj.Date != nil {
|
||||||
result := obj.Date.String()
|
result := obj.Date.String()
|
||||||
return &result, nil
|
return &result, nil
|
||||||
|
|
@ -16,11 +19,40 @@ func (r *movieResolver) Date(ctx context.Context, obj *models.Movie) (*string, e
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *movieResolver) Rating100(ctx context.Context, obj *models.Movie) (*int, error) {
|
func (r *groupResolver) Rating100(ctx context.Context, obj *models.Group) (*int, error) {
|
||||||
return obj.Rating, nil
|
return obj.Rating, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *movieResolver) Studio(ctx context.Context, obj *models.Movie) (ret *models.Studio, err error) {
|
func (r *groupResolver) URL(ctx context.Context, obj *models.Group) (*string, error) {
|
||||||
|
if !obj.URLs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadURLs(ctx, r.repository.Group)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urls := obj.URLs.List()
|
||||||
|
if len(urls) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &urls[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *groupResolver) Urls(ctx context.Context, obj *models.Group) ([]string, error) {
|
||||||
|
if !obj.URLs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadURLs(ctx, r.repository.Group)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.URLs.List(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *groupResolver) Studio(ctx context.Context, obj *models.Group) (ret *models.Studio, err error) {
|
||||||
if obj.StudioID == nil {
|
if obj.StudioID == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -28,26 +60,102 @@ func (r *movieResolver) Studio(ctx context.Context, obj *models.Movie) (ret *mod
|
||||||
return loaders.From(ctx).StudioByID.Load(*obj.StudioID)
|
return loaders.From(ctx).StudioByID.Load(*obj.StudioID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *movieResolver) FrontImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
|
func (r groupResolver) Tags(ctx context.Context, obj *models.Group) (ret []*models.Tag, err error) {
|
||||||
|
if !obj.TagIDs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadTagIDs(ctx, r.repository.Group)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
ret, errs = loaders.From(ctx).TagByID.LoadAll(obj.TagIDs.List())
|
||||||
|
return ret, firstError(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r groupResolver) relatedGroups(ctx context.Context, rgd models.RelatedGroupDescriptions) (ret []*GroupDescription, err error) {
|
||||||
|
// rgd must be loaded
|
||||||
|
gds := rgd.List()
|
||||||
|
ids := make([]int, len(gds))
|
||||||
|
for i, gd := range gds {
|
||||||
|
ids[i] = gd.GroupID
|
||||||
|
}
|
||||||
|
|
||||||
|
groups, errs := loaders.From(ctx).GroupByID.LoadAll(ids)
|
||||||
|
|
||||||
|
err = firstError(errs)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = make([]*GroupDescription, len(groups))
|
||||||
|
for i, group := range groups {
|
||||||
|
ret[i] = &GroupDescription{Group: group}
|
||||||
|
d := gds[i].Description
|
||||||
|
if d != "" {
|
||||||
|
ret[i].Description = &d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, firstError(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r groupResolver) ContainingGroups(ctx context.Context, obj *models.Group) (ret []*GroupDescription, err error) {
|
||||||
|
if !obj.ContainingGroups.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadContainingGroupIDs(ctx, r.repository.Group)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.relatedGroups(ctx, obj.ContainingGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r groupResolver) SubGroups(ctx context.Context, obj *models.Group) (ret []*GroupDescription, err error) {
|
||||||
|
if !obj.SubGroups.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadSubGroupIDs(ctx, r.repository.Group)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.relatedGroups(ctx, obj.SubGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *groupResolver) SubGroupCount(ctx context.Context, obj *models.Group, depth *int) (ret int, err error) {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
ret, err = group.CountByContainingGroupID(ctx, r.repository.Group, obj.ID, depth)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *groupResolver) FrontImagePath(ctx context.Context, obj *models.Group) (*string, error) {
|
||||||
var hasImage bool
|
var hasImage bool
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
hasImage, err = r.repository.Movie.HasFrontImage(ctx, obj.ID)
|
hasImage, err = r.repository.Group.HasFrontImage(ctx, obj.ID)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||||
imagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj).GetMovieFrontImageURL(hasImage)
|
imagePath := urlbuilders.NewGroupURLBuilder(baseURL, obj).GetGroupFrontImageURL(hasImage)
|
||||||
return &imagePath, nil
|
return &imagePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
|
func (r *groupResolver) BackImagePath(ctx context.Context, obj *models.Group) (*string, error) {
|
||||||
var hasImage bool
|
var hasImage bool
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
hasImage, err = r.repository.Movie.HasBackImage(ctx, obj.ID)
|
hasImage, err = r.repository.Group.HasBackImage(ctx, obj.ID)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -59,13 +167,13 @@ func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||||
imagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj).GetMovieBackImageURL()
|
imagePath := urlbuilders.NewGroupURLBuilder(baseURL, obj).GetGroupBackImageURL()
|
||||||
return &imagePath, nil
|
return &imagePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret int, err error) {
|
func (r *groupResolver) SceneCount(ctx context.Context, obj *models.Group, depth *int) (ret int, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Scene.CountByMovieID(ctx, obj.ID)
|
ret, err = scene.CountByGroupID(ctx, r.repository.Scene, obj.ID, depth)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
@ -74,10 +182,21 @@ func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*models.Scene, err error) {
|
func (r *groupResolver) PerformerCount(ctx context.Context, obj *models.Group, depth *int) (ret int, err error) {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
ret, err = performer.CountByGroupID(ctx, r.repository.Performer, obj.ID, depth)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *groupResolver) Scenes(ctx context.Context, obj *models.Group) (ret []*models.Scene, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
ret, err = r.repository.Scene.FindByMovieID(ctx, obj.ID)
|
ret, err = r.repository.Scene.FindByGroupID(ctx, obj.ID)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -85,3 +204,14 @@ func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*m
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *groupResolver) OCounter(ctx context.Context, obj *models.Group) (ret *int, err error) {
|
||||||
|
var count int
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
count, err = r.repository.Scene.OCountByGroupID(ctx, obj.ID)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &count, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,79 @@ func (r *performerResolver) AliasList(ctx context.Context, obj *models.Performer
|
||||||
return obj.Aliases.List(), nil
|
return obj.Aliases.List(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *performerResolver) URL(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||||
|
if !obj.URLs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadURLs(ctx, r.repository.Performer)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urls := obj.URLs.List()
|
||||||
|
if len(urls) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &urls[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *performerResolver) Twitter(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||||
|
if !obj.URLs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadURLs(ctx, r.repository.Performer)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urls := obj.URLs.List()
|
||||||
|
|
||||||
|
// find the first twitter url
|
||||||
|
for _, url := range urls {
|
||||||
|
if performer.IsTwitterURL(url) {
|
||||||
|
u := url
|
||||||
|
return &u, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *performerResolver) Instagram(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||||
|
if !obj.URLs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadURLs(ctx, r.repository.Performer)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urls := obj.URLs.List()
|
||||||
|
|
||||||
|
// find the first instagram url
|
||||||
|
for _, url := range urls {
|
||||||
|
if performer.IsInstagramURL(url) {
|
||||||
|
u := url
|
||||||
|
return &u, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *performerResolver) Urls(ctx context.Context, obj *models.Performer) ([]string, error) {
|
||||||
|
if !obj.URLs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadURLs(ctx, r.repository.Performer)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.URLs.List(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *performerResolver) Height(ctx context.Context, obj *models.Performer) (*string, error) {
|
func (r *performerResolver) Height(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||||
if obj.Height != nil {
|
if obj.Height != nil {
|
||||||
ret := strconv.Itoa(*obj.Height)
|
ret := strconv.Itoa(*obj.Height)
|
||||||
|
|
@ -106,9 +179,9 @@ func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Perfor
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
|
func (r *performerResolver) GroupCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Movie.CountByPerformerID(ctx, obj.ID)
|
ret, err = r.repository.Group.CountByPerformerID(ctx, obj.ID)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
@ -117,6 +190,11 @@ func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performe
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
|
||||||
|
return r.GroupCount(ctx, obj)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *performerResolver) PerformerCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
|
func (r *performerResolver) PerformerCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = performer.CountByAppearsWith(ctx, r.repository.Performer, obj.ID)
|
ret, err = performer.CountByAppearsWith(ctx, r.repository.Performer, obj.ID)
|
||||||
|
|
@ -179,9 +257,9 @@ func (r *performerResolver) DeathDate(ctx context.Context, obj *models.Performer
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) {
|
func (r *performerResolver) Groups(ctx context.Context, obj *models.Performer) (ret []*models.Group, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Movie.FindByPerformerID(ctx, obj.ID)
|
ret, err = r.repository.Group.FindByPerformerID(ctx, obj.ID)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -189,3 +267,21 @@ func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *performerResolver) CustomFields(ctx context.Context, obj *models.Performer) (map[string]interface{}, error) {
|
||||||
|
m, err := loaders.From(ctx).PerformerCustomFields.Load(obj.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m == nil {
|
||||||
|
return make(map[string]interface{}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Group, err error) {
|
||||||
|
return r.Groups(ctx, obj)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,20 +184,20 @@ func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (ret *mod
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*SceneMovie, err error) {
|
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*SceneMovie, err error) {
|
||||||
if !obj.Movies.Loaded() {
|
if !obj.Groups.Loaded() {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
qb := r.repository.Scene
|
qb := r.repository.Scene
|
||||||
|
|
||||||
return obj.LoadMovies(ctx, qb)
|
return obj.LoadGroups(ctx, qb)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loader := loaders.From(ctx).MovieByID
|
loader := loaders.From(ctx).GroupByID
|
||||||
|
|
||||||
for _, sm := range obj.Movies.List() {
|
for _, sm := range obj.Groups.List() {
|
||||||
movie, err := loader.Load(sm.MovieID)
|
movie, err := loader.Load(sm.GroupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -214,6 +214,37 @@ func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*S
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *sceneResolver) Groups(ctx context.Context, obj *models.Scene) (ret []*SceneGroup, err error) {
|
||||||
|
if !obj.Groups.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Scene
|
||||||
|
|
||||||
|
return obj.LoadGroups(ctx, qb)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loader := loaders.From(ctx).GroupByID
|
||||||
|
|
||||||
|
for _, sm := range obj.Groups.List() {
|
||||||
|
group, err := loader.Load(sm.GroupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneIdx := sm.SceneIndex
|
||||||
|
sceneGroup := &SceneGroup{
|
||||||
|
Group: group,
|
||||||
|
SceneIndex: sceneIdx,
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, sceneGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) (ret []*models.Tag, err error) {
|
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) (ret []*models.Tag, err error) {
|
||||||
if !obj.TagIDs.Loaded() {
|
if !obj.TagIDs.Loaded() {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"github.com/stashapp/stash/internal/api/loaders"
|
"github.com/stashapp/stash/internal/api/loaders"
|
||||||
"github.com/stashapp/stash/internal/api/urlbuilders"
|
"github.com/stashapp/stash/internal/api/urlbuilders"
|
||||||
"github.com/stashapp/stash/pkg/gallery"
|
"github.com/stashapp/stash/pkg/gallery"
|
||||||
|
"github.com/stashapp/stash/pkg/group"
|
||||||
"github.com/stashapp/stash/pkg/image"
|
"github.com/stashapp/stash/pkg/image"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/movie"
|
|
||||||
"github.com/stashapp/stash/pkg/performer"
|
"github.com/stashapp/stash/pkg/performer"
|
||||||
"github.com/stashapp/stash/pkg/scene"
|
"github.com/stashapp/stash/pkg/scene"
|
||||||
)
|
)
|
||||||
|
|
@ -40,6 +40,49 @@ func (r *studioResolver) Aliases(ctx context.Context, obj *models.Studio) ([]str
|
||||||
return obj.Aliases.List(), nil
|
return obj.Aliases.List(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *studioResolver) URL(ctx context.Context, obj *models.Studio) (*string, error) {
|
||||||
|
if !obj.URLs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadURLs(ctx, r.repository.Studio)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
urls := obj.URLs.List()
|
||||||
|
if len(urls) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &urls[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *studioResolver) Urls(ctx context.Context, obj *models.Studio) ([]string, error) {
|
||||||
|
if !obj.URLs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadURLs(ctx, r.repository.Studio)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.URLs.List(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *studioResolver) Tags(ctx context.Context, obj *models.Studio) (ret []*models.Tag, err error) {
|
||||||
|
if !obj.TagIDs.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadTagIDs(ctx, r.repository.Studio)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
ret, errs = loaders.From(ctx).TagByID.LoadAll(obj.TagIDs.List())
|
||||||
|
return ret, firstError(errs)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) {
|
func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = scene.CountByStudioID(ctx, r.repository.Scene, obj.ID, depth)
|
ret, err = scene.CountByStudioID(ctx, r.repository.Scene, obj.ID, depth)
|
||||||
|
|
@ -84,9 +127,9 @@ func (r *studioResolver) PerformerCount(ctx context.Context, obj *models.Studio,
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) {
|
func (r *studioResolver) GroupCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = movie.CountByStudioID(ctx, r.repository.Movie, obj.ID, depth)
|
ret, err = group.CountByStudioID(ctx, r.repository.Group, obj.ID, depth)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
@ -95,6 +138,29 @@ func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, dep
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
func (r *studioResolver) MovieCount(ctx context.Context, obj *models.Studio, depth *int) (ret int, err error) {
|
||||||
|
return r.GroupCount(ctx, obj, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *studioResolver) OCounter(ctx context.Context, obj *models.Studio) (ret *int, err error) {
|
||||||
|
var res_scene int
|
||||||
|
var res_image int
|
||||||
|
var res int
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
res_scene, err = r.repository.Scene.OCountByStudioID(ctx, obj.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res_image, err = r.repository.Image.OCountByStudioID(ctx, obj.ID)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = res_scene + res_image
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (ret *models.Studio, err error) {
|
func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (ret *models.Studio, err error) {
|
||||||
if obj.ParentID == nil {
|
if obj.ParentID == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
@ -130,9 +196,9 @@ func (r *studioResolver) Rating100(ctx context.Context, obj *models.Studio) (*in
|
||||||
return obj.Rating, nil
|
return obj.Rating, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret []*models.Movie, err error) {
|
func (r *studioResolver) Groups(ctx context.Context, obj *models.Studio) (ret []*models.Group, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Movie.FindByStudioID(ctx, obj.ID)
|
ret, err = r.repository.Group.FindByStudioID(ctx, obj.ID)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -140,3 +206,8 @@ func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret []
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deprecated
|
||||||
|
func (r *studioResolver) Movies(ctx context.Context, obj *models.Studio) (ret []*models.Group, err error) {
|
||||||
|
return r.Groups(ctx, obj)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,45 +3,65 @@ package api
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/internal/api/loaders"
|
||||||
"github.com/stashapp/stash/internal/api/urlbuilders"
|
"github.com/stashapp/stash/internal/api/urlbuilders"
|
||||||
"github.com/stashapp/stash/pkg/gallery"
|
"github.com/stashapp/stash/pkg/gallery"
|
||||||
|
"github.com/stashapp/stash/pkg/group"
|
||||||
"github.com/stashapp/stash/pkg/image"
|
"github.com/stashapp/stash/pkg/image"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/performer"
|
"github.com/stashapp/stash/pkg/performer"
|
||||||
"github.com/stashapp/stash/pkg/scene"
|
"github.com/stashapp/stash/pkg/scene"
|
||||||
|
"github.com/stashapp/stash/pkg/studio"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *tagResolver) Parents(ctx context.Context, obj *models.Tag) (ret []*models.Tag, err error) {
|
func (r *tagResolver) Parents(ctx context.Context, obj *models.Tag) (ret []*models.Tag, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if !obj.ParentIDs.Loaded() {
|
||||||
ret, err = r.repository.Tag.FindByChildTagID(ctx, obj.ID)
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
return err
|
return obj.LoadParentIDs(ctx, r.repository.Tag)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, nil
|
var errs []error
|
||||||
|
ret, errs = loaders.From(ctx).TagByID.LoadAll(obj.ParentIDs.List())
|
||||||
|
return ret, firstError(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tagResolver) Children(ctx context.Context, obj *models.Tag) (ret []*models.Tag, err error) {
|
func (r *tagResolver) Children(ctx context.Context, obj *models.Tag) (ret []*models.Tag, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if !obj.ChildIDs.Loaded() {
|
||||||
ret, err = r.repository.Tag.FindByParentTagID(ctx, obj.ID)
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
return err
|
return obj.LoadChildIDs(ctx, r.repository.Tag)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, nil
|
var errs []error
|
||||||
|
ret, errs = loaders.From(ctx).TagByID.LoadAll(obj.ChildIDs.List())
|
||||||
|
return ret, firstError(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tagResolver) Aliases(ctx context.Context, obj *models.Tag) (ret []string, err error) {
|
func (r *tagResolver) Aliases(ctx context.Context, obj *models.Tag) (ret []string, err error) {
|
||||||
|
if !obj.Aliases.Loaded() {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return obj.LoadAliases(ctx, r.repository.Tag)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.Aliases.List(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *tagResolver) StashIds(ctx context.Context, obj *models.Tag) ([]*models.StashID, error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Tag.GetAliases(ctx, obj.ID)
|
return obj.LoadStashIDs(ctx, r.repository.Tag)
|
||||||
return err
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, err
|
return stashIDsSliceToPtrSlice(obj.StashIDs.List()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) {
|
func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) {
|
||||||
|
|
@ -99,6 +119,32 @@ func (r *tagResolver) PerformerCount(ctx context.Context, obj *models.Tag, depth
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *tagResolver) StudioCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
ret, err = studio.CountByTagID(ctx, r.repository.Studio, obj.ID, depth)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *tagResolver) GroupCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
ret, err = group.CountByTagID(ctx, r.repository.Group, obj.ID, depth)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *tagResolver) MovieCount(ctx context.Context, obj *models.Tag, depth *int) (ret int, err error) {
|
||||||
|
return r.GroupCount(ctx, obj, depth)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *tagResolver) ImagePath(ctx context.Context, obj *models.Tag) (*string, error) {
|
func (r *tagResolver) ImagePath(ctx context.Context, obj *models.Tag) (*string, error) {
|
||||||
var hasImage bool
|
var hasImage bool
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -149,6 +150,15 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
|
||||||
c.SetString(config.BackupDirectoryPath, *input.BackupDirectoryPath)
|
c.SetString(config.BackupDirectoryPath, *input.BackupDirectoryPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
existingDeleteTrashPath := c.GetDeleteTrashPath()
|
||||||
|
if input.DeleteTrashPath != nil && existingDeleteTrashPath != *input.DeleteTrashPath {
|
||||||
|
if err := validateDir(config.DeleteTrashPath, *input.DeleteTrashPath, true); err != nil {
|
||||||
|
return makeConfigGeneralResult(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SetString(config.DeleteTrashPath, *input.DeleteTrashPath)
|
||||||
|
}
|
||||||
|
|
||||||
existingGeneratedPath := c.GetGeneratedPath()
|
existingGeneratedPath := c.GetGeneratedPath()
|
||||||
if input.GeneratedPath != nil && existingGeneratedPath != *input.GeneratedPath {
|
if input.GeneratedPath != nil && existingGeneratedPath != *input.GeneratedPath {
|
||||||
if err := validateDir(config.Generated, *input.GeneratedPath, false); err != nil {
|
if err := validateDir(config.Generated, *input.GeneratedPath, false); err != nil {
|
||||||
|
|
@ -333,6 +343,10 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
|
||||||
logger.SetLogLevel(*input.LogLevel)
|
logger.SetLogLevel(*input.LogLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.LogFileMaxSize != nil && *input.LogFileMaxSize != c.GetLogFileMaxSize() {
|
||||||
|
c.SetInt(config.LogFileMaxSize, *input.LogFileMaxSize)
|
||||||
|
}
|
||||||
|
|
||||||
if input.Excludes != nil {
|
if input.Excludes != nil {
|
||||||
for _, r := range input.Excludes {
|
for _, r := range input.Excludes {
|
||||||
_, err := regexp.Compile(r)
|
_, err := regexp.Compile(r)
|
||||||
|
|
@ -444,6 +458,8 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
|
||||||
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigInterfaceInput) (*ConfigInterfaceResult, error) {
|
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigInterfaceInput) (*ConfigInterfaceResult, error) {
|
||||||
c := config.GetInstance()
|
c := config.GetInstance()
|
||||||
|
|
||||||
|
r.setConfigBool(config.SFWContentMode, input.SfwContentMode)
|
||||||
|
|
||||||
if input.MenuItems != nil {
|
if input.MenuItems != nil {
|
||||||
c.SetInterface(config.MenuItems, input.MenuItems)
|
c.SetInterface(config.MenuItems, input.MenuItems)
|
||||||
}
|
}
|
||||||
|
|
@ -477,6 +493,8 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI
|
||||||
r.setConfigString(config.ImageLightboxScrollModeKey, (*string)(options.ScrollMode))
|
r.setConfigString(config.ImageLightboxScrollModeKey, (*string)(options.ScrollMode))
|
||||||
|
|
||||||
r.setConfigInt(config.ImageLightboxScrollAttemptsBeforeChange, options.ScrollAttemptsBeforeChange)
|
r.setConfigInt(config.ImageLightboxScrollAttemptsBeforeChange, options.ScrollAttemptsBeforeChange)
|
||||||
|
|
||||||
|
r.setConfigBool(config.ImageLightboxDisableAnimation, options.DisableAnimation)
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.CSS != nil {
|
if input.CSS != nil {
|
||||||
|
|
@ -643,10 +661,14 @@ func (r *mutationResolver) ConfigureUI(ctx context.Context, input map[string]int
|
||||||
c := config.GetInstance()
|
c := config.GetInstance()
|
||||||
|
|
||||||
if input != nil {
|
if input != nil {
|
||||||
|
// #5483 - convert JSON numbers to float64 or int64
|
||||||
|
input = convertMapJSONNumbers(input)
|
||||||
c.SetUIConfiguration(input)
|
c.SetUIConfiguration(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
if partial != nil {
|
if partial != nil {
|
||||||
|
// #5483 - convert JSON numbers to float64 or int64
|
||||||
|
partial = convertMapJSONNumbers(partial)
|
||||||
// merge partial into existing config
|
// merge partial into existing config
|
||||||
existing := c.GetUIConfiguration()
|
existing := c.GetUIConfiguration()
|
||||||
utils.MergeMaps(existing, partial)
|
utils.MergeMaps(existing, partial)
|
||||||
|
|
@ -664,6 +686,14 @@ func (r *mutationResolver) ConfigureUISetting(ctx context.Context, key string, v
|
||||||
c := config.GetInstance()
|
c := config.GetInstance()
|
||||||
|
|
||||||
cfg := utils.NestedMap(c.GetUIConfiguration())
|
cfg := utils.NestedMap(c.GetUIConfiguration())
|
||||||
|
|
||||||
|
// #5483 - convert JSON numbers to float64 or int64
|
||||||
|
if m, ok := value.(map[string]interface{}); ok {
|
||||||
|
value = convertMapJSONNumbers(m)
|
||||||
|
} else if n, ok := value.(json.Number); ok {
|
||||||
|
value = jsonNumberToNumber(n)
|
||||||
|
}
|
||||||
|
|
||||||
cfg.Set(key, value)
|
cfg.Set(key, value)
|
||||||
|
|
||||||
return r.ConfigureUI(ctx, cfg, nil)
|
return r.ConfigureUI(ctx, cfg, nil)
|
||||||
|
|
@ -671,6 +701,9 @@ func (r *mutationResolver) ConfigureUISetting(ctx context.Context, key string, v
|
||||||
|
|
||||||
func (r *mutationResolver) ConfigurePlugin(ctx context.Context, pluginID string, input map[string]interface{}) (map[string]interface{}, error) {
|
func (r *mutationResolver) ConfigurePlugin(ctx context.Context, pluginID string, input map[string]interface{}) (map[string]interface{}, error) {
|
||||||
c := config.GetInstance()
|
c := config.GetInstance()
|
||||||
|
|
||||||
|
// #5483 - convert JSON numbers to float64 or int64
|
||||||
|
input = convertMapJSONNumbers(input)
|
||||||
c.SetPluginConfiguration(pluginID, input)
|
c.SetPluginConfiguration(pluginID, input)
|
||||||
|
|
||||||
if err := c.Write(); err != nil {
|
if err := c.Write(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,9 @@ func (r *mutationResolver) DeleteFiles(ctx context.Context, ids []string) (ret b
|
||||||
return false, fmt.Errorf("converting ids: %w", err)
|
return false, fmt.Errorf("converting ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileDeleter := file.NewDeleter()
|
trashPath := manager.GetInstance().Config.GetDeleteTrashPath()
|
||||||
|
|
||||||
|
fileDeleter := file.NewDeleterWithTrash(trashPath)
|
||||||
destroyer := &file.ZipDestroyer{
|
destroyer := &file.ZipDestroyer{
|
||||||
FileDestroyer: r.repository.File,
|
FileDestroyer: r.repository.File,
|
||||||
FolderDestroyer: r.repository.Folder,
|
FolderDestroyer: r.repository.Folder,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/manager"
|
"github.com/stashapp/stash/internal/manager"
|
||||||
"github.com/stashapp/stash/pkg/file"
|
"github.com/stashapp/stash/pkg/file"
|
||||||
|
|
@ -43,7 +44,7 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input GalleryCreat
|
||||||
// Populate a new gallery from the input
|
// Populate a new gallery from the input
|
||||||
newGallery := models.NewGallery()
|
newGallery := models.NewGallery()
|
||||||
|
|
||||||
newGallery.Title = input.Title
|
newGallery.Title = strings.TrimSpace(input.Title)
|
||||||
newGallery.Code = translator.string(input.Code)
|
newGallery.Code = translator.string(input.Code)
|
||||||
newGallery.Details = translator.string(input.Details)
|
newGallery.Details = translator.string(input.Details)
|
||||||
newGallery.Photographer = translator.string(input.Photographer)
|
newGallery.Photographer = translator.string(input.Photographer)
|
||||||
|
|
@ -74,9 +75,9 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input GalleryCreat
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.Urls != nil {
|
if input.Urls != nil {
|
||||||
newGallery.URLs = models.NewRelatedStrings(input.Urls)
|
newGallery.URLs = models.NewRelatedStrings(stringslice.TrimSpace(input.Urls))
|
||||||
} else if input.URL != nil {
|
} else if input.URL != nil {
|
||||||
newGallery.URLs = models.NewRelatedStrings([]string{*input.URL})
|
newGallery.URLs = models.NewRelatedStrings([]string{strings.TrimSpace(*input.URL)})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the transaction and save the gallery
|
// Start the transaction and save the gallery
|
||||||
|
|
@ -333,10 +334,12 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
|
||||||
return false, fmt.Errorf("converting ids: %w", err)
|
return false, fmt.Errorf("converting ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trashPath := manager.GetInstance().Config.GetDeleteTrashPath()
|
||||||
|
|
||||||
var galleries []*models.Gallery
|
var galleries []*models.Gallery
|
||||||
var imgsDestroyed []*models.Image
|
var imgsDestroyed []*models.Image
|
||||||
fileDeleter := &image.FileDeleter{
|
fileDeleter := &image.FileDeleter{
|
||||||
Deleter: file.NewDeleter(),
|
Deleter: file.NewDeleterWithTrash(trashPath),
|
||||||
Paths: manager.GetInstance().Paths,
|
Paths: manager.GetInstance().Paths,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -478,6 +481,61 @@ func (r *mutationResolver) RemoveGalleryImages(ctx context.Context, input Galler
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) SetGalleryCover(ctx context.Context, input GallerySetCoverInput) (bool, error) {
|
||||||
|
galleryID, err := strconv.Atoi(input.GalleryID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting gallery id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageID, err := strconv.Atoi(input.CoverImageID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting cover image id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Gallery
|
||||||
|
gallery, err := qb.Find(ctx, galleryID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gallery == nil {
|
||||||
|
return fmt.Errorf("gallery with id %d not found", galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.galleryService.SetCover(ctx, gallery, coverImageID)
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) ResetGalleryCover(ctx context.Context, input GalleryResetCoverInput) (bool, error) {
|
||||||
|
galleryID, err := strconv.Atoi(input.GalleryID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting gallery id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Gallery
|
||||||
|
gallery, err := qb.Find(ctx, galleryID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gallery == nil {
|
||||||
|
return fmt.Errorf("gallery with id %d not found", galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.galleryService.ResetCover(ctx, gallery)
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) getGalleryChapter(ctx context.Context, id int) (ret *models.GalleryChapter, err error) {
|
func (r *mutationResolver) getGalleryChapter(ctx context.Context, id int) (ret *models.GalleryChapter, err error) {
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.GalleryChapter.Find(ctx, id)
|
ret, err = r.repository.GalleryChapter.Find(ctx, id)
|
||||||
|
|
|
||||||
414
internal/api/resolver_mutation_group.go
Normal file
414
internal/api/resolver_mutation_group.go
Normal file
|
|
@ -0,0 +1,414 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/internal/static"
|
||||||
|
"github.com/stashapp/stash/pkg/group"
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/plugin/hook"
|
||||||
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func groupFromGroupCreateInput(ctx context.Context, input GroupCreateInput) (*models.Group, error) {
|
||||||
|
translator := changesetTranslator{
|
||||||
|
inputMap: getUpdateInputMap(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate a new group from the input
|
||||||
|
newGroup := models.NewGroup()
|
||||||
|
|
||||||
|
newGroup.Name = strings.TrimSpace(input.Name)
|
||||||
|
newGroup.Aliases = translator.string(input.Aliases)
|
||||||
|
newGroup.Duration = input.Duration
|
||||||
|
newGroup.Rating = input.Rating100
|
||||||
|
newGroup.Director = translator.string(input.Director)
|
||||||
|
newGroup.Synopsis = translator.string(input.Synopsis)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
newGroup.Date, err = translator.datePtr(input.Date)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting date: %w", err)
|
||||||
|
}
|
||||||
|
newGroup.StudioID, err = translator.intPtrFromString(input.StudioID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newGroup.TagIDs, err = translator.relatedIds(input.TagIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newGroup.ContainingGroups, err = translator.groupIDDescriptions(input.ContainingGroups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting containing group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newGroup.SubGroups, err = translator.groupIDDescriptions(input.SubGroups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting containing group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.Urls != nil {
|
||||||
|
newGroup.URLs = models.NewRelatedStrings(stringslice.TrimSpace(input.Urls))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &newGroup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) GroupCreate(ctx context.Context, input GroupCreateInput) (*models.Group, error) {
|
||||||
|
newGroup, err := groupFromGroupCreateInput(ctx, input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the base 64 encoded image string
|
||||||
|
var frontimageData []byte
|
||||||
|
if input.FrontImage != nil {
|
||||||
|
frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("processing front image: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the base 64 encoded image string
|
||||||
|
var backimageData []byte
|
||||||
|
if input.BackImage != nil {
|
||||||
|
backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("processing back image: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: if back image is being set, set the front image to the default.
|
||||||
|
// This is because we can't have a null front image with a non-null back image.
|
||||||
|
if len(frontimageData) == 0 && len(backimageData) != 0 {
|
||||||
|
frontimageData = static.ReadAll(static.DefaultGroupImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the transaction and save the group
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
if err = r.groupService.Create(ctx, newGroup, frontimageData, backimageData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// for backwards compatibility - run both movie and group hooks
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, newGroup.ID, hook.GroupCreatePost, input, nil)
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, newGroup.ID, hook.MovieCreatePost, input, nil)
|
||||||
|
return r.getGroup(ctx, newGroup.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupPartialFromGroupUpdateInput(translator changesetTranslator, input GroupUpdateInput) (ret models.GroupPartial, err error) {
|
||||||
|
// Populate group from the input
|
||||||
|
updatedGroup := models.NewGroupPartial()
|
||||||
|
|
||||||
|
updatedGroup.Name = translator.optionalString(input.Name, "name")
|
||||||
|
updatedGroup.Aliases = translator.optionalString(input.Aliases, "aliases")
|
||||||
|
updatedGroup.Duration = translator.optionalInt(input.Duration, "duration")
|
||||||
|
updatedGroup.Rating = translator.optionalInt(input.Rating100, "rating100")
|
||||||
|
updatedGroup.Director = translator.optionalString(input.Director, "director")
|
||||||
|
updatedGroup.Synopsis = translator.optionalString(input.Synopsis, "synopsis")
|
||||||
|
|
||||||
|
updatedGroup.Date, err = translator.optionalDate(input.Date, "date")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting date: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updatedGroup.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting studio id: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.ContainingGroups, err = translator.updateGroupIDDescriptions(input.ContainingGroups, "containing_groups")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting containing group ids: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.SubGroups, err = translator.updateGroupIDDescriptions(input.SubGroups, "sub_groups")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting containing group ids: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.URLs = translator.updateStrings(input.Urls, "urls")
|
||||||
|
|
||||||
|
return updatedGroup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) GroupUpdate(ctx context.Context, input GroupUpdateInput) (*models.Group, error) {
|
||||||
|
groupID, err := strconv.Atoi(input.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
translator := changesetTranslator{
|
||||||
|
inputMap: getUpdateInputMap(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup, err := groupPartialFromGroupUpdateInput(translator, input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var frontimageData []byte
|
||||||
|
frontImageIncluded := translator.hasField("front_image")
|
||||||
|
if input.FrontImage != nil {
|
||||||
|
frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("processing front image: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var backimageData []byte
|
||||||
|
backImageIncluded := translator.hasField("back_image")
|
||||||
|
if input.BackImage != nil {
|
||||||
|
backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("processing back image: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
frontImage := group.ImageInput{
|
||||||
|
Image: frontimageData,
|
||||||
|
Set: frontImageIncluded,
|
||||||
|
}
|
||||||
|
|
||||||
|
backImage := group.ImageInput{
|
||||||
|
Image: backimageData,
|
||||||
|
Set: backImageIncluded,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.groupService.UpdatePartial(ctx, groupID, updatedGroup, frontImage, backImage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// for backwards compatibility - run both movie and group hooks
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, groupID, hook.GroupUpdatePost, input, translator.getFields())
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, groupID, hook.MovieUpdatePost, input, translator.getFields())
|
||||||
|
return r.getGroup(ctx, groupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupPartialFromBulkGroupUpdateInput(translator changesetTranslator, input BulkGroupUpdateInput) (ret models.GroupPartial, err error) {
|
||||||
|
updatedGroup := models.NewGroupPartial()
|
||||||
|
|
||||||
|
updatedGroup.Rating = translator.optionalInt(input.Rating100, "rating100")
|
||||||
|
updatedGroup.Director = translator.optionalString(input.Director, "director")
|
||||||
|
|
||||||
|
updatedGroup.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting studio id: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.ContainingGroups, err = translator.updateGroupIDDescriptionsBulk(input.ContainingGroups, "containing_groups")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting containing group ids: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.SubGroups, err = translator.updateGroupIDDescriptionsBulk(input.SubGroups, "sub_groups")
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("converting containing group ids: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.URLs = translator.optionalURLsBulk(input.Urls, nil)
|
||||||
|
|
||||||
|
return updatedGroup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) BulkGroupUpdate(ctx context.Context, input BulkGroupUpdateInput) ([]*models.Group, error) {
|
||||||
|
groupIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
translator := changesetTranslator{
|
||||||
|
inputMap: getUpdateInputMap(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate group from the input
|
||||||
|
updatedGroup, err := groupPartialFromBulkGroupUpdateInput(translator, input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []*models.Group{}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
for _, groupID := range groupIDs {
|
||||||
|
group, err := r.groupService.UpdatePartial(ctx, groupID, updatedGroup, group.ImageInput{}, group.ImageInput{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var newRet []*models.Group
|
||||||
|
for _, group := range ret {
|
||||||
|
// for backwards compatibility - run both movie and group hooks
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.GroupUpdatePost, input, translator.getFields())
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.MovieUpdatePost, input, translator.getFields())
|
||||||
|
|
||||||
|
group, err = r.getGroup(ctx, group.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newRet = append(newRet, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) GroupDestroy(ctx context.Context, input GroupDestroyInput) (bool, error) {
|
||||||
|
id, err := strconv.Atoi(input.ID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return r.repository.Group.Destroy(ctx, id)
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// for backwards compatibility - run both movie and group hooks
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, input, nil)
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, input, nil)
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) GroupsDestroy(ctx context.Context, groupIDs []string) (bool, error) {
|
||||||
|
ids, err := stringslice.StringSliceToIntSlice(groupIDs)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Group
|
||||||
|
for _, id := range ids {
|
||||||
|
if err := qb.Destroy(ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
// for backwards compatibility - run both movie and group hooks
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, groupIDs, nil)
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, groupIDs, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) AddGroupSubGroups(ctx context.Context, input GroupSubGroupAddInput) (bool, error) {
|
||||||
|
groupID, err := strconv.Atoi(input.ContainingGroupID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting group id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subGroups, err := groupsDescriptionsFromGroupInput(input.SubGroups)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting sub group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return r.groupService.AddSubGroups(ctx, groupID, subGroups, input.InsertIndex)
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) RemoveGroupSubGroups(ctx context.Context, input GroupSubGroupRemoveInput) (bool, error) {
|
||||||
|
groupID, err := strconv.Atoi(input.ContainingGroupID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting group id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subGroupIDs, err := stringslice.StringSliceToIntSlice(input.SubGroupIds)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting sub group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return r.groupService.RemoveSubGroups(ctx, groupID, subGroupIDs)
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) ReorderSubGroups(ctx context.Context, input ReorderSubGroupsInput) (bool, error) {
|
||||||
|
groupID, err := strconv.Atoi(input.GroupID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting group id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subGroupIDs, err := stringslice.StringSliceToIntSlice(input.SubGroupIds)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting sub group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertPointID, err := strconv.Atoi(input.InsertAtID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting insert at id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertAfter := utils.IsTrue(input.InsertAfter)
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
return r.groupService.ReorderSubGroups(ctx, groupID, subGroupIDs, insertPointID, insertAfter)
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ func (r *mutationResolver) getImage(ctx context.Context, id int) (ret *models.Im
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) ImageUpdate(ctx context.Context, input ImageUpdateInput) (ret *models.Image, err error) {
|
func (r *mutationResolver) ImageUpdate(ctx context.Context, input models.ImageUpdateInput) (ret *models.Image, err error) {
|
||||||
translator := changesetTranslator{
|
translator := changesetTranslator{
|
||||||
inputMap: getUpdateInputMap(ctx),
|
inputMap: getUpdateInputMap(ctx),
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ func (r *mutationResolver) ImageUpdate(ctx context.Context, input ImageUpdateInp
|
||||||
return r.getImage(ctx, ret.ID)
|
return r.getImage(ctx, ret.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*ImageUpdateInput) (ret []*models.Image, err error) {
|
func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*models.ImageUpdateInput) (ret []*models.Image, err error) {
|
||||||
inputMaps := getUpdateInputMaps(ctx)
|
inputMaps := getUpdateInputMaps(ctx)
|
||||||
|
|
||||||
// Start the transaction and save the image
|
// Start the transaction and save the image
|
||||||
|
|
@ -89,7 +89,7 @@ func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*ImageUpdat
|
||||||
return newRet, nil
|
return newRet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInput, translator changesetTranslator) (*models.Image, error) {
|
func (r *mutationResolver) imageUpdate(ctx context.Context, input models.ImageUpdateInput, translator changesetTranslator) (*models.Image, error) {
|
||||||
imageID, err := strconv.Atoi(input.ID)
|
imageID, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting id: %w", err)
|
return nil, fmt.Errorf("converting id: %w", err)
|
||||||
|
|
@ -308,9 +308,11 @@ func (r *mutationResolver) ImageDestroy(ctx context.Context, input models.ImageD
|
||||||
return false, fmt.Errorf("converting id: %w", err)
|
return false, fmt.Errorf("converting id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trashPath := manager.GetInstance().Config.GetDeleteTrashPath()
|
||||||
|
|
||||||
var i *models.Image
|
var i *models.Image
|
||||||
fileDeleter := &image.FileDeleter{
|
fileDeleter := &image.FileDeleter{
|
||||||
Deleter: file.NewDeleter(),
|
Deleter: file.NewDeleterWithTrash(trashPath),
|
||||||
Paths: manager.GetInstance().Paths,
|
Paths: manager.GetInstance().Paths,
|
||||||
}
|
}
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
|
@ -348,9 +350,11 @@ func (r *mutationResolver) ImagesDestroy(ctx context.Context, input models.Image
|
||||||
return false, fmt.Errorf("converting ids: %w", err)
|
return false, fmt.Errorf("converting ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trashPath := manager.GetInstance().Config.GetDeleteTrashPath()
|
||||||
|
|
||||||
var images []*models.Image
|
var images []*models.Image
|
||||||
fileDeleter := &image.FileDeleter{
|
fileDeleter := &image.FileDeleter{
|
||||||
Deleter: file.NewDeleter(),
|
Deleter: file.NewDeleterWithTrash(trashPath),
|
||||||
Paths: manager.GetInstance().Paths,
|
Paths: manager.GetInstance().Paths,
|
||||||
}
|
}
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/static"
|
"github.com/stashapp/stash/internal/static"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
|
@ -12,10 +13,10 @@ import (
|
||||||
"github.com/stashapp/stash/pkg/utils"
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// used to refetch movie after hooks run
|
// used to refetch group after hooks run
|
||||||
func (r *mutationResolver) getMovie(ctx context.Context, id int) (ret *models.Movie, err error) {
|
func (r *mutationResolver) getGroup(ctx context.Context, id int) (ret *models.Group, err error) {
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Movie.Find(ctx, id)
|
ret, err = r.repository.Group.Find(ctx, id)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -24,33 +25,43 @@ func (r *mutationResolver) getMovie(ctx context.Context, id int) (ret *models.Mo
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInput) (*models.Movie, error) {
|
func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInput) (*models.Group, error) {
|
||||||
translator := changesetTranslator{
|
translator := changesetTranslator{
|
||||||
inputMap: getUpdateInputMap(ctx),
|
inputMap: getUpdateInputMap(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate a new movie from the input
|
// Populate a new group from the input
|
||||||
newMovie := models.NewMovie()
|
newGroup := models.NewGroup()
|
||||||
|
|
||||||
newMovie.Name = input.Name
|
newGroup.Name = strings.TrimSpace(input.Name)
|
||||||
newMovie.Aliases = translator.string(input.Aliases)
|
newGroup.Aliases = translator.string(input.Aliases)
|
||||||
newMovie.Duration = input.Duration
|
newGroup.Duration = input.Duration
|
||||||
newMovie.Rating = input.Rating100
|
newGroup.Rating = input.Rating100
|
||||||
newMovie.Director = translator.string(input.Director)
|
newGroup.Director = translator.string(input.Director)
|
||||||
newMovie.Synopsis = translator.string(input.Synopsis)
|
newGroup.Synopsis = translator.string(input.Synopsis)
|
||||||
newMovie.URL = translator.string(input.URL)
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
newMovie.Date, err = translator.datePtr(input.Date)
|
newGroup.Date, err = translator.datePtr(input.Date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting date: %w", err)
|
return nil, fmt.Errorf("converting date: %w", err)
|
||||||
}
|
}
|
||||||
newMovie.StudioID, err = translator.intPtrFromString(input.StudioID)
|
newGroup.StudioID, err = translator.intPtrFromString(input.StudioID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newGroup.TagIDs, err = translator.relatedIds(input.TagIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.Urls != nil {
|
||||||
|
newGroup.URLs = models.NewRelatedStrings(stringslice.TrimSpace(input.Urls))
|
||||||
|
} else if input.URL != nil {
|
||||||
|
newGroup.URLs = models.NewRelatedStrings([]string{strings.TrimSpace(*input.URL)})
|
||||||
|
}
|
||||||
|
|
||||||
// Process the base 64 encoded image string
|
// Process the base 64 encoded image string
|
||||||
var frontimageData []byte
|
var frontimageData []byte
|
||||||
if input.FrontImage != nil {
|
if input.FrontImage != nil {
|
||||||
|
|
@ -72,27 +83,27 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp
|
||||||
// HACK: if back image is being set, set the front image to the default.
|
// HACK: if back image is being set, set the front image to the default.
|
||||||
// This is because we can't have a null front image with a non-null back image.
|
// This is because we can't have a null front image with a non-null back image.
|
||||||
if len(frontimageData) == 0 && len(backimageData) != 0 {
|
if len(frontimageData) == 0 && len(backimageData) != 0 {
|
||||||
frontimageData = static.ReadAll(static.DefaultMovieImage)
|
frontimageData = static.ReadAll(static.DefaultGroupImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the transaction and save the movie
|
// Start the transaction and save the group
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
qb := r.repository.Movie
|
qb := r.repository.Group
|
||||||
|
|
||||||
err = qb.Create(ctx, &newMovie)
|
err = qb.Create(ctx, &newGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// update image table
|
// update image table
|
||||||
if len(frontimageData) > 0 {
|
if len(frontimageData) > 0 {
|
||||||
if err := qb.UpdateFrontImage(ctx, newMovie.ID, frontimageData); err != nil {
|
if err := qb.UpdateFrontImage(ctx, newGroup.ID, frontimageData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(backimageData) > 0 {
|
if len(backimageData) > 0 {
|
||||||
if err := qb.UpdateBackImage(ctx, newMovie.ID, backimageData); err != nil {
|
if err := qb.UpdateBackImage(ctx, newGroup.ID, backimageData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,12 +113,14 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInp
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.hookExecutor.ExecutePostHooks(ctx, newMovie.ID, hook.MovieCreatePost, input, nil)
|
// for backwards compatibility - run both movie and group hooks
|
||||||
return r.getMovie(ctx, newMovie.ID)
|
r.hookExecutor.ExecutePostHooks(ctx, newGroup.ID, hook.GroupCreatePost, input, nil)
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, newGroup.ID, hook.MovieCreatePost, input, nil)
|
||||||
|
return r.getGroup(ctx, newGroup.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInput) (*models.Movie, error) {
|
func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInput) (*models.Group, error) {
|
||||||
movieID, err := strconv.Atoi(input.ID)
|
groupID, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting id: %w", err)
|
return nil, fmt.Errorf("converting id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -116,26 +129,32 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp
|
||||||
inputMap: getUpdateInputMap(ctx),
|
inputMap: getUpdateInputMap(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate movie from the input
|
// Populate group from the input
|
||||||
updatedMovie := models.NewMoviePartial()
|
updatedGroup := models.NewGroupPartial()
|
||||||
|
|
||||||
updatedMovie.Name = translator.optionalString(input.Name, "name")
|
updatedGroup.Name = translator.optionalString(input.Name, "name")
|
||||||
updatedMovie.Aliases = translator.optionalString(input.Aliases, "aliases")
|
updatedGroup.Aliases = translator.optionalString(input.Aliases, "aliases")
|
||||||
updatedMovie.Duration = translator.optionalInt(input.Duration, "duration")
|
updatedGroup.Duration = translator.optionalInt(input.Duration, "duration")
|
||||||
updatedMovie.Rating = translator.optionalInt(input.Rating100, "rating100")
|
updatedGroup.Rating = translator.optionalInt(input.Rating100, "rating100")
|
||||||
updatedMovie.Director = translator.optionalString(input.Director, "director")
|
updatedGroup.Director = translator.optionalString(input.Director, "director")
|
||||||
updatedMovie.Synopsis = translator.optionalString(input.Synopsis, "synopsis")
|
updatedGroup.Synopsis = translator.optionalString(input.Synopsis, "synopsis")
|
||||||
updatedMovie.URL = translator.optionalString(input.URL, "url")
|
|
||||||
|
|
||||||
updatedMovie.Date, err = translator.optionalDate(input.Date, "date")
|
updatedGroup.Date, err = translator.optionalDate(input.Date, "date")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting date: %w", err)
|
return nil, fmt.Errorf("converting date: %w", err)
|
||||||
}
|
}
|
||||||
updatedMovie.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
updatedGroup.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedGroup.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.URLs = translator.optionalURLs(input.Urls, input.URL)
|
||||||
|
|
||||||
var frontimageData []byte
|
var frontimageData []byte
|
||||||
frontImageIncluded := translator.hasField("front_image")
|
frontImageIncluded := translator.hasField("front_image")
|
||||||
if input.FrontImage != nil {
|
if input.FrontImage != nil {
|
||||||
|
|
@ -154,24 +173,24 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the transaction and save the movie
|
// Start the transaction and save the group
|
||||||
var movie *models.Movie
|
var group *models.Group
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
qb := r.repository.Movie
|
qb := r.repository.Group
|
||||||
movie, err = qb.UpdatePartial(ctx, movieID, updatedMovie)
|
group, err = qb.UpdatePartial(ctx, groupID, updatedGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// update image table
|
// update image table
|
||||||
if frontImageIncluded {
|
if frontImageIncluded {
|
||||||
if err := qb.UpdateFrontImage(ctx, movie.ID, frontimageData); err != nil {
|
if err := qb.UpdateFrontImage(ctx, group.ID, frontimageData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if backImageIncluded {
|
if backImageIncluded {
|
||||||
if err := qb.UpdateBackImage(ctx, movie.ID, backimageData); err != nil {
|
if err := qb.UpdateBackImage(ctx, group.ID, backimageData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,12 +200,14 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInp
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.MovieUpdatePost, input, translator.getFields())
|
// for backwards compatibility - run both movie and group hooks
|
||||||
return r.getMovie(ctx, movie.ID)
|
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.GroupUpdatePost, input, translator.getFields())
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.MovieUpdatePost, input, translator.getFields())
|
||||||
|
return r.getGroup(ctx, group.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieUpdateInput) ([]*models.Movie, error) {
|
func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieUpdateInput) ([]*models.Group, error) {
|
||||||
movieIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
|
groupIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting ids: %w", err)
|
return nil, fmt.Errorf("converting ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -195,29 +216,36 @@ func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieU
|
||||||
inputMap: getUpdateInputMap(ctx),
|
inputMap: getUpdateInputMap(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate movie from the input
|
// Populate group from the input
|
||||||
updatedMovie := models.NewMoviePartial()
|
updatedGroup := models.NewGroupPartial()
|
||||||
|
|
||||||
updatedMovie.Rating = translator.optionalInt(input.Rating100, "rating100")
|
updatedGroup.Rating = translator.optionalInt(input.Rating100, "rating100")
|
||||||
updatedMovie.Director = translator.optionalString(input.Director, "director")
|
updatedGroup.Director = translator.optionalString(input.Director, "director")
|
||||||
|
|
||||||
updatedMovie.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
updatedGroup.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting studio id: %w", err)
|
return nil, fmt.Errorf("converting studio id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := []*models.Movie{}
|
updatedGroup.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedGroup.URLs = translator.optionalURLsBulk(input.Urls, nil)
|
||||||
|
|
||||||
|
ret := []*models.Group{}
|
||||||
|
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
qb := r.repository.Movie
|
qb := r.repository.Group
|
||||||
|
|
||||||
for _, movieID := range movieIDs {
|
for _, groupID := range groupIDs {
|
||||||
movie, err := qb.UpdatePartial(ctx, movieID, updatedMovie)
|
group, err := qb.UpdatePartial(ctx, groupID, updatedGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = append(ret, movie)
|
ret = append(ret, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -225,16 +253,18 @@ func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieU
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var newRet []*models.Movie
|
var newRet []*models.Group
|
||||||
for _, movie := range ret {
|
for _, group := range ret {
|
||||||
r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.MovieUpdatePost, input, translator.getFields())
|
// for backwards compatibility - run both movie and group hooks
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.GroupUpdatePost, input, translator.getFields())
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, group.ID, hook.MovieUpdatePost, input, translator.getFields())
|
||||||
|
|
||||||
movie, err = r.getMovie(ctx, movie.ID)
|
group, err = r.getGroup(ctx, group.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
newRet = append(newRet, movie)
|
newRet = append(newRet, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newRet, nil
|
return newRet, nil
|
||||||
|
|
@ -247,24 +277,26 @@ func (r *mutationResolver) MovieDestroy(ctx context.Context, input MovieDestroyI
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
return r.repository.Movie.Destroy(ctx, id)
|
return r.repository.Group.Destroy(ctx, id)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for backwards compatibility - run both movie and group hooks
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, input, nil)
|
||||||
r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, input, nil)
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, input, nil)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) MoviesDestroy(ctx context.Context, movieIDs []string) (bool, error) {
|
func (r *mutationResolver) MoviesDestroy(ctx context.Context, groupIDs []string) (bool, error) {
|
||||||
ids, err := stringslice.StringSliceToIntSlice(movieIDs)
|
ids, err := stringslice.StringSliceToIntSlice(groupIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("converting ids: %w", err)
|
return false, fmt.Errorf("converting ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
qb := r.repository.Movie
|
qb := r.repository.Group
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
if err := qb.Destroy(ctx, id); err != nil {
|
if err := qb.Destroy(ctx, id); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -277,7 +309,9 @@ func (r *mutationResolver) MoviesDestroy(ctx context.Context, movieIDs []string)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, movieIDs, nil)
|
// for backwards compatibility - run both movie and group hooks
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.GroupDestroyPost, groupIDs, nil)
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, groupIDs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/performer"
|
"github.com/stashapp/stash/pkg/performer"
|
||||||
|
|
@ -12,6 +13,11 @@ import (
|
||||||
"github.com/stashapp/stash/pkg/utils"
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
twitterURL = "https://twitter.com"
|
||||||
|
instagramURL = "https://instagram.com"
|
||||||
|
)
|
||||||
|
|
||||||
// used to refetch performer after hooks run
|
// used to refetch performer after hooks run
|
||||||
func (r *mutationResolver) getPerformer(ctx context.Context, id int) (ret *models.Performer, err error) {
|
func (r *mutationResolver) getPerformer(ctx context.Context, id int) (ret *models.Performer, err error) {
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
|
@ -32,10 +38,9 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||||
// Populate a new performer from the input
|
// Populate a new performer from the input
|
||||||
newPerformer := models.NewPerformer()
|
newPerformer := models.NewPerformer()
|
||||||
|
|
||||||
newPerformer.Name = input.Name
|
newPerformer.Name = strings.TrimSpace(input.Name)
|
||||||
newPerformer.Disambiguation = translator.string(input.Disambiguation)
|
newPerformer.Disambiguation = translator.string(input.Disambiguation)
|
||||||
newPerformer.Aliases = models.NewRelatedStrings(input.AliasList)
|
newPerformer.Aliases = models.NewRelatedStrings(stringslice.TrimSpace(input.AliasList))
|
||||||
newPerformer.URL = translator.string(input.URL)
|
|
||||||
newPerformer.Gender = input.Gender
|
newPerformer.Gender = input.Gender
|
||||||
newPerformer.Ethnicity = translator.string(input.Ethnicity)
|
newPerformer.Ethnicity = translator.string(input.Ethnicity)
|
||||||
newPerformer.Country = translator.string(input.Country)
|
newPerformer.Country = translator.string(input.Country)
|
||||||
|
|
@ -47,8 +52,6 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||||
newPerformer.CareerLength = translator.string(input.CareerLength)
|
newPerformer.CareerLength = translator.string(input.CareerLength)
|
||||||
newPerformer.Tattoos = translator.string(input.Tattoos)
|
newPerformer.Tattoos = translator.string(input.Tattoos)
|
||||||
newPerformer.Piercings = translator.string(input.Piercings)
|
newPerformer.Piercings = translator.string(input.Piercings)
|
||||||
newPerformer.Twitter = translator.string(input.Twitter)
|
|
||||||
newPerformer.Instagram = translator.string(input.Instagram)
|
|
||||||
newPerformer.Favorite = translator.bool(input.Favorite)
|
newPerformer.Favorite = translator.bool(input.Favorite)
|
||||||
newPerformer.Rating = input.Rating100
|
newPerformer.Rating = input.Rating100
|
||||||
newPerformer.Details = translator.string(input.Details)
|
newPerformer.Details = translator.string(input.Details)
|
||||||
|
|
@ -56,7 +59,22 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||||
newPerformer.Height = input.HeightCm
|
newPerformer.Height = input.HeightCm
|
||||||
newPerformer.Weight = input.Weight
|
newPerformer.Weight = input.Weight
|
||||||
newPerformer.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag)
|
newPerformer.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag)
|
||||||
newPerformer.StashIDs = models.NewRelatedStashIDs(input.StashIds)
|
newPerformer.StashIDs = models.NewRelatedStashIDs(models.StashIDInputs(input.StashIds).ToStashIDs())
|
||||||
|
|
||||||
|
newPerformer.URLs = models.NewRelatedStrings([]string{})
|
||||||
|
if input.URL != nil {
|
||||||
|
newPerformer.URLs.Add(strings.TrimSpace(*input.URL))
|
||||||
|
}
|
||||||
|
if input.Twitter != nil {
|
||||||
|
newPerformer.URLs.Add(utils.URLFromHandle(strings.TrimSpace(*input.Twitter), twitterURL))
|
||||||
|
}
|
||||||
|
if input.Instagram != nil {
|
||||||
|
newPerformer.URLs.Add(utils.URLFromHandle(strings.TrimSpace(*input.Instagram), instagramURL))
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.Urls != nil {
|
||||||
|
newPerformer.URLs.Add(stringslice.TrimSpace(input.Urls)...)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
@ -91,7 +109,13 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = qb.Create(ctx, &newPerformer)
|
i := &models.CreatePerformerInput{
|
||||||
|
Performer: &newPerformer,
|
||||||
|
// convert json.Numbers to int/float
|
||||||
|
CustomFields: convertMapJSONNumbers(input.CustomFields),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = qb.Create(ctx, i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -112,6 +136,96 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||||
return r.getPerformer(ctx, newPerformer.ID)
|
return r.getPerformer(ctx, newPerformer.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) validateNoLegacyURLs(translator changesetTranslator) error {
|
||||||
|
// ensure url/twitter/instagram are not included in the input
|
||||||
|
if translator.hasField("url") {
|
||||||
|
return fmt.Errorf("url field must not be included if urls is included")
|
||||||
|
}
|
||||||
|
if translator.hasField("twitter") {
|
||||||
|
return fmt.Errorf("twitter field must not be included if urls is included")
|
||||||
|
}
|
||||||
|
if translator.hasField("instagram") {
|
||||||
|
return fmt.Errorf("instagram field must not be included if urls is included")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) handleLegacyURLs(ctx context.Context, performerID int, legacyURL, legacyTwitter, legacyInstagram models.OptionalString, updatedPerformer *models.PerformerPartial) error {
|
||||||
|
qb := r.repository.Performer
|
||||||
|
|
||||||
|
// we need to be careful with URL/Twitter/Instagram
|
||||||
|
// treat URL as replacing the first non-Twitter/Instagram URL in the list
|
||||||
|
// twitter should replace any existing twitter URL
|
||||||
|
// instagram should replace any existing instagram URL
|
||||||
|
p, err := qb.Find(ctx, performerID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.LoadURLs(ctx, qb); err != nil {
|
||||||
|
return fmt.Errorf("loading performer URLs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingURLs := p.URLs.List()
|
||||||
|
|
||||||
|
// performer partial URLs should be empty
|
||||||
|
if legacyURL.Set {
|
||||||
|
replaced := false
|
||||||
|
for i, url := range existingURLs {
|
||||||
|
if !performer.IsTwitterURL(url) && !performer.IsInstagramURL(url) {
|
||||||
|
existingURLs[i] = legacyURL.Value
|
||||||
|
replaced = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !replaced {
|
||||||
|
existingURLs = append(existingURLs, legacyURL.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if legacyTwitter.Set {
|
||||||
|
value := utils.URLFromHandle(legacyTwitter.Value, twitterURL)
|
||||||
|
found := false
|
||||||
|
// find and replace the first twitter URL
|
||||||
|
for i, url := range existingURLs {
|
||||||
|
if performer.IsTwitterURL(url) {
|
||||||
|
existingURLs[i] = value
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
existingURLs = append(existingURLs, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if legacyInstagram.Set {
|
||||||
|
found := false
|
||||||
|
value := utils.URLFromHandle(legacyInstagram.Value, instagramURL)
|
||||||
|
// find and replace the first instagram URL
|
||||||
|
for i, url := range existingURLs {
|
||||||
|
if performer.IsInstagramURL(url) {
|
||||||
|
existingURLs[i] = value
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
existingURLs = append(existingURLs, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedPerformer.URLs = &models.UpdateStrings{
|
||||||
|
Values: existingURLs,
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.PerformerUpdateInput) (*models.Performer, error) {
|
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.PerformerUpdateInput) (*models.Performer, error) {
|
||||||
performerID, err := strconv.Atoi(input.ID)
|
performerID, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -127,7 +241,6 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||||
|
|
||||||
updatedPerformer.Name = translator.optionalString(input.Name, "name")
|
updatedPerformer.Name = translator.optionalString(input.Name, "name")
|
||||||
updatedPerformer.Disambiguation = translator.optionalString(input.Disambiguation, "disambiguation")
|
updatedPerformer.Disambiguation = translator.optionalString(input.Disambiguation, "disambiguation")
|
||||||
updatedPerformer.URL = translator.optionalString(input.URL, "url")
|
|
||||||
updatedPerformer.Gender = translator.optionalString((*string)(input.Gender), "gender")
|
updatedPerformer.Gender = translator.optionalString((*string)(input.Gender), "gender")
|
||||||
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
|
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
|
||||||
updatedPerformer.Country = translator.optionalString(input.Country, "country")
|
updatedPerformer.Country = translator.optionalString(input.Country, "country")
|
||||||
|
|
@ -139,8 +252,6 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||||
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
||||||
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
||||||
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
||||||
updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter")
|
|
||||||
updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram")
|
|
||||||
updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
||||||
updatedPerformer.Rating = translator.optionalInt(input.Rating100, "rating100")
|
updatedPerformer.Rating = translator.optionalInt(input.Rating100, "rating100")
|
||||||
updatedPerformer.Details = translator.optionalString(input.Details, "details")
|
updatedPerformer.Details = translator.optionalString(input.Details, "details")
|
||||||
|
|
@ -149,6 +260,19 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||||
updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
||||||
updatedPerformer.StashIDs = translator.updateStashIDs(input.StashIds, "stash_ids")
|
updatedPerformer.StashIDs = translator.updateStashIDs(input.StashIds, "stash_ids")
|
||||||
|
|
||||||
|
if translator.hasField("urls") {
|
||||||
|
// ensure url/twitter/instagram are not included in the input
|
||||||
|
if err := r.validateNoLegacyURLs(translator); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedPerformer.URLs = translator.updateStrings(input.Urls, "urls")
|
||||||
|
}
|
||||||
|
|
||||||
|
legacyURL := translator.optionalString(input.URL, "url")
|
||||||
|
legacyTwitter := translator.optionalString(input.Twitter, "twitter")
|
||||||
|
legacyInstagram := translator.optionalString(input.Instagram, "instagram")
|
||||||
|
|
||||||
updatedPerformer.Birthdate, err = translator.optionalDate(input.Birthdate, "birthdate")
|
updatedPerformer.Birthdate, err = translator.optionalDate(input.Birthdate, "birthdate")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting birthdate: %w", err)
|
return nil, fmt.Errorf("converting birthdate: %w", err)
|
||||||
|
|
@ -173,6 +297,8 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||||
return nil, fmt.Errorf("converting tag ids: %w", err)
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedPerformer.CustomFields = handleUpdateCustomFields(input.CustomFields)
|
||||||
|
|
||||||
var imageData []byte
|
var imageData []byte
|
||||||
imageIncluded := translator.hasField("image")
|
imageIncluded := translator.hasField("image")
|
||||||
if input.Image != nil {
|
if input.Image != nil {
|
||||||
|
|
@ -186,6 +312,12 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
qb := r.repository.Performer
|
qb := r.repository.Performer
|
||||||
|
|
||||||
|
if legacyURL.Set || legacyTwitter.Set || legacyInstagram.Set {
|
||||||
|
if err := r.handleLegacyURLs(ctx, performerID, legacyURL, legacyTwitter, legacyInstagram, &updatedPerformer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := performer.ValidateUpdate(ctx, performerID, updatedPerformer, qb); err != nil {
|
if err := performer.ValidateUpdate(ctx, performerID, updatedPerformer, qb); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -225,7 +357,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
||||||
updatedPerformer := models.NewPerformerPartial()
|
updatedPerformer := models.NewPerformerPartial()
|
||||||
|
|
||||||
updatedPerformer.Disambiguation = translator.optionalString(input.Disambiguation, "disambiguation")
|
updatedPerformer.Disambiguation = translator.optionalString(input.Disambiguation, "disambiguation")
|
||||||
updatedPerformer.URL = translator.optionalString(input.URL, "url")
|
|
||||||
updatedPerformer.Gender = translator.optionalString((*string)(input.Gender), "gender")
|
updatedPerformer.Gender = translator.optionalString((*string)(input.Gender), "gender")
|
||||||
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
|
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
|
||||||
updatedPerformer.Country = translator.optionalString(input.Country, "country")
|
updatedPerformer.Country = translator.optionalString(input.Country, "country")
|
||||||
|
|
@ -237,8 +369,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
||||||
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
||||||
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
||||||
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
||||||
updatedPerformer.Twitter = translator.optionalString(input.Twitter, "twitter")
|
|
||||||
updatedPerformer.Instagram = translator.optionalString(input.Instagram, "instagram")
|
|
||||||
updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
updatedPerformer.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
||||||
updatedPerformer.Rating = translator.optionalInt(input.Rating100, "rating100")
|
updatedPerformer.Rating = translator.optionalInt(input.Rating100, "rating100")
|
||||||
updatedPerformer.Details = translator.optionalString(input.Details, "details")
|
updatedPerformer.Details = translator.optionalString(input.Details, "details")
|
||||||
|
|
@ -246,6 +377,19 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
||||||
updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight")
|
updatedPerformer.Weight = translator.optionalInt(input.Weight, "weight")
|
||||||
updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
updatedPerformer.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
||||||
|
|
||||||
|
if translator.hasField("urls") {
|
||||||
|
// ensure url/twitter/instagram are not included in the input
|
||||||
|
if err := r.validateNoLegacyURLs(translator); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedPerformer.URLs = translator.updateStringsBulk(input.Urls, "urls")
|
||||||
|
}
|
||||||
|
|
||||||
|
legacyURL := translator.optionalString(input.URL, "url")
|
||||||
|
legacyTwitter := translator.optionalString(input.Twitter, "twitter")
|
||||||
|
legacyInstagram := translator.optionalString(input.Instagram, "instagram")
|
||||||
|
|
||||||
updatedPerformer.Birthdate, err = translator.optionalDate(input.Birthdate, "birthdate")
|
updatedPerformer.Birthdate, err = translator.optionalDate(input.Birthdate, "birthdate")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting birthdate: %w", err)
|
return nil, fmt.Errorf("converting birthdate: %w", err)
|
||||||
|
|
@ -270,6 +414,10 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
||||||
return nil, fmt.Errorf("converting tag ids: %w", err)
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.CustomFields != nil {
|
||||||
|
updatedPerformer.CustomFields = handleUpdateCustomFields(*input.CustomFields)
|
||||||
|
}
|
||||||
|
|
||||||
ret := []*models.Performer{}
|
ret := []*models.Performer{}
|
||||||
|
|
||||||
// Start the transaction and save the performers
|
// Start the transaction and save the performers
|
||||||
|
|
@ -277,6 +425,12 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
||||||
qb := r.repository.Performer
|
qb := r.repository.Performer
|
||||||
|
|
||||||
for _, performerID := range performerIDs {
|
for _, performerID := range performerIDs {
|
||||||
|
if legacyURL.Set || legacyTwitter.Set || legacyInstagram.Set {
|
||||||
|
if err := r.handleLegacyURLs(ctx, performerID, legacyURL, legacyTwitter, legacyInstagram, &updatedPerformer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := performer.ValidateUpdate(ctx, performerID, updatedPerformer, qb); err != nil {
|
if err := performer.ValidateUpdate(ctx, performerID, updatedPerformer, qb); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput) (ret *models.SavedFilter, err error) {
|
func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput) (ret *models.SavedFilter, err error) {
|
||||||
|
|
@ -29,7 +32,7 @@ func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput
|
||||||
|
|
||||||
f := models.SavedFilter{
|
f := models.SavedFilter{
|
||||||
Mode: input.Mode,
|
Mode: input.Mode,
|
||||||
Name: input.Name,
|
Name: strings.TrimSpace(input.Name),
|
||||||
FindFilter: input.FindFilter,
|
FindFilter: input.FindFilter,
|
||||||
ObjectFilter: input.ObjectFilter,
|
ObjectFilter: input.ObjectFilter,
|
||||||
UIOptions: input.UIOptions,
|
UIOptions: input.UIOptions,
|
||||||
|
|
@ -67,30 +70,48 @@ func (r *mutationResolver) DestroySavedFilter(ctx context.Context, input Destroy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SetDefaultFilter(ctx context.Context, input SetDefaultFilterInput) (bool, error) {
|
func (r *mutationResolver) SetDefaultFilter(ctx context.Context, input SetDefaultFilterInput) (bool, error) {
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
// deprecated - write to the config in the meantime
|
||||||
qb := r.repository.SavedFilter
|
config := config.GetInstance()
|
||||||
|
|
||||||
if input.FindFilter == nil && input.ObjectFilter == nil && input.UIOptions == nil {
|
uiConfig := config.GetUIConfiguration()
|
||||||
// clearing
|
if uiConfig == nil {
|
||||||
def, err := qb.FindDefault(ctx, input.Mode)
|
uiConfig = make(map[string]interface{})
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if def != nil {
|
m := utils.NestedMap(uiConfig)
|
||||||
return qb.Destroy(ctx, def.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
if input.FindFilter == nil && input.ObjectFilter == nil && input.UIOptions == nil {
|
||||||
|
// clearing
|
||||||
|
m.Delete("defaultFilters." + strings.ToLower(input.Mode.String()))
|
||||||
|
config.SetUIConfiguration(m)
|
||||||
|
|
||||||
|
if err := config.Write(); err != nil {
|
||||||
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return qb.SetDefault(ctx, &models.SavedFilter{
|
return true, nil
|
||||||
Mode: input.Mode,
|
}
|
||||||
FindFilter: input.FindFilter,
|
|
||||||
ObjectFilter: input.ObjectFilter,
|
subMap := make(map[string]interface{})
|
||||||
UIOptions: input.UIOptions,
|
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
})
|
TagName: "json",
|
||||||
}); err != nil {
|
WeaklyTypedInput: true,
|
||||||
|
Result: &subMap,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.Decode(input); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Set("defaultFilters."+strings.ToLower(input.Mode.String()), subMap)
|
||||||
|
|
||||||
|
config.SetUIConfiguration(m)
|
||||||
|
|
||||||
|
if err := config.Write(); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/manager"
|
"github.com/stashapp/stash/internal/manager"
|
||||||
|
|
@ -50,7 +51,7 @@ func (r *mutationResolver) SceneCreate(ctx context.Context, input models.SceneCr
|
||||||
newScene.Director = translator.string(input.Director)
|
newScene.Director = translator.string(input.Director)
|
||||||
newScene.Rating = input.Rating100
|
newScene.Rating = input.Rating100
|
||||||
newScene.Organized = translator.bool(input.Organized)
|
newScene.Organized = translator.bool(input.Organized)
|
||||||
newScene.StashIDs = models.NewRelatedStashIDs(input.StashIds)
|
newScene.StashIDs = models.NewRelatedStashIDs(models.StashIDInputs(input.StashIds).ToStashIDs())
|
||||||
|
|
||||||
newScene.Date, err = translator.datePtr(input.Date)
|
newScene.Date, err = translator.datePtr(input.Date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -62,9 +63,9 @@ func (r *mutationResolver) SceneCreate(ctx context.Context, input models.SceneCr
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.Urls != nil {
|
if input.Urls != nil {
|
||||||
newScene.URLs = models.NewRelatedStrings(input.Urls)
|
newScene.URLs = models.NewRelatedStrings(stringslice.TrimSpace(input.Urls))
|
||||||
} else if input.URL != nil {
|
} else if input.URL != nil {
|
||||||
newScene.URLs = models.NewRelatedStrings([]string{*input.URL})
|
newScene.URLs = models.NewRelatedStrings([]string{strings.TrimSpace(*input.URL)})
|
||||||
}
|
}
|
||||||
|
|
||||||
newScene.PerformerIDs, err = translator.relatedIds(input.PerformerIds)
|
newScene.PerformerIDs, err = translator.relatedIds(input.PerformerIds)
|
||||||
|
|
@ -80,9 +81,17 @@ func (r *mutationResolver) SceneCreate(ctx context.Context, input models.SceneCr
|
||||||
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newScene.Movies, err = translator.relatedMovies(input.Movies)
|
// prefer groups over movies
|
||||||
if err != nil {
|
if len(input.Groups) > 0 {
|
||||||
return nil, fmt.Errorf("converting movies: %w", err)
|
newScene.Groups, err = translator.relatedGroups(input.Groups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting groups: %w", err)
|
||||||
|
}
|
||||||
|
} else if len(input.Movies) > 0 {
|
||||||
|
newScene.Groups, err = translator.relatedGroupsFromMovies(input.Movies)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting movies: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var coverImageData []byte
|
var coverImageData []byte
|
||||||
|
|
@ -216,9 +225,16 @@ func scenePartialFromInput(input models.SceneUpdateInput, translator changesetTr
|
||||||
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedScene.MovieIDs, err = translator.updateMovieIDs(input.Movies, "movies")
|
if translator.hasField("groups") {
|
||||||
if err != nil {
|
updatedScene.GroupIDs, err = translator.updateGroupIDs(input.Groups, "groups")
|
||||||
return nil, fmt.Errorf("converting movies: %w", err)
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting groups: %w", err)
|
||||||
|
}
|
||||||
|
} else if translator.hasField("movies") {
|
||||||
|
updatedScene.GroupIDs, err = translator.updateGroupIDsFromMovies(input.Movies, "movies")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting movies: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &updatedScene, nil
|
return &updatedScene, nil
|
||||||
|
|
@ -358,9 +374,16 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneU
|
||||||
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
return nil, fmt.Errorf("converting gallery ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedScene.MovieIDs, err = translator.updateMovieIDsBulk(input.MovieIds, "movie_ids")
|
if translator.hasField("group_ids") {
|
||||||
if err != nil {
|
updatedScene.GroupIDs, err = translator.updateGroupIDsBulk(input.GroupIds, "group_ids")
|
||||||
return nil, fmt.Errorf("converting movie ids: %w", err)
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting group ids: %w", err)
|
||||||
|
}
|
||||||
|
} else if translator.hasField("movie_ids") {
|
||||||
|
updatedScene.GroupIDs, err = translator.updateGroupIDsBulk(input.MovieIds, "movie_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting movie ids: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := []*models.Scene{}
|
ret := []*models.Scene{}
|
||||||
|
|
@ -406,10 +429,11 @@ func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneD
|
||||||
}
|
}
|
||||||
|
|
||||||
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
||||||
|
trashPath := manager.GetInstance().Config.GetDeleteTrashPath()
|
||||||
|
|
||||||
var s *models.Scene
|
var s *models.Scene
|
||||||
fileDeleter := &scene.FileDeleter{
|
fileDeleter := &scene.FileDeleter{
|
||||||
Deleter: file.NewDeleter(),
|
Deleter: file.NewDeleterWithTrash(trashPath),
|
||||||
FileNamingAlgo: fileNamingAlgo,
|
FileNamingAlgo: fileNamingAlgo,
|
||||||
Paths: manager.GetInstance().Paths,
|
Paths: manager.GetInstance().Paths,
|
||||||
}
|
}
|
||||||
|
|
@ -460,9 +484,10 @@ func (r *mutationResolver) ScenesDestroy(ctx context.Context, input models.Scene
|
||||||
|
|
||||||
var scenes []*models.Scene
|
var scenes []*models.Scene
|
||||||
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
||||||
|
trashPath := manager.GetInstance().Config.GetDeleteTrashPath()
|
||||||
|
|
||||||
fileDeleter := &scene.FileDeleter{
|
fileDeleter := &scene.FileDeleter{
|
||||||
Deleter: file.NewDeleter(),
|
Deleter: file.NewDeleterWithTrash(trashPath),
|
||||||
FileNamingAlgo: fileNamingAlgo,
|
FileNamingAlgo: fileNamingAlgo,
|
||||||
Paths: manager.GetInstance().Paths,
|
Paths: manager.GetInstance().Paths,
|
||||||
}
|
}
|
||||||
|
|
@ -571,8 +596,9 @@ func (r *mutationResolver) SceneMerge(ctx context.Context, input SceneMergeInput
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr := manager.GetInstance()
|
mgr := manager.GetInstance()
|
||||||
|
trashPath := mgr.Config.GetDeleteTrashPath()
|
||||||
fileDeleter := &scene.FileDeleter{
|
fileDeleter := &scene.FileDeleter{
|
||||||
Deleter: file.NewDeleter(),
|
Deleter: file.NewDeleterWithTrash(trashPath),
|
||||||
FileNamingAlgo: mgr.Config.GetVideoFileNamingAlgorithm(),
|
FileNamingAlgo: mgr.Config.GetVideoFileNamingAlgorithm(),
|
||||||
Paths: mgr.Paths,
|
Paths: mgr.Paths,
|
||||||
}
|
}
|
||||||
|
|
@ -628,11 +654,18 @@ func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input SceneMar
|
||||||
// Populate a new scene marker from the input
|
// Populate a new scene marker from the input
|
||||||
newMarker := models.NewSceneMarker()
|
newMarker := models.NewSceneMarker()
|
||||||
|
|
||||||
newMarker.Title = input.Title
|
newMarker.Title = strings.TrimSpace(input.Title)
|
||||||
newMarker.Seconds = input.Seconds
|
newMarker.Seconds = input.Seconds
|
||||||
newMarker.PrimaryTagID = primaryTagID
|
newMarker.PrimaryTagID = primaryTagID
|
||||||
newMarker.SceneID = sceneID
|
newMarker.SceneID = sceneID
|
||||||
|
|
||||||
|
if input.EndSeconds != nil {
|
||||||
|
if err := validateSceneMarkerEndSeconds(newMarker.Seconds, *input.EndSeconds); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newMarker.EndSeconds = input.EndSeconds
|
||||||
|
}
|
||||||
|
|
||||||
tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds)
|
tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting tag ids: %w", err)
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
|
@ -658,6 +691,20 @@ func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input SceneMar
|
||||||
return r.getSceneMarker(ctx, newMarker.ID)
|
return r.getSceneMarker(ctx, newMarker.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSceneMarkerEndSeconds(seconds, endSeconds float64) error {
|
||||||
|
if endSeconds < seconds {
|
||||||
|
return fmt.Errorf("end_seconds (%f) must be greater than or equal to seconds (%f)", endSeconds, seconds)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func float64OrZero(f *float64) float64 {
|
||||||
|
if f == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *f
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMarkerUpdateInput) (*models.SceneMarker, error) {
|
func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMarkerUpdateInput) (*models.SceneMarker, error) {
|
||||||
markerID, err := strconv.Atoi(input.ID)
|
markerID, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -673,6 +720,7 @@ func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMar
|
||||||
|
|
||||||
updatedMarker.Title = translator.optionalString(input.Title, "title")
|
updatedMarker.Title = translator.optionalString(input.Title, "title")
|
||||||
updatedMarker.Seconds = translator.optionalFloat64(input.Seconds, "seconds")
|
updatedMarker.Seconds = translator.optionalFloat64(input.Seconds, "seconds")
|
||||||
|
updatedMarker.EndSeconds = translator.optionalFloat64(input.EndSeconds, "end_seconds")
|
||||||
updatedMarker.SceneID, err = translator.optionalIntFromString(input.SceneID, "scene_id")
|
updatedMarker.SceneID, err = translator.optionalIntFromString(input.SceneID, "scene_id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting scene id: %w", err)
|
return nil, fmt.Errorf("converting scene id: %w", err)
|
||||||
|
|
@ -692,9 +740,10 @@ func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMar
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr := manager.GetInstance()
|
mgr := manager.GetInstance()
|
||||||
|
trashPath := mgr.Config.GetDeleteTrashPath()
|
||||||
|
|
||||||
fileDeleter := &scene.FileDeleter{
|
fileDeleter := &scene.FileDeleter{
|
||||||
Deleter: file.NewDeleter(),
|
Deleter: file.NewDeleterWithTrash(trashPath),
|
||||||
FileNamingAlgo: mgr.Config.GetVideoFileNamingAlgorithm(),
|
FileNamingAlgo: mgr.Config.GetVideoFileNamingAlgorithm(),
|
||||||
Paths: mgr.Paths,
|
Paths: mgr.Paths,
|
||||||
}
|
}
|
||||||
|
|
@ -713,6 +762,26 @@ func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMar
|
||||||
return fmt.Errorf("scene marker with id %d not found", markerID)
|
return fmt.Errorf("scene marker with id %d not found", markerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate end_seconds
|
||||||
|
shouldValidateEndSeconds := (updatedMarker.Seconds.Set || updatedMarker.EndSeconds.Set) && !updatedMarker.EndSeconds.Null
|
||||||
|
if shouldValidateEndSeconds {
|
||||||
|
seconds := existingMarker.Seconds
|
||||||
|
if updatedMarker.Seconds.Set {
|
||||||
|
seconds = updatedMarker.Seconds.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
endSeconds := existingMarker.EndSeconds
|
||||||
|
if updatedMarker.EndSeconds.Set {
|
||||||
|
endSeconds = &updatedMarker.EndSeconds.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if endSeconds != nil {
|
||||||
|
if err := validateSceneMarkerEndSeconds(seconds, *endSeconds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newMarker, err := qb.UpdatePartial(ctx, markerID, updatedMarker)
|
newMarker, err := qb.UpdatePartial(ctx, markerID, updatedMarker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -727,7 +796,7 @@ func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMar
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the marker preview if the scene changed or if the timestamp was changed
|
// remove the marker preview if the scene changed or if the timestamp was changed
|
||||||
if existingMarker.SceneID != newMarker.SceneID || existingMarker.Seconds != newMarker.Seconds {
|
if existingMarker.SceneID != newMarker.SceneID || existingMarker.Seconds != newMarker.Seconds || float64OrZero(existingMarker.EndSeconds) != float64OrZero(newMarker.EndSeconds) {
|
||||||
seconds := int(existingMarker.Seconds)
|
seconds := int(existingMarker.Seconds)
|
||||||
if err := fileDeleter.MarkMarkerFiles(existingScene, seconds); err != nil {
|
if err := fileDeleter.MarkMarkerFiles(existingScene, seconds); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -756,16 +825,139 @@ func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMar
|
||||||
return r.getSceneMarker(ctx, markerID)
|
return r.getSceneMarker(ctx, markerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (bool, error) {
|
func (r *mutationResolver) BulkSceneMarkerUpdate(ctx context.Context, input BulkSceneMarkerUpdateInput) ([]*models.SceneMarker, error) {
|
||||||
markerID, err := strconv.Atoi(id)
|
ids, err := stringslice.StringSliceToIntSlice(input.Ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("converting id: %w", err)
|
return nil, fmt.Errorf("converting ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
translator := changesetTranslator{
|
||||||
|
inputMap: getUpdateInputMap(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate performer from the input
|
||||||
|
partial := models.NewSceneMarkerPartial()
|
||||||
|
|
||||||
|
partial.Title = translator.optionalString(input.Title, "title")
|
||||||
|
|
||||||
|
partial.PrimaryTagID, err = translator.optionalIntFromString(input.PrimaryTagID, "primary_tag_id")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting primary tag id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
partial.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []*models.SceneMarker{}
|
||||||
|
|
||||||
|
// Start the transaction and save the performers
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.SceneMarker
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
l := partial
|
||||||
|
|
||||||
|
if err := adjustMarkerPartialForTagExclusion(ctx, r.repository.SceneMarker, id, &l); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := qb.UpdatePartial(ctx, id, l)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute post hooks outside of txn
|
||||||
|
var newRet []*models.SceneMarker
|
||||||
|
for _, m := range ret {
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, m.ID, hook.SceneMarkerUpdatePost, input, translator.getFields())
|
||||||
|
|
||||||
|
m, err = r.getSceneMarker(ctx, m.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newRet = append(newRet, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjustMarkerPartialForTagExclusion adjusts the SceneMarkerPartial to exclude the primary tag from tag updates.
|
||||||
|
func adjustMarkerPartialForTagExclusion(ctx context.Context, r models.SceneMarkerReader, id int, partial *models.SceneMarkerPartial) error {
|
||||||
|
if partial.TagIDs == nil && !partial.PrimaryTagID.Set {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// exclude primary tag from tag updates
|
||||||
|
var primaryTagID int
|
||||||
|
if partial.PrimaryTagID.Set {
|
||||||
|
primaryTagID = partial.PrimaryTagID.Value
|
||||||
|
} else {
|
||||||
|
existing, err := r.Find(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("finding existing primary tag id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryTagID = existing.PrimaryTagID
|
||||||
|
}
|
||||||
|
|
||||||
|
existingTagIDs, err := r.GetTagIDs(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting existing tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagIDAttr := partial.TagIDs
|
||||||
|
|
||||||
|
if tagIDAttr == nil {
|
||||||
|
tagIDAttr = &models.UpdateIDs{
|
||||||
|
IDs: existingTagIDs,
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newTagIDs := tagIDAttr.Apply(existingTagIDs)
|
||||||
|
// Remove primary tag from newTagIDs if present
|
||||||
|
newTagIDs = sliceutil.Exclude(newTagIDs, []int{primaryTagID})
|
||||||
|
|
||||||
|
if len(existingTagIDs) != len(newTagIDs) {
|
||||||
|
partial.TagIDs = &models.UpdateIDs{
|
||||||
|
IDs: newTagIDs,
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no change to tags required
|
||||||
|
partial.TagIDs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (bool, error) {
|
||||||
|
return r.SceneMarkersDestroy(ctx, []string{id})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) SceneMarkersDestroy(ctx context.Context, markerIDs []string) (bool, error) {
|
||||||
|
ids, err := stringslice.StringSliceToIntSlice(markerIDs)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var markers []*models.SceneMarker
|
||||||
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
|
||||||
|
trashPath := manager.GetInstance().Config.GetDeleteTrashPath()
|
||||||
|
|
||||||
fileDeleter := &scene.FileDeleter{
|
fileDeleter := &scene.FileDeleter{
|
||||||
Deleter: file.NewDeleter(),
|
Deleter: file.NewDeleterWithTrash(trashPath),
|
||||||
FileNamingAlgo: fileNamingAlgo,
|
FileNamingAlgo: fileNamingAlgo,
|
||||||
Paths: manager.GetInstance().Paths,
|
Paths: manager.GetInstance().Paths,
|
||||||
}
|
}
|
||||||
|
|
@ -774,35 +966,45 @@ func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (b
|
||||||
qb := r.repository.SceneMarker
|
qb := r.repository.SceneMarker
|
||||||
sqb := r.repository.Scene
|
sqb := r.repository.Scene
|
||||||
|
|
||||||
marker, err := qb.Find(ctx, markerID)
|
for _, markerID := range ids {
|
||||||
|
marker, err := qb.Find(ctx, markerID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if marker == nil {
|
||||||
|
return fmt.Errorf("scene marker with id %d not found", markerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := sqb.Find(ctx, marker.SceneID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
return fmt.Errorf("scene with id %d not found", marker.SceneID)
|
||||||
|
}
|
||||||
|
|
||||||
|
markers = append(markers, marker)
|
||||||
|
|
||||||
|
if err := scene.DestroyMarker(ctx, s, marker, qb, fileDeleter); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if marker == nil {
|
return nil
|
||||||
return fmt.Errorf("scene marker with id %d not found", markerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := sqb.Find(ctx, marker.SceneID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s == nil {
|
|
||||||
return fmt.Errorf("scene with id %d not found", marker.SceneID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return scene.DestroyMarker(ctx, s, marker, qb, fileDeleter)
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
fileDeleter.Rollback()
|
fileDeleter.Rollback()
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform the post-commit actions
|
|
||||||
fileDeleter.Commit()
|
fileDeleter.Commit()
|
||||||
|
|
||||||
r.hookExecutor.ExecutePostHooks(ctx, markerID, hook.SceneMarkerDestroyPost, id, nil)
|
for _, marker := range markers {
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, marker.ID, hook.SceneMarkerDestroyPost, markerIDs, nil)
|
||||||
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
@ -825,6 +1027,24 @@ func (r *mutationResolver) SceneSaveActivity(ctx context.Context, id string, res
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) SceneResetActivity(ctx context.Context, id string, resetResume *bool, resetDuration *bool) (ret bool, err error) {
|
||||||
|
sceneID, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("converting id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Scene
|
||||||
|
|
||||||
|
ret, err = qb.ResetActivity(ctx, sceneID, utils.IsTrue(resetResume), utils.IsTrue(resetDuration))
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
// deprecated
|
// deprecated
|
||||||
func (r *mutationResolver) SceneIncrementPlayCount(ctx context.Context, id string) (ret int, err error) {
|
func (r *mutationResolver) SceneIncrementPlayCount(ctx context.Context, id string) (ret int, err error) {
|
||||||
sceneID, err := strconv.Atoi(id)
|
sceneID, err := strconv.Atoi(id)
|
||||||
|
|
|
||||||
|
|
@ -6,41 +6,65 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/manager"
|
"github.com/stashapp/stash/internal/manager"
|
||||||
"github.com/stashapp/stash/internal/manager/config"
|
|
||||||
"github.com/stashapp/stash/pkg/logger"
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
"github.com/stashapp/stash/pkg/scraper/stashbox"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/scene"
|
||||||
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
|
"github.com/stashapp/stash/pkg/stashbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input StashBoxFingerprintSubmissionInput) (bool, error) {
|
func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input StashBoxFingerprintSubmissionInput) (bool, error) {
|
||||||
boxes := config.GetInstance().GetStashBoxes()
|
b, err := resolveStashBox(input.StashBoxIndex, input.StashBoxEndpoint)
|
||||||
|
if err != nil {
|
||||||
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {
|
return false, err
|
||||||
return false, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.stashboxRepository())
|
ids, err := stringslice.StringSliceToIntSlice(input.SceneIds)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
return client.SubmitStashBoxFingerprints(ctx, input.SceneIds, boxes[input.StashBoxIndex].Endpoint)
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
|
var scenes []*models.Scene
|
||||||
|
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
scenes, err = r.sceneService.FindByIDs(ctx, ids, scene.LoadStashIDs, scene.LoadFiles)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.SubmitFingerprints(ctx, scenes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) StashBoxBatchPerformerTag(ctx context.Context, input manager.StashBoxBatchTagInput) (string, error) {
|
func (r *mutationResolver) StashBoxBatchPerformerTag(ctx context.Context, input manager.StashBoxBatchTagInput) (string, error) {
|
||||||
jobID := manager.GetInstance().StashBoxBatchPerformerTag(ctx, input)
|
b, err := resolveStashBoxBatchTagInput(input.Endpoint, input.StashBoxEndpoint) //nolint:staticcheck
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
jobID := manager.GetInstance().StashBoxBatchPerformerTag(ctx, b, input)
|
||||||
return strconv.Itoa(jobID), nil
|
return strconv.Itoa(jobID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) StashBoxBatchStudioTag(ctx context.Context, input manager.StashBoxBatchTagInput) (string, error) {
|
func (r *mutationResolver) StashBoxBatchStudioTag(ctx context.Context, input manager.StashBoxBatchTagInput) (string, error) {
|
||||||
jobID := manager.GetInstance().StashBoxBatchStudioTag(ctx, input)
|
b, err := resolveStashBoxBatchTagInput(input.Endpoint, input.StashBoxEndpoint) //nolint:staticcheck
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
jobID := manager.GetInstance().StashBoxBatchStudioTag(ctx, b, input)
|
||||||
return strconv.Itoa(jobID), nil
|
return strconv.Itoa(jobID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input StashBoxDraftSubmissionInput) (*string, error) {
|
func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input StashBoxDraftSubmissionInput) (*string, error) {
|
||||||
boxes := config.GetInstance().GetStashBoxes()
|
b, err := resolveStashBox(input.StashBoxIndex, input.StashBoxEndpoint)
|
||||||
|
if err != nil {
|
||||||
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {
|
return nil, err
|
||||||
return nil, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.stashboxRepository())
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
id, err := strconv.Atoi(input.ID)
|
id, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -64,25 +88,91 @@ func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input S
|
||||||
logger.Errorf("Error getting scene cover: %v", err)
|
logger.Errorf("Error getting scene cover: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := scene.LoadURLs(ctx, r.repository.Scene); err != nil {
|
draft, err := r.makeSceneDraft(ctx, scene, cover)
|
||||||
return fmt.Errorf("loading scene URLs: %w", err)
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err = client.SubmitSceneDraft(ctx, scene, boxes[input.StashBoxIndex].Endpoint, cover)
|
res, err = client.SubmitSceneDraft(ctx, *draft)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, input StashBoxDraftSubmissionInput) (*string, error) {
|
func (r *mutationResolver) makeSceneDraft(ctx context.Context, s *models.Scene, cover []byte) (*stashbox.SceneDraft, error) {
|
||||||
boxes := config.GetInstance().GetStashBoxes()
|
if err := s.LoadURLs(ctx, r.repository.Scene); err != nil {
|
||||||
|
return nil, fmt.Errorf("loading scene URLs: %w", err)
|
||||||
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {
|
|
||||||
return nil, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.stashboxRepository())
|
if err := s.LoadStashIDs(ctx, r.repository.Scene); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
draft := &stashbox.SceneDraft{
|
||||||
|
Scene: s,
|
||||||
|
}
|
||||||
|
|
||||||
|
pqb := r.repository.Performer
|
||||||
|
sqb := r.repository.Studio
|
||||||
|
|
||||||
|
if s.StudioID != nil {
|
||||||
|
var err error
|
||||||
|
draft.Studio, err = sqb.Find(ctx, *s.StudioID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if draft.Studio == nil {
|
||||||
|
return nil, fmt.Errorf("studio with id %d not found", *s.StudioID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := draft.Studio.LoadStashIDs(ctx, r.repository.Studio); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// submit all file fingerprints
|
||||||
|
if err := s.LoadFiles(ctx, r.repository.Scene); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scenePerformers, err := pqb.FindBySceneID(ctx, s.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range scenePerformers {
|
||||||
|
if err := p.LoadStashIDs(ctx, pqb); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
draft.Performers = scenePerformers
|
||||||
|
|
||||||
|
draft.Tags, err = r.repository.Tag.FindBySceneID(ctx, s.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load StashIDs for tags
|
||||||
|
tqb := r.repository.Tag
|
||||||
|
for _, t := range draft.Tags {
|
||||||
|
if err := t.LoadStashIDs(ctx, tqb); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.Cover = cover
|
||||||
|
|
||||||
|
return draft, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, input StashBoxDraftSubmissionInput) (*string, error) {
|
||||||
|
b, err := resolveStashBox(input.StashBoxIndex, input.StashBoxEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
id, err := strconv.Atoi(input.ID)
|
id, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -101,7 +191,22 @@ func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, inp
|
||||||
return fmt.Errorf("performer with id %d not found", id)
|
return fmt.Errorf("performer with id %d not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err = client.SubmitPerformerDraft(ctx, performer, boxes[input.StashBoxIndex].Endpoint)
|
pqb := r.repository.Performer
|
||||||
|
if err := performer.LoadAliases(ctx, pqb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := performer.LoadURLs(ctx, pqb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := performer.LoadStashIDs(ctx, pqb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
img, _ := pqb.GetImage(ctx, performer.ID)
|
||||||
|
|
||||||
|
res, err = client.SubmitPerformerDraft(ctx, performer, img)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/plugin/hook"
|
"github.com/stashapp/stash/pkg/plugin/hook"
|
||||||
|
|
@ -32,22 +33,35 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio
|
||||||
// Populate a new studio from the input
|
// Populate a new studio from the input
|
||||||
newStudio := models.NewStudio()
|
newStudio := models.NewStudio()
|
||||||
|
|
||||||
newStudio.Name = input.Name
|
newStudio.Name = strings.TrimSpace(input.Name)
|
||||||
newStudio.URL = translator.string(input.URL)
|
|
||||||
newStudio.Rating = input.Rating100
|
newStudio.Rating = input.Rating100
|
||||||
newStudio.Favorite = translator.bool(input.Favorite)
|
newStudio.Favorite = translator.bool(input.Favorite)
|
||||||
newStudio.Details = translator.string(input.Details)
|
newStudio.Details = translator.string(input.Details)
|
||||||
newStudio.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag)
|
newStudio.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag)
|
||||||
newStudio.Aliases = models.NewRelatedStrings(input.Aliases)
|
newStudio.Aliases = models.NewRelatedStrings(stringslice.TrimSpace(input.Aliases))
|
||||||
newStudio.StashIDs = models.NewRelatedStashIDs(input.StashIds)
|
newStudio.StashIDs = models.NewRelatedStashIDs(models.StashIDInputs(input.StashIds).ToStashIDs())
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
newStudio.URLs = models.NewRelatedStrings([]string{})
|
||||||
|
if input.URL != nil {
|
||||||
|
newStudio.URLs.Add(strings.TrimSpace(*input.URL))
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.Urls != nil {
|
||||||
|
newStudio.URLs.Add(stringslice.TrimSpace(input.Urls)...)
|
||||||
|
}
|
||||||
|
|
||||||
newStudio.ParentID, err = translator.intPtrFromString(input.ParentID)
|
newStudio.ParentID, err = translator.intPtrFromString(input.ParentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting parent id: %w", err)
|
return nil, fmt.Errorf("converting parent id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newStudio.TagIDs, err = translator.relatedIds(input.TagIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Process the base 64 encoded image string
|
// Process the base 64 encoded image string
|
||||||
var imageData []byte
|
var imageData []byte
|
||||||
if input.Image != nil {
|
if input.Image != nil {
|
||||||
|
|
@ -101,7 +115,6 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
|
||||||
|
|
||||||
updatedStudio.ID = studioID
|
updatedStudio.ID = studioID
|
||||||
updatedStudio.Name = translator.optionalString(input.Name, "name")
|
updatedStudio.Name = translator.optionalString(input.Name, "name")
|
||||||
updatedStudio.URL = translator.optionalString(input.URL, "url")
|
|
||||||
updatedStudio.Details = translator.optionalString(input.Details, "details")
|
updatedStudio.Details = translator.optionalString(input.Details, "details")
|
||||||
updatedStudio.Rating = translator.optionalInt(input.Rating100, "rating100")
|
updatedStudio.Rating = translator.optionalInt(input.Rating100, "rating100")
|
||||||
updatedStudio.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
updatedStudio.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
||||||
|
|
@ -114,6 +127,31 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
|
||||||
return nil, fmt.Errorf("converting parent id: %w", err)
|
return nil, fmt.Errorf("converting parent id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedStudio.TagIDs, err = translator.updateIds(input.TagIds, "tag_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if translator.hasField("urls") {
|
||||||
|
// ensure url not included in the input
|
||||||
|
if err := r.validateNoLegacyURLs(translator); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedStudio.URLs = translator.updateStrings(input.Urls, "urls")
|
||||||
|
} else if translator.hasField("url") {
|
||||||
|
// handle legacy url field
|
||||||
|
legacyURLs := []string{}
|
||||||
|
if input.URL != nil {
|
||||||
|
legacyURLs = append(legacyURLs, *input.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedStudio.URLs = &models.UpdateStrings{
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
Values: legacyURLs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Process the base 64 encoded image string
|
// Process the base 64 encoded image string
|
||||||
var imageData []byte
|
var imageData []byte
|
||||||
imageIncluded := translator.hasField("image")
|
imageIncluded := translator.hasField("image")
|
||||||
|
|
@ -153,6 +191,96 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
|
||||||
return r.getStudio(ctx, studioID)
|
return r.getStudio(ctx, studioID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) BulkStudioUpdate(ctx context.Context, input BulkStudioUpdateInput) ([]*models.Studio, error) {
|
||||||
|
ids, err := stringslice.StringSliceToIntSlice(input.Ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
translator := changesetTranslator{
|
||||||
|
inputMap: getUpdateInputMap(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate performer from the input
|
||||||
|
partial := models.NewStudioPartial()
|
||||||
|
|
||||||
|
partial.ParentID, err = translator.optionalIntFromString(input.ParentID, "parent_id")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting parent id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if translator.hasField("urls") {
|
||||||
|
// ensure url/twitter/instagram are not included in the input
|
||||||
|
if err := r.validateNoLegacyURLs(translator); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
partial.URLs = translator.updateStringsBulk(input.Urls, "urls")
|
||||||
|
} else if translator.hasField("url") {
|
||||||
|
// handle legacy url field
|
||||||
|
legacyURLs := []string{}
|
||||||
|
if input.URL != nil {
|
||||||
|
legacyURLs = append(legacyURLs, *input.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
partial.URLs = &models.UpdateStrings{
|
||||||
|
Mode: models.RelationshipUpdateModeSet,
|
||||||
|
Values: legacyURLs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
||||||
|
partial.Rating = translator.optionalInt(input.Rating100, "rating100")
|
||||||
|
partial.Details = translator.optionalString(input.Details, "details")
|
||||||
|
partial.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
||||||
|
|
||||||
|
partial.TagIDs, err = translator.updateIdsBulk(input.TagIds, "tag_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []*models.Studio{}
|
||||||
|
|
||||||
|
// Start the transaction and save the performers
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Studio
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
local := partial
|
||||||
|
local.ID = id
|
||||||
|
if err := studio.ValidateModify(ctx, local, qb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err := qb.UpdatePartial(ctx, local)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute post hooks outside of txn
|
||||||
|
var newRet []*models.Studio
|
||||||
|
for _, studio := range ret {
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, studio.ID, hook.StudioUpdatePost, input, translator.getFields())
|
||||||
|
|
||||||
|
studio, err = r.getStudio(ctx, studio.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newRet = append(newRet, studio)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) StudioDestroy(ctx context.Context, input StudioDestroyInput) (bool, error) {
|
func (r *mutationResolver) StudioDestroy(ctx context.Context, input StudioDestroyInput) (bool, error) {
|
||||||
id, err := strconv.Atoi(input.ID)
|
id, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/logger"
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
|
@ -32,27 +33,31 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput)
|
||||||
// Populate a new tag from the input
|
// Populate a new tag from the input
|
||||||
newTag := models.NewTag()
|
newTag := models.NewTag()
|
||||||
|
|
||||||
newTag.Name = input.Name
|
newTag.Name = strings.TrimSpace(input.Name)
|
||||||
|
newTag.SortName = translator.string(input.SortName)
|
||||||
|
newTag.Aliases = models.NewRelatedStrings(stringslice.TrimSpace(input.Aliases))
|
||||||
newTag.Favorite = translator.bool(input.Favorite)
|
newTag.Favorite = translator.bool(input.Favorite)
|
||||||
newTag.Description = translator.string(input.Description)
|
newTag.Description = translator.string(input.Description)
|
||||||
newTag.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag)
|
newTag.IgnoreAutoTag = translator.bool(input.IgnoreAutoTag)
|
||||||
|
|
||||||
var err error
|
var stashIDInputs models.StashIDInputs
|
||||||
|
for _, sid := range input.StashIds {
|
||||||
var parentIDs []int
|
if sid != nil {
|
||||||
if len(input.ParentIds) > 0 {
|
stashIDInputs = append(stashIDInputs, *sid)
|
||||||
parentIDs, err = stringslice.StringSliceToIntSlice(input.ParentIds)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("converting parent ids: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
newTag.StashIDs = models.NewRelatedStashIDs(stashIDInputs.ToStashIDs())
|
||||||
|
|
||||||
var childIDs []int
|
var err error
|
||||||
if len(input.ChildIds) > 0 {
|
|
||||||
childIDs, err = stringslice.StringSliceToIntSlice(input.ChildIds)
|
newTag.ParentIDs, err = translator.relatedIds(input.ParentIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("converting child ids: %w", err)
|
return nil, fmt.Errorf("converting parent tag ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newTag.ChildIDs, err = translator.relatedIds(input.ChildIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting child tag ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the base 64 encoded image string
|
// Process the base 64 encoded image string
|
||||||
|
|
@ -68,8 +73,7 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput)
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
qb := r.repository.Tag
|
qb := r.repository.Tag
|
||||||
|
|
||||||
// ensure name is unique
|
if err := tag.ValidateCreate(ctx, newTag, qb); err != nil {
|
||||||
if err := tag.EnsureTagNameUnique(ctx, 0, newTag.Name, qb); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,36 +89,6 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(input.Aliases) > 0 {
|
|
||||||
if err := tag.EnsureAliasesUnique(ctx, newTag.ID, input.Aliases, qb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := qb.UpdateAliases(ctx, newTag.ID, input.Aliases); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parentIDs) > 0 {
|
|
||||||
if err := qb.UpdateParentTags(ctx, newTag.ID, parentIDs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(childIDs) > 0 {
|
|
||||||
if err := qb.UpdateChildTags(ctx, newTag.ID, childIDs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: This should be called before any changes are made, but
|
|
||||||
// requires a rewrite of ValidateHierarchy.
|
|
||||||
if len(parentIDs) > 0 || len(childIDs) > 0 {
|
|
||||||
if err := tag.ValidateHierarchy(ctx, &newTag, parentIDs, childIDs, qb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -137,24 +111,30 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput)
|
||||||
// Populate tag from the input
|
// Populate tag from the input
|
||||||
updatedTag := models.NewTagPartial()
|
updatedTag := models.NewTagPartial()
|
||||||
|
|
||||||
|
updatedTag.Name = translator.optionalString(input.Name, "name")
|
||||||
|
updatedTag.SortName = translator.optionalString(input.SortName, "sort_name")
|
||||||
updatedTag.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
updatedTag.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
||||||
updatedTag.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
updatedTag.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
||||||
updatedTag.Description = translator.optionalString(input.Description, "description")
|
updatedTag.Description = translator.optionalString(input.Description, "description")
|
||||||
|
|
||||||
var parentIDs []int
|
updatedTag.Aliases = translator.updateStrings(input.Aliases, "aliases")
|
||||||
if translator.hasField("parent_ids") {
|
|
||||||
parentIDs, err = stringslice.StringSliceToIntSlice(input.ParentIds)
|
var updateStashIDInputs models.StashIDInputs
|
||||||
if err != nil {
|
for _, sid := range input.StashIds {
|
||||||
return nil, fmt.Errorf("converting parent ids: %w", err)
|
if sid != nil {
|
||||||
|
updateStashIDInputs = append(updateStashIDInputs, *sid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updatedTag.StashIDs = translator.updateStashIDs(updateStashIDInputs, "stash_ids")
|
||||||
|
|
||||||
var childIDs []int
|
updatedTag.ParentIDs, err = translator.updateIds(input.ParentIds, "parent_ids")
|
||||||
if translator.hasField("child_ids") {
|
if err != nil {
|
||||||
childIDs, err = stringslice.StringSliceToIntSlice(input.ChildIds)
|
return nil, fmt.Errorf("converting parent tag ids: %w", err)
|
||||||
if err != nil {
|
}
|
||||||
return nil, fmt.Errorf("converting child ids: %w", err)
|
|
||||||
}
|
updatedTag.ChildIDs, err = translator.updateIds(input.ChildIds, "child_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting child tag ids: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageData []byte
|
var imageData []byte
|
||||||
|
|
@ -171,24 +151,10 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput)
|
||||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
qb := r.repository.Tag
|
qb := r.repository.Tag
|
||||||
|
|
||||||
// ensure name is unique
|
if err := tag.ValidateUpdate(ctx, tagID, updatedTag, qb); err != nil {
|
||||||
t, err = qb.Find(ctx, tagID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t == nil {
|
|
||||||
return fmt.Errorf("tag with id %d not found", tagID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.Name != nil && t.Name != *input.Name {
|
|
||||||
if err := tag.EnsureTagNameUnique(ctx, tagID, *input.Name, qb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedTag.Name = models.NewOptionalString(*input.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err = qb.UpdatePartial(ctx, tagID, updatedTag)
|
t, err = qb.UpdatePartial(ctx, tagID, updatedTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -201,37 +167,6 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if translator.hasField("aliases") {
|
|
||||||
if err := tag.EnsureAliasesUnique(ctx, tagID, input.Aliases, qb); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := qb.UpdateAliases(ctx, tagID, input.Aliases); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentIDs != nil {
|
|
||||||
if err := qb.UpdateParentTags(ctx, tagID, parentIDs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if childIDs != nil {
|
|
||||||
if err := qb.UpdateChildTags(ctx, tagID, childIDs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: This should be called before any changes are made, but
|
|
||||||
// requires a rewrite of ValidateHierarchy.
|
|
||||||
if parentIDs != nil || childIDs != nil {
|
|
||||||
if err := tag.ValidateHierarchy(ctx, t, parentIDs, childIDs, qb); err != nil {
|
|
||||||
logger.Errorf("Error saving tag: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -241,6 +176,75 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput)
|
||||||
return r.getTag(ctx, t.ID)
|
return r.getTag(ctx, t.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) BulkTagUpdate(ctx context.Context, input BulkTagUpdateInput) ([]*models.Tag, error) {
|
||||||
|
tagIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
translator := changesetTranslator{
|
||||||
|
inputMap: getUpdateInputMap(ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate scene from the input
|
||||||
|
updatedTag := models.NewTagPartial()
|
||||||
|
|
||||||
|
updatedTag.Description = translator.optionalString(input.Description, "description")
|
||||||
|
updatedTag.Favorite = translator.optionalBool(input.Favorite, "favorite")
|
||||||
|
updatedTag.IgnoreAutoTag = translator.optionalBool(input.IgnoreAutoTag, "ignore_auto_tag")
|
||||||
|
|
||||||
|
updatedTag.Aliases = translator.updateStringsBulk(input.Aliases, "aliases")
|
||||||
|
|
||||||
|
updatedTag.ParentIDs, err = translator.updateIdsBulk(input.ParentIds, "parent_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting parent tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedTag.ChildIDs, err = translator.updateIdsBulk(input.ChildIds, "child_ids")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("converting child tag ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []*models.Tag{}
|
||||||
|
|
||||||
|
// Start the transaction and save the scenes
|
||||||
|
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Tag
|
||||||
|
|
||||||
|
for _, tagID := range tagIDs {
|
||||||
|
if err := tag.ValidateUpdate(ctx, tagID, updatedTag, qb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tag, err := qb.UpdatePartial(ctx, tagID, updatedTag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute post hooks outside of txn
|
||||||
|
var newRet []*models.Tag
|
||||||
|
for _, tag := range ret {
|
||||||
|
r.hookExecutor.ExecutePostHooks(ctx, tag.ID, hook.TagUpdatePost, input, translator.getFields())
|
||||||
|
|
||||||
|
tag, err = r.getTag(ctx, tag.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newRet = append(newRet, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRet, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) TagDestroy(ctx context.Context, input TagDestroyInput) (bool, error) {
|
func (r *mutationResolver) TagDestroy(ctx context.Context, input TagDestroyInput) (bool, error) {
|
||||||
tagID, err := strconv.Atoi(input.ID)
|
tagID, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -331,7 +335,7 @@ func (r *mutationResolver) TagsMerge(ctx context.Context, input TagsMergeInput)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tag.ValidateHierarchy(ctx, t, parents, children, qb)
|
err = tag.ValidateHierarchyExisting(ctx, t, parents, children, qb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Error merging tag: %s", err)
|
logger.Errorf("Error merging tag: %s", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/stashapp/stash/internal/manager/config"
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
"github.com/stashapp/stash/pkg/fsutil"
|
"github.com/stashapp/stash/pkg/fsutil"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/scraper/stashbox"
|
|
||||||
"golang.org/x/text/collate"
|
"golang.org/x/text/collate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,6 +82,7 @@ func makeConfigGeneralResult() *ConfigGeneralResult {
|
||||||
Stashes: config.GetStashPaths(),
|
Stashes: config.GetStashPaths(),
|
||||||
DatabasePath: config.GetDatabasePath(),
|
DatabasePath: config.GetDatabasePath(),
|
||||||
BackupDirectoryPath: config.GetBackupDirectoryPath(),
|
BackupDirectoryPath: config.GetBackupDirectoryPath(),
|
||||||
|
DeleteTrashPath: config.GetDeleteTrashPath(),
|
||||||
GeneratedPath: config.GetGeneratedPath(),
|
GeneratedPath: config.GetGeneratedPath(),
|
||||||
MetadataPath: config.GetMetadataPath(),
|
MetadataPath: config.GetMetadataPath(),
|
||||||
ConfigFilePath: config.GetConfigFile(),
|
ConfigFilePath: config.GetConfigFile(),
|
||||||
|
|
@ -116,6 +116,7 @@ func makeConfigGeneralResult() *ConfigGeneralResult {
|
||||||
LogOut: config.GetLogOut(),
|
LogOut: config.GetLogOut(),
|
||||||
LogLevel: config.GetLogLevel(),
|
LogLevel: config.GetLogLevel(),
|
||||||
LogAccess: config.GetLogAccess(),
|
LogAccess: config.GetLogAccess(),
|
||||||
|
LogFileMaxSize: config.GetLogFileMaxSize(),
|
||||||
VideoExtensions: config.GetVideoExtensions(),
|
VideoExtensions: config.GetVideoExtensions(),
|
||||||
ImageExtensions: config.GetImageExtensions(),
|
ImageExtensions: config.GetImageExtensions(),
|
||||||
GalleryExtensions: config.GetGalleryExtensions(),
|
GalleryExtensions: config.GetGalleryExtensions(),
|
||||||
|
|
@ -163,6 +164,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
|
||||||
disableDropdownCreate := config.GetDisableDropdownCreate()
|
disableDropdownCreate := config.GetDisableDropdownCreate()
|
||||||
|
|
||||||
return &ConfigInterfaceResult{
|
return &ConfigInterfaceResult{
|
||||||
|
SfwContentMode: config.GetSFWContentMode(),
|
||||||
MenuItems: menuItems,
|
MenuItems: menuItems,
|
||||||
SoundOnPreview: &soundOnPreview,
|
SoundOnPreview: &soundOnPreview,
|
||||||
WallShowTitle: &wallShowTitle,
|
WallShowTitle: &wallShowTitle,
|
||||||
|
|
@ -241,7 +243,7 @@ func makeConfigUIResult() map[string]interface{} {
|
||||||
|
|
||||||
func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input config.StashBoxInput) (*StashBoxValidationResult, error) {
|
func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input config.StashBoxInput) (*StashBoxValidationResult, error) {
|
||||||
box := models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}
|
box := models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}
|
||||||
client := stashbox.NewClient(box, r.stashboxRepository())
|
client := r.newStashBoxClient(box)
|
||||||
|
|
||||||
user, err := client.GetUser(ctx)
|
user, err := client.GetUser(ctx)
|
||||||
|
|
||||||
|
|
@ -250,18 +252,19 @@ func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input c
|
||||||
if valid {
|
if valid {
|
||||||
status = fmt.Sprintf("Successfully authenticated as %s", user.Me.Name)
|
status = fmt.Sprintf("Successfully authenticated as %s", user.Me.Name)
|
||||||
} else {
|
} else {
|
||||||
|
errorStr := strings.ToLower(err.Error())
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(strings.ToLower(err.Error()), "doctype"):
|
case strings.Contains(errorStr, "doctype"):
|
||||||
// Index file returned rather than graphql
|
// Index file returned rather than graphql
|
||||||
status = "Invalid endpoint"
|
status = "Invalid endpoint"
|
||||||
case strings.Contains(err.Error(), "request failed"):
|
case strings.Contains(errorStr, "request failed"):
|
||||||
status = "No response from server"
|
status = "No response from server"
|
||||||
case strings.HasPrefix(err.Error(), "invalid character") ||
|
case strings.Contains(errorStr, "invalid character") ||
|
||||||
strings.HasPrefix(err.Error(), "illegal base64 data") ||
|
strings.Contains(errorStr, "illegal base64 data") ||
|
||||||
err.Error() == "unexpected end of JSON input" ||
|
strings.Contains(errorStr, "unexpected end of json input") ||
|
||||||
err.Error() == "token contains an invalid number of segments":
|
strings.Contains(errorStr, "token contains an invalid number of segments"):
|
||||||
status = "Malformed API key."
|
status = "Malformed API key."
|
||||||
case err.Error() == "" || err.Error() == "signature is invalid":
|
case strings.Contains(errorStr, "signature is invalid"):
|
||||||
status = "Invalid or expired API key."
|
status = "Invalid or expired API key."
|
||||||
default:
|
default:
|
||||||
status = fmt.Sprintf("Unknown error: %s", err)
|
status = fmt.Sprintf("Unknown error: %s", err)
|
||||||
|
|
|
||||||
120
internal/api/resolver_query_find_file.go
Normal file
120
internal/api/resolver_query_find_file.go
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *queryResolver) FindFile(ctx context.Context, id *string, path *string) (BaseFile, error) {
|
||||||
|
var ret models.File
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.File
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case id != nil:
|
||||||
|
idInt, err := strconv.Atoi(*id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var files []models.File
|
||||||
|
files, err = qb.Find(ctx, models.FileID(idInt))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(files) > 0 {
|
||||||
|
ret = files[0]
|
||||||
|
}
|
||||||
|
case path != nil:
|
||||||
|
ret, err = qb.FindByPath(ctx, *path, true)
|
||||||
|
if err == nil && ret == nil {
|
||||||
|
return errors.New("file not found")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("either id or path must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertBaseFile(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) FindFiles(
|
||||||
|
ctx context.Context,
|
||||||
|
fileFilter *models.FileFilterType,
|
||||||
|
filter *models.FindFilterType,
|
||||||
|
ids []string,
|
||||||
|
) (ret *FindFilesResultType, err error) {
|
||||||
|
var fileIDs []models.FileID
|
||||||
|
if len(ids) > 0 {
|
||||||
|
fileIDsInt, err := stringslice.StringSliceToIntSlice(ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileIDs = models.FileIDsFromInts(fileIDsInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
var files []models.File
|
||||||
|
var err error
|
||||||
|
|
||||||
|
fields := collectQueryFields(ctx)
|
||||||
|
result := &models.FileQueryResult{}
|
||||||
|
|
||||||
|
if len(fileIDs) > 0 {
|
||||||
|
files, err = r.repository.File.Find(ctx, fileIDs...)
|
||||||
|
if err == nil {
|
||||||
|
result.Count = len(files)
|
||||||
|
for _, f := range files {
|
||||||
|
if asVideo, ok := f.(*models.VideoFile); ok {
|
||||||
|
result.TotalDuration += asVideo.Duration
|
||||||
|
}
|
||||||
|
if asImage, ok := f.(*models.ImageFile); ok {
|
||||||
|
result.Megapixels += asImage.Megapixels()
|
||||||
|
}
|
||||||
|
|
||||||
|
result.TotalSize += f.Base().Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result, err = r.repository.File.Query(ctx, models.FileQueryOptions{
|
||||||
|
QueryOptions: models.QueryOptions{
|
||||||
|
FindFilter: filter,
|
||||||
|
Count: fields.Has("count"),
|
||||||
|
},
|
||||||
|
FileFilter: fileFilter,
|
||||||
|
TotalDuration: fields.Has("duration"),
|
||||||
|
Megapixels: fields.Has("megapixels"),
|
||||||
|
TotalSize: fields.Has("size"),
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
files, err = result.Resolve(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = &FindFilesResultType{
|
||||||
|
Count: result.Count,
|
||||||
|
Files: convertBaseFiles(files),
|
||||||
|
Duration: result.TotalDuration,
|
||||||
|
Megapixels: result.Megapixels,
|
||||||
|
Size: int(result.TotalSize),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
100
internal/api/resolver_query_find_folder.go
Normal file
100
internal/api/resolver_query_find_folder.go
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *queryResolver) FindFolder(ctx context.Context, id *string, path *string) (*models.Folder, error) {
|
||||||
|
var ret *models.Folder
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Folder
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case id != nil:
|
||||||
|
idInt, err := strconv.Atoi(*id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ret, err = qb.Find(ctx, models.FolderID(idInt))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case path != nil:
|
||||||
|
ret, err = qb.FindByPath(ctx, *path, true)
|
||||||
|
if err == nil && ret == nil {
|
||||||
|
return errors.New("folder not found")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("either id or path must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) FindFolders(
|
||||||
|
ctx context.Context,
|
||||||
|
folderFilter *models.FolderFilterType,
|
||||||
|
filter *models.FindFilterType,
|
||||||
|
ids []string,
|
||||||
|
) (ret *FindFoldersResultType, err error) {
|
||||||
|
var folderIDs []models.FolderID
|
||||||
|
if len(ids) > 0 {
|
||||||
|
folderIDsInt, err := stringslice.StringSliceToIntSlice(ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
folderIDs = models.FolderIDsFromInts(folderIDsInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
var folders []*models.Folder
|
||||||
|
var err error
|
||||||
|
|
||||||
|
fields := collectQueryFields(ctx)
|
||||||
|
result := &models.FolderQueryResult{}
|
||||||
|
|
||||||
|
if len(folderIDs) > 0 {
|
||||||
|
folders, err = r.repository.Folder.FindMany(ctx, folderIDs)
|
||||||
|
if err == nil {
|
||||||
|
result.Count = len(folders)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result, err = r.repository.Folder.Query(ctx, models.FolderQueryOptions{
|
||||||
|
QueryOptions: models.QueryOptions{
|
||||||
|
FindFilter: filter,
|
||||||
|
Count: fields.Has("count"),
|
||||||
|
},
|
||||||
|
FolderFilter: folderFilter,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
folders, err = result.Resolve(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = &FindFoldersResultType{
|
||||||
|
Count: result.Count,
|
||||||
|
Folders: folders,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
59
internal/api/resolver_query_find_group.go
Normal file
59
internal/api/resolver_query_find_group.go
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *queryResolver) FindGroup(ctx context.Context, id string) (ret *models.Group, err error) {
|
||||||
|
idInt, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
ret, err = r.repository.Group.Find(ctx, idInt)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) FindGroups(ctx context.Context, groupFilter *models.GroupFilterType, filter *models.FindFilterType, ids []string) (ret *FindGroupsResultType, err error) {
|
||||||
|
idInts, err := stringslice.StringSliceToIntSlice(ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
var groups []*models.Group
|
||||||
|
var err error
|
||||||
|
var total int
|
||||||
|
|
||||||
|
if len(idInts) > 0 {
|
||||||
|
groups, err = r.repository.Group.FindMany(ctx, idInts)
|
||||||
|
total = len(groups)
|
||||||
|
} else {
|
||||||
|
groups, total, err = r.repository.Group.Query(ctx, groupFilter, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = &FindGroupsResultType{
|
||||||
|
Count: total,
|
||||||
|
Groups: groups,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,11 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql"
|
"github.com/99designs/gqlgen/graphql"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/sliceutil"
|
|
||||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -95,11 +95,11 @@ func (r *queryResolver) FindImages(
|
||||||
result, err = qb.Query(ctx, models.ImageQueryOptions{
|
result, err = qb.Query(ctx, models.ImageQueryOptions{
|
||||||
QueryOptions: models.QueryOptions{
|
QueryOptions: models.QueryOptions{
|
||||||
FindFilter: filter,
|
FindFilter: filter,
|
||||||
Count: sliceutil.Contains(fields, "count"),
|
Count: slices.Contains(fields, "count"),
|
||||||
},
|
},
|
||||||
ImageFilter: imageFilter,
|
ImageFilter: imageFilter,
|
||||||
Megapixels: sliceutil.Contains(fields, "megapixels"),
|
Megapixels: slices.Contains(fields, "megapixels"),
|
||||||
TotalSize: sliceutil.Contains(fields, "filesize"),
|
TotalSize: slices.Contains(fields, "filesize"),
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
images, err = result.Resolve(ctx)
|
images, err = result.Resolve(ctx)
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,14 @@ import (
|
||||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *queryResolver) FindMovie(ctx context.Context, id string) (ret *models.Movie, err error) {
|
func (r *queryResolver) FindMovie(ctx context.Context, id string) (ret *models.Group, err error) {
|
||||||
idInt, err := strconv.Atoi(id)
|
idInt, err := strconv.Atoi(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Movie.Find(ctx, idInt)
|
ret, err = r.repository.Group.Find(ctx, idInt)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -24,22 +24,22 @@ func (r *queryResolver) FindMovie(ctx context.Context, id string) (ret *models.M
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.MovieFilterType, filter *models.FindFilterType, ids []string) (ret *FindMoviesResultType, err error) {
|
func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.GroupFilterType, filter *models.FindFilterType, ids []string) (ret *FindMoviesResultType, err error) {
|
||||||
idInts, err := stringslice.StringSliceToIntSlice(ids)
|
idInts, err := stringslice.StringSliceToIntSlice(ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
var movies []*models.Movie
|
var groups []*models.Group
|
||||||
var err error
|
var err error
|
||||||
var total int
|
var total int
|
||||||
|
|
||||||
if len(idInts) > 0 {
|
if len(idInts) > 0 {
|
||||||
movies, err = r.repository.Movie.FindMany(ctx, idInts)
|
groups, err = r.repository.Group.FindMany(ctx, idInts)
|
||||||
total = len(movies)
|
total = len(groups)
|
||||||
} else {
|
} else {
|
||||||
movies, total, err = r.repository.Movie.Query(ctx, movieFilter, filter)
|
groups, total, err = r.repository.Group.Query(ctx, movieFilter, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -48,7 +48,7 @@ func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.Movi
|
||||||
|
|
||||||
ret = &FindMoviesResultType{
|
ret = &FindMoviesResultType{
|
||||||
Count: total,
|
Count: total,
|
||||||
Movies: movies,
|
Movies: groups,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|
@ -58,9 +58,9 @@ func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.Movi
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) AllMovies(ctx context.Context) (ret []*models.Movie, err error) {
|
func (r *queryResolver) AllMovies(ctx context.Context) (ret []*models.Group, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Movie.All(ctx)
|
ret, err = r.repository.Group.All(ctx)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ func (r *queryResolver) FindPerformers(ctx context.Context, performerFilter *mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #5682 - convert JSON numbers to float64 or int64
|
||||||
|
if performerFilter != nil {
|
||||||
|
performerFilter.CustomFields = convertCustomFieldCriterionInputJSONNumbers(performerFilter.CustomFields)
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
var performers []*models.Performer
|
var performers []*models.Performer
|
||||||
var err error
|
var err error
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,12 @@ package api
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *queryResolver) FindSavedFilter(ctx context.Context, id string) (ret *models.SavedFilter, err error) {
|
func (r *queryResolver) FindSavedFilter(ctx context.Context, id string) (ret *models.SavedFilter, err error) {
|
||||||
|
|
@ -37,11 +41,35 @@ func (r *queryResolver) FindSavedFilters(ctx context.Context, mode *models.Filte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) FindDefaultFilter(ctx context.Context, mode models.FilterMode) (ret *models.SavedFilter, err error) {
|
func (r *queryResolver) FindDefaultFilter(ctx context.Context, mode models.FilterMode) (ret *models.SavedFilter, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
// deprecated - read from the config in the meantime
|
||||||
ret, err = r.repository.SavedFilter.FindDefault(ctx, mode)
|
config := config.GetInstance()
|
||||||
return err
|
|
||||||
}); err != nil {
|
uiConfig := config.GetUIConfiguration()
|
||||||
|
if uiConfig == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m := utils.NestedMap(uiConfig)
|
||||||
|
filterRaw, _ := m.Get("defaultFilters." + strings.ToLower(mode.String()))
|
||||||
|
|
||||||
|
if filterRaw == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = &models.SavedFilter{}
|
||||||
|
d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
|
TagName: "json",
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: ret,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ret, err
|
|
||||||
|
if err := d.Decode(filterRaw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql"
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/scene"
|
"github.com/stashapp/stash/pkg/scene"
|
||||||
"github.com/stashapp/stash/pkg/sliceutil"
|
|
||||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -119,11 +119,11 @@ func (r *queryResolver) FindScenes(
|
||||||
result, err = r.repository.Scene.Query(ctx, models.SceneQueryOptions{
|
result, err = r.repository.Scene.Query(ctx, models.SceneQueryOptions{
|
||||||
QueryOptions: models.QueryOptions{
|
QueryOptions: models.QueryOptions{
|
||||||
FindFilter: filter,
|
FindFilter: filter,
|
||||||
Count: sliceutil.Contains(fields, "count"),
|
Count: slices.Contains(fields, "count"),
|
||||||
},
|
},
|
||||||
SceneFilter: sceneFilter,
|
SceneFilter: sceneFilter,
|
||||||
TotalDuration: sliceutil.Contains(fields, "duration"),
|
TotalDuration: slices.Contains(fields, "duration"),
|
||||||
TotalSize: sliceutil.Contains(fields, "filesize"),
|
TotalSize: slices.Contains(fields, "filesize"),
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
scenes, err = result.Resolve(ctx)
|
scenes, err = result.Resolve(ctx)
|
||||||
|
|
@ -174,11 +174,11 @@ func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *model
|
||||||
result, err := r.repository.Scene.Query(ctx, models.SceneQueryOptions{
|
result, err := r.repository.Scene.Query(ctx, models.SceneQueryOptions{
|
||||||
QueryOptions: models.QueryOptions{
|
QueryOptions: models.QueryOptions{
|
||||||
FindFilter: queryFilter,
|
FindFilter: queryFilter,
|
||||||
Count: sliceutil.Contains(fields, "count"),
|
Count: slices.Contains(fields, "count"),
|
||||||
},
|
},
|
||||||
SceneFilter: sceneFilter,
|
SceneFilter: sceneFilter,
|
||||||
TotalDuration: sliceutil.Contains(fields, "duration"),
|
TotalDuration: slices.Contains(fields, "duration"),
|
||||||
TotalSize: sliceutil.Contains(fields, "filesize"),
|
TotalSize: slices.Contains(fields, "filesize"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,31 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *queryResolver) FindSceneMarkers(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, filter *models.FindFilterType) (ret *FindSceneMarkersResultType, err error) {
|
func (r *queryResolver) FindSceneMarkers(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, filter *models.FindFilterType, ids []string) (ret *FindSceneMarkersResultType, err error) {
|
||||||
|
idInts, err := stringslice.StringSliceToIntSlice(ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
sceneMarkers, total, err := r.repository.SceneMarker.Query(ctx, sceneMarkerFilter, filter)
|
var sceneMarkers []*models.SceneMarker
|
||||||
|
var err error
|
||||||
|
var total int
|
||||||
|
|
||||||
|
if len(idInts) > 0 {
|
||||||
|
sceneMarkers, err = r.repository.SceneMarker.FindMany(ctx, idInts)
|
||||||
|
total = len(sceneMarkers)
|
||||||
|
} else {
|
||||||
|
sceneMarkers, total, err = r.repository.SceneMarker.Query(ctx, sceneMarkerFilter, filter)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = &FindSceneMarkersResultType{
|
ret = &FindSceneMarkersResultType{
|
||||||
Count: total,
|
Count: total,
|
||||||
SceneMarkers: sceneMarkers,
|
SceneMarkers: sceneMarkers,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,11 @@ func (r *queryResolver) FindTags(ctx context.Context, tagFilter *models.TagFilte
|
||||||
func (r *queryResolver) AllTags(ctx context.Context) (ret []*models.Tag, err error) {
|
func (r *queryResolver) AllTags(ctx context.Context) (ret []*models.Tag, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
ret, err = r.repository.Tag.All(ctx)
|
ret, err = r.repository.Tag.All(ctx)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -11,7 +12,6 @@ import (
|
||||||
"github.com/stashapp/stash/internal/manager"
|
"github.com/stashapp/stash/internal/manager"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/pkg"
|
"github.com/stashapp/stash/pkg/pkg"
|
||||||
"github.com/stashapp/stash/pkg/sliceutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidPackageType = errors.New("invalid package type")
|
var ErrInvalidPackageType = errors.New("invalid package type")
|
||||||
|
|
@ -166,7 +166,7 @@ func (r *queryResolver) InstalledPackages(ctx context.Context, typeArg PackageTy
|
||||||
|
|
||||||
var ret []*Package
|
var ret []*Package
|
||||||
|
|
||||||
if sliceutil.Contains(graphql.CollectAllFields(ctx), "source_package") {
|
if slices.Contains(graphql.CollectAllFields(ctx), "source_package") {
|
||||||
ret, err = r.getInstalledPackagesWithUpgrades(ctx, pm)
|
ret, err = r.getInstalledPackagesWithUpgrades(ctx, pm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/manager"
|
"github.com/stashapp/stash/pkg/match"
|
||||||
"github.com/stashapp/stash/internal/manager/config"
|
|
||||||
"github.com/stashapp/stash/pkg/logger"
|
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/scraper"
|
"github.com/stashapp/stash/pkg/scraper"
|
||||||
"github.com/stashapp/stash/pkg/scraper/stashbox"
|
|
||||||
"github.com/stashapp/stash/pkg/sliceutil"
|
"github.com/stashapp/stash/pkg/sliceutil"
|
||||||
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
)
|
)
|
||||||
|
|
@ -35,7 +31,7 @@ func (r *queryResolver) ScrapePerformerURL(ctx context.Context, url string) (*mo
|
||||||
return marshalScrapedPerformer(content)
|
return marshalScrapedPerformer(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeSceneQuery(ctx context.Context, scraperID string, query string) ([]*scraper.ScrapedScene, error) {
|
func (r *queryResolver) ScrapeSceneQuery(ctx context.Context, scraperID string, query string) ([]*models.ScrapedScene, error) {
|
||||||
if query == "" {
|
if query == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -50,102 +46,10 @@ func (r *queryResolver) ScrapeSceneQuery(ctx context.Context, scraperID string,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filterSceneTags(ret)
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileRegexps(patterns []string) []*regexp.Regexp {
|
func (r *queryResolver) ScrapeSceneURL(ctx context.Context, url string) (*models.ScrapedScene, error) {
|
||||||
excludePatterns := patterns
|
|
||||||
var excludeRegexps []*regexp.Regexp
|
|
||||||
|
|
||||||
for _, excludePattern := range excludePatterns {
|
|
||||||
reg, err := regexp.Compile(strings.ToLower(excludePattern))
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Invalid tag exclusion pattern: %v", err)
|
|
||||||
} else {
|
|
||||||
excludeRegexps = append(excludeRegexps, reg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return excludeRegexps
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterSceneTags removes tags matching excluded tag patterns from the provided scraped scenes
|
|
||||||
func filterTags(excludeRegexps []*regexp.Regexp, tags []*models.ScrapedTag) (newTags []*models.ScrapedTag, ignoredTags []string) {
|
|
||||||
if len(excludeRegexps) == 0 {
|
|
||||||
return tags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range tags {
|
|
||||||
ignore := false
|
|
||||||
for _, reg := range excludeRegexps {
|
|
||||||
if reg.MatchString(strings.ToLower(t.Name)) {
|
|
||||||
ignore = true
|
|
||||||
ignoredTags = sliceutil.AppendUnique(ignoredTags, t.Name)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ignore {
|
|
||||||
newTags = append(newTags, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterSceneTags removes tags matching excluded tag patterns from the provided scraped scenes
|
|
||||||
func filterSceneTags(scenes []*scraper.ScrapedScene) {
|
|
||||||
excludeRegexps := compileRegexps(manager.GetInstance().Config.GetScraperExcludeTagPatterns())
|
|
||||||
|
|
||||||
var ignoredTags []string
|
|
||||||
|
|
||||||
for _, s := range scenes {
|
|
||||||
var ignored []string
|
|
||||||
s.Tags, ignored = filterTags(excludeRegexps, s.Tags)
|
|
||||||
ignoredTags = sliceutil.AppendUniques(ignoredTags, ignored)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ignoredTags) > 0 {
|
|
||||||
logger.Debugf("Scraping ignored tags: %s", strings.Join(ignoredTags, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterGalleryTags removes tags matching excluded tag patterns from the provided scraped galleries
|
|
||||||
func filterGalleryTags(g []*scraper.ScrapedGallery) {
|
|
||||||
excludeRegexps := compileRegexps(manager.GetInstance().Config.GetScraperExcludeTagPatterns())
|
|
||||||
|
|
||||||
var ignoredTags []string
|
|
||||||
|
|
||||||
for _, s := range g {
|
|
||||||
var ignored []string
|
|
||||||
s.Tags, ignored = filterTags(excludeRegexps, s.Tags)
|
|
||||||
ignoredTags = sliceutil.AppendUniques(ignoredTags, ignored)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ignoredTags) > 0 {
|
|
||||||
logger.Debugf("Scraping ignored tags: %s", strings.Join(ignoredTags, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterGalleryTags removes tags matching excluded tag patterns from the provided scraped galleries
|
|
||||||
func filterPerformerTags(p []*models.ScrapedPerformer) {
|
|
||||||
excludeRegexps := compileRegexps(manager.GetInstance().Config.GetScraperExcludeTagPatterns())
|
|
||||||
|
|
||||||
var ignoredTags []string
|
|
||||||
|
|
||||||
for _, s := range p {
|
|
||||||
var ignored []string
|
|
||||||
s.Tags, ignored = filterTags(excludeRegexps, s.Tags)
|
|
||||||
ignoredTags = sliceutil.AppendUniques(ignoredTags, ignored)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ignoredTags) > 0 {
|
|
||||||
logger.Debugf("Scraping ignored tags: %s", strings.Join(ignoredTags, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeSceneURL(ctx context.Context, url string) (*scraper.ScrapedScene, error) {
|
|
||||||
content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeScene)
|
content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeScene)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -156,14 +60,10 @@ func (r *queryResolver) ScrapeSceneURL(ctx context.Context, url string) (*scrape
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ret != nil {
|
|
||||||
filterSceneTags([]*scraper.ScrapedScene{ret})
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeGalleryURL(ctx context.Context, url string) (*scraper.ScrapedGallery, error) {
|
func (r *queryResolver) ScrapeGalleryURL(ctx context.Context, url string) (*models.ScrapedGallery, error) {
|
||||||
content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeGallery)
|
content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeGallery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -174,11 +74,16 @@ func (r *queryResolver) ScrapeGalleryURL(ctx context.Context, url string) (*scra
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ret != nil {
|
return ret, nil
|
||||||
filterGalleryTags([]*scraper.ScrapedGallery{ret})
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) ScrapeImageURL(ctx context.Context, url string) (*models.ScrapedImage, error) {
|
||||||
|
content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, nil
|
return marshalScrapedImage(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeMovieURL(ctx context.Context, url string) (*models.ScrapedMovie, error) {
|
func (r *queryResolver) ScrapeMovieURL(ctx context.Context, url string) (*models.ScrapedMovie, error) {
|
||||||
|
|
@ -187,23 +92,47 @@ func (r *queryResolver) ScrapeMovieURL(ctx context.Context, url string) (*models
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return marshalScrapedMovie(content)
|
ret, err := marshalScrapedMovie(content)
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
func (r *queryResolver) getStashBoxClient(index int) (*stashbox.Client, error) {
|
|
||||||
boxes := config.GetInstance().GetStashBoxes()
|
|
||||||
|
|
||||||
if index < 0 || index >= len(boxes) {
|
|
||||||
return nil, fmt.Errorf("%w: invalid stash_box_index %d", ErrInput, index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return stashbox.NewClient(*boxes[index], r.stashboxRepository()), nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - in the following resolvers, we're processing the deprecated field and not processing the new endpoint input
|
func (r *queryResolver) ScrapeGroupURL(ctx context.Context, url string) (*models.ScrapedGroup, error) {
|
||||||
|
content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source scraper.Source, input ScrapeSingleSceneInput) ([]*scraper.ScrapedScene, error) {
|
ret, err := marshalScrapedGroup(content)
|
||||||
var ret []*scraper.ScrapedScene
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to scraped group
|
||||||
|
group := &models.ScrapedGroup{
|
||||||
|
StoredID: ret.StoredID,
|
||||||
|
Name: ret.Name,
|
||||||
|
Aliases: ret.Aliases,
|
||||||
|
Duration: ret.Duration,
|
||||||
|
Date: ret.Date,
|
||||||
|
Rating: ret.Rating,
|
||||||
|
Director: ret.Director,
|
||||||
|
URLs: ret.URLs,
|
||||||
|
Synopsis: ret.Synopsis,
|
||||||
|
Studio: ret.Studio,
|
||||||
|
Tags: ret.Tags,
|
||||||
|
FrontImage: ret.FrontImage,
|
||||||
|
BackImage: ret.BackImage,
|
||||||
|
}
|
||||||
|
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source scraper.Source, input ScrapeSingleSceneInput) ([]*models.ScrapedScene, error) {
|
||||||
|
var ret []*models.ScrapedScene
|
||||||
|
|
||||||
var sceneID int
|
var sceneID int
|
||||||
if input.SceneID != nil {
|
if input.SceneID != nil {
|
||||||
|
|
@ -245,17 +174,24 @@ func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source scraper.So
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case source.StashBoxIndex != nil:
|
case source.StashBoxIndex != nil || source.StashBoxEndpoint != nil:
|
||||||
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
b, err := resolveStashBox(source.StashBoxIndex, source.StashBoxEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case input.SceneID != nil:
|
case input.SceneID != nil:
|
||||||
ret, err = client.FindStashBoxSceneByFingerprints(ctx, sceneID)
|
var fps []models.Fingerprints
|
||||||
|
fps, err = r.getScenesFingerprints(ctx, []int{sceneID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ret, err = client.FindSceneByFingerprints(ctx, fps[0])
|
||||||
case input.Query != nil:
|
case input.Query != nil:
|
||||||
ret, err = client.QueryStashBoxScene(ctx, *input.Query)
|
ret, err = client.QueryScene(ctx, *input.Query)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: scene_id or query must be set", ErrInput)
|
return nil, fmt.Errorf("%w: scene_id or query must be set", ErrInput)
|
||||||
}
|
}
|
||||||
|
|
@ -263,44 +199,132 @@ func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source scraper.So
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO - this should happen after any scene is scraped
|
||||||
|
if err := r.matchScenesRelationships(ctx, ret, b.Endpoint); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: scraper_id or stash_box_index must be set", ErrInput)
|
return nil, fmt.Errorf("%w: scraper_id or stash_box_index must be set", ErrInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
filterSceneTags(ret)
|
for i := range ret {
|
||||||
|
slices.SortFunc(ret[i].Tags, models.ScrapedTagSortFunction)
|
||||||
|
}
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeMultiScenes(ctx context.Context, source scraper.Source, input ScrapeMultiScenesInput) ([][]*scraper.ScrapedScene, error) {
|
func (r *queryResolver) ScrapeMultiScenes(ctx context.Context, source scraper.Source, input ScrapeMultiScenesInput) ([][]*models.ScrapedScene, error) {
|
||||||
if source.ScraperID != nil {
|
if source.ScraperID != nil {
|
||||||
return nil, ErrNotImplemented
|
return nil, ErrNotImplemented
|
||||||
} else if source.StashBoxIndex != nil {
|
} else if source.StashBoxIndex != nil || source.StashBoxEndpoint != nil {
|
||||||
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
b, err := resolveStashBox(source.StashBoxIndex, source.StashBoxEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
sceneIDs, err := stringslice.StringSliceToIntSlice(input.SceneIds)
|
sceneIDs, err := stringslice.StringSliceToIntSlice(input.SceneIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.FindStashBoxScenesByFingerprints(ctx, sceneIDs)
|
fps, err := r.getScenesFingerprints(ctx, sceneIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := client.FindScenesByFingerprints(ctx, fps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// match relationships - this mutates the existing scenes so we can
|
||||||
|
// just flatten the slice and pass it in
|
||||||
|
flat := sliceutil.Flatten(ret)
|
||||||
|
|
||||||
|
if err := r.matchScenesRelationships(ctx, flat, b.Endpoint); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("scraper_id or stash_box_index must be set")
|
return nil, errors.New("scraper_id or stash_box_index must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) getScenesFingerprints(ctx context.Context, ids []int) ([]models.Fingerprints, error) {
|
||||||
|
fingerprints := make([]models.Fingerprints, len(ids))
|
||||||
|
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
qb := r.repository.Scene
|
||||||
|
|
||||||
|
for i, sceneID := range ids {
|
||||||
|
scene, err := qb.Find(ctx, sceneID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if scene == nil {
|
||||||
|
return fmt.Errorf("scene with id %d not found", sceneID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scene.LoadFiles(ctx, qb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sceneFPs models.Fingerprints
|
||||||
|
|
||||||
|
for _, f := range scene.Files.List() {
|
||||||
|
sceneFPs = append(sceneFPs, f.Fingerprints...)
|
||||||
|
}
|
||||||
|
|
||||||
|
fingerprints[i] = sceneFPs
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fingerprints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchSceneRelationships accepts scraped scenes and attempts to match its relationships to existing stash models.
|
||||||
|
func (r *queryResolver) matchScenesRelationships(ctx context.Context, ss []*models.ScrapedScene, endpoint string) error {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
matcher := match.SceneRelationships{
|
||||||
|
PerformerFinder: r.repository.Performer,
|
||||||
|
TagFinder: r.repository.Tag,
|
||||||
|
StudioFinder: r.repository.Studio,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range ss {
|
||||||
|
if err := matcher.MatchRelationships(ctx, s, endpoint); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeSingleStudio(ctx context.Context, source scraper.Source, input ScrapeSingleStudioInput) ([]*models.ScrapedStudio, error) {
|
func (r *queryResolver) ScrapeSingleStudio(ctx context.Context, source scraper.Source, input ScrapeSingleStudioInput) ([]*models.ScrapedStudio, error) {
|
||||||
if source.StashBoxIndex != nil {
|
if source.StashBoxIndex != nil || source.StashBoxEndpoint != nil {
|
||||||
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
b, err := resolveStashBox(source.StashBoxIndex, source.StashBoxEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
var ret []*models.ScrapedStudio
|
var ret []*models.ScrapedStudio
|
||||||
out, err := client.FindStashBoxStudio(ctx, *input.Query)
|
out, err := client.FindStudio(ctx, *input.Query)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -309,13 +333,63 @@ func (r *queryResolver) ScrapeSingleStudio(ctx context.Context, source scraper.S
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ret) > 0 {
|
if len(ret) > 0 {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
for _, studio := range ret {
|
||||||
|
if err := match.ScrapedStudioHierarchy(ctx, r.repository.Studio, studio, b.Endpoint); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("stash_box_index must be set")
|
return nil, errors.New("stash_box_endpoint must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) ScrapeSingleTag(ctx context.Context, source scraper.Source, input ScrapeSingleTagInput) ([]*models.ScrapedTag, error) {
|
||||||
|
if source.StashBoxIndex != nil || source.StashBoxEndpoint != nil {
|
||||||
|
b, err := resolveStashBox(source.StashBoxIndex, source.StashBoxEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
|
var ret []*models.ScrapedTag
|
||||||
|
out, err := client.QueryTag(ctx, *input.Query)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if out != nil {
|
||||||
|
ret = append(ret, out...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ret) > 0 {
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
for _, tag := range ret {
|
||||||
|
if err := match.ScrapedTag(ctx, r.repository.Tag, tag, b.Endpoint); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("stash_box_endpoint must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source scraper.Source, input ScrapeSinglePerformerInput) ([]*models.ScrapedPerformer, error) {
|
func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source scraper.Source, input ScrapeSinglePerformerInput) ([]*models.ScrapedPerformer, error) {
|
||||||
|
|
@ -346,58 +420,96 @@ func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source scrape
|
||||||
default:
|
default:
|
||||||
return nil, ErrNotImplemented
|
return nil, ErrNotImplemented
|
||||||
}
|
}
|
||||||
// FIXME - we're relying on a deprecated field and not processing the endpoint input
|
case source.StashBoxIndex != nil || source.StashBoxEndpoint != nil:
|
||||||
case source.StashBoxIndex != nil:
|
b, err := resolveStashBox(source.StashBoxIndex, source.StashBoxEndpoint)
|
||||||
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []*stashbox.StashBoxPerformerQueryResult
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
|
var query string
|
||||||
switch {
|
switch {
|
||||||
case input.PerformerID != nil:
|
case input.PerformerID != nil:
|
||||||
res, err = client.FindStashBoxPerformersByNames(ctx, []string{*input.PerformerID})
|
names, err := r.findPerformerNames(ctx, []string{*input.PerformerID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query = names[0]
|
||||||
case input.Query != nil:
|
case input.Query != nil:
|
||||||
res, err = client.QueryStashBoxPerformer(ctx, *input.Query)
|
query = *input.Query
|
||||||
default:
|
default:
|
||||||
return nil, ErrNotImplemented
|
return nil, ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
ret, err = client.QueryPerformer(ctx, query)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res) > 0 {
|
|
||||||
ret = res[0].Results
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("scraper_id or stash_box_index must be set")
|
return nil, errors.New("scraper_id or stash_box_index must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
filterPerformerTags(ret)
|
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeMultiPerformers(ctx context.Context, source scraper.Source, input ScrapeMultiPerformersInput) ([][]*models.ScrapedPerformer, error) {
|
func (r *queryResolver) ScrapeMultiPerformers(ctx context.Context, source scraper.Source, input ScrapeMultiPerformersInput) ([][]*models.ScrapedPerformer, error) {
|
||||||
if source.ScraperID != nil {
|
if source.ScraperID != nil {
|
||||||
return nil, ErrNotImplemented
|
return nil, ErrNotImplemented
|
||||||
} else if source.StashBoxIndex != nil {
|
} else if source.StashBoxIndex != nil || source.StashBoxEndpoint != nil {
|
||||||
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
names, err := r.findPerformerNames(ctx, input.PerformerIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.FindStashBoxPerformersByPerformerNames(ctx, input.PerformerIds)
|
b, err := resolveStashBox(source.StashBoxIndex, source.StashBoxEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := r.newStashBoxClient(*b)
|
||||||
|
|
||||||
|
return client.QueryPerformers(ctx, names)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("scraper_id or stash_box_index must be set")
|
return nil, errors.New("scraper_id or stash_box_index must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source scraper.Source, input ScrapeSingleGalleryInput) ([]*scraper.ScrapedGallery, error) {
|
func (r *queryResolver) findPerformerNames(ctx context.Context, performerIDs []string) ([]string, error) {
|
||||||
var ret []*scraper.ScrapedGallery
|
ids, err := stringslice.StringSliceToIntSlice(performerIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if source.StashBoxIndex != nil {
|
names := make([]string, len(ids))
|
||||||
|
|
||||||
|
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
|
||||||
|
p, err := r.repository.Performer.FindMany(ctx, ids)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, pp := range p {
|
||||||
|
names[i] = pp.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source scraper.Source, input ScrapeSingleGalleryInput) ([]*models.ScrapedGallery, error) {
|
||||||
|
var ret []*models.ScrapedGallery
|
||||||
|
|
||||||
|
if source.StashBoxIndex != nil || source.StashBoxEndpoint != nil {
|
||||||
return nil, ErrNotSupported
|
return nil, ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -434,10 +546,46 @@ func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source scraper.
|
||||||
return nil, ErrNotImplemented
|
return nil, ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
filterGalleryTags(ret)
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) ScrapeSingleImage(ctx context.Context, source scraper.Source, input ScrapeSingleImageInput) ([]*models.ScrapedImage, error) {
|
||||||
|
if source.StashBoxIndex != nil {
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
if source.ScraperID == nil {
|
||||||
|
return nil, fmt.Errorf("%w: scraper_id must be set", ErrInput)
|
||||||
|
}
|
||||||
|
|
||||||
|
var c scraper.ScrapedContent
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case input.ImageID != nil:
|
||||||
|
imageID, err := strconv.Atoi(*input.ImageID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: image id is not an integer: '%s'", ErrInput, *input.ImageID)
|
||||||
|
}
|
||||||
|
c, err = r.scraperCache().ScrapeID(ctx, *source.ScraperID, imageID, scraper.ScrapeContentTypeImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return marshalScrapedImages([]scraper.ScrapedContent{c})
|
||||||
|
case input.ImageInput != nil:
|
||||||
|
c, err := r.scraperCache().ScrapeFragment(ctx, *source.ScraperID, scraper.Input{Image: input.ImageInput})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return marshalScrapedImages([]scraper.ScrapedContent{c})
|
||||||
|
default:
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeSingleMovie(ctx context.Context, source scraper.Source, input ScrapeSingleMovieInput) ([]*models.ScrapedMovie, error) {
|
func (r *queryResolver) ScrapeSingleMovie(ctx context.Context, source scraper.Source, input ScrapeSingleMovieInput) ([]*models.ScrapedMovie, error) {
|
||||||
return nil, ErrNotSupported
|
return nil, ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) ScrapeSingleGroup(ctx context.Context, source scraper.Source, input ScrapeSingleGroupInput) ([]*models.ScrapedGroup, error) {
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
|
||||||
159
internal/api/routes_gallery.go
Normal file
159
internal/api/routes_gallery.go
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
|
"github.com/stashapp/stash/internal/static"
|
||||||
|
"github.com/stashapp/stash/pkg/image"
|
||||||
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GalleryFinder interface {
|
||||||
|
models.GalleryGetter
|
||||||
|
FindByChecksum(ctx context.Context, checksum string) ([]*models.Gallery, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GalleryImageFinder interface {
|
||||||
|
FindByGalleryIDIndex(ctx context.Context, galleryID int, index uint) (*models.Image, error)
|
||||||
|
image.Queryer
|
||||||
|
image.CoverQueryer
|
||||||
|
}
|
||||||
|
|
||||||
|
type galleryRoutes struct {
|
||||||
|
routes
|
||||||
|
imageRoutes imageRoutes
|
||||||
|
galleryFinder GalleryFinder
|
||||||
|
imageFinder GalleryImageFinder
|
||||||
|
fileGetter models.FileGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs galleryRoutes) Routes() chi.Router {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
|
||||||
|
r.Route("/{galleryId}", func(r chi.Router) {
|
||||||
|
r.Use(rs.GalleryCtx)
|
||||||
|
|
||||||
|
r.Get("/cover", rs.Cover)
|
||||||
|
r.Get("/preview/{imageIndex}", rs.Preview)
|
||||||
|
})
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs galleryRoutes) Cover(w http.ResponseWriter, r *http.Request) {
|
||||||
|
g := r.Context().Value(galleryKey).(*models.Gallery)
|
||||||
|
|
||||||
|
var i *models.Image
|
||||||
|
_ = rs.withReadTxn(r, func(ctx context.Context) error {
|
||||||
|
// Find cover image first
|
||||||
|
i, _ = image.FindGalleryCover(ctx, rs.imageFinder, g.ID, config.GetInstance().GetGalleryCoverRegex())
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serveThumbnail needs files populated
|
||||||
|
if err := i.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
logger.Errorf("error loading primary file for image %d: %v", i.ID, err)
|
||||||
|
}
|
||||||
|
// set image to nil so that it doesn't try to use the primary file
|
||||||
|
i = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if i == nil {
|
||||||
|
// fallback to default image
|
||||||
|
image := static.ReadAll(static.DefaultGalleryImage)
|
||||||
|
utils.ServeImage(w, r, image)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.imageRoutes.serveThumbnail(w, r, i, &g.UpdatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs galleryRoutes) Preview(w http.ResponseWriter, r *http.Request) {
|
||||||
|
g := r.Context().Value(galleryKey).(*models.Gallery)
|
||||||
|
indexQueryParam := chi.URLParam(r, "imageIndex")
|
||||||
|
var i *models.Image
|
||||||
|
|
||||||
|
index, err := strconv.Atoi(indexQueryParam)
|
||||||
|
if err != nil || index < 0 {
|
||||||
|
http.Error(w, "bad index", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = rs.withReadTxn(r, func(ctx context.Context) error {
|
||||||
|
qb := rs.imageFinder
|
||||||
|
i, _ = qb.FindByGalleryIDIndex(ctx, g.ID, uint(index))
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// TODO - handle errors?
|
||||||
|
|
||||||
|
// serveThumbnail needs files populated
|
||||||
|
if err := i.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
logger.Errorf("error loading primary file for image %d: %v", i.ID, err)
|
||||||
|
}
|
||||||
|
// set image to nil so that it doesn't try to use the primary file
|
||||||
|
i = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if i == nil {
|
||||||
|
http.Error(w, http.StatusText(404), 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.imageRoutes.serveThumbnail(w, r, i, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs galleryRoutes) GalleryCtx(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
galleryIdentifierQueryParam := chi.URLParam(r, "galleryId")
|
||||||
|
galleryID, _ := strconv.Atoi(galleryIdentifierQueryParam)
|
||||||
|
|
||||||
|
var gallery *models.Gallery
|
||||||
|
_ = rs.withReadTxn(r, func(ctx context.Context) error {
|
||||||
|
qb := rs.galleryFinder
|
||||||
|
if galleryID == 0 {
|
||||||
|
galleries, _ := qb.FindByChecksum(ctx, galleryIdentifierQueryParam)
|
||||||
|
if len(galleries) > 0 {
|
||||||
|
gallery = galleries[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gallery, _ = qb.Find(ctx, galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gallery != nil {
|
||||||
|
if err := gallery.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
logger.Errorf("error loading primary file for gallery %d: %v", galleryID, err)
|
||||||
|
}
|
||||||
|
// set image to nil so that it doesn't try to use the primary file
|
||||||
|
gallery = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if gallery == nil {
|
||||||
|
http.Error(w, http.StatusText(404), 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), galleryKey, gallery)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue