Compare commits

..

421 Commits

Author SHA1 Message Date
WithoutPants
2ec595dedf Merge to master for 0.5 2021-02-22 18:04:33 +11:00
WithoutPants
f9b2a4be5e Update changelog 2021-02-22 16:00:53 +11:00
WithoutPants
ac117c2934 Fix performer input values being lost when failing to create (#1133)
* Fix lost performer input values
* Fix unsetting image studio
2021-02-19 20:09:59 +11:00
InfiniteTF
dd03c63da2 Add country synonyms for Iran and Moldova (#1121) 2021-02-19 14:27:21 +11:00
Tweeticoats
3f9b395b89 build arm and x86_64 docker containers (#1097) 2021-02-19 10:38:13 +11:00
InfiniteTF
deebeab9e8 Fix performer details gender (#1124) 2021-02-14 11:18:26 +01:00
InfiniteTF
647ae2d7f9 Fix cover image in scene edit tab (#1123) 2021-02-13 19:03:29 +01:00
InfiniteTF
758eccc659 Fix scene edit state resetting when scene is updated (#1112)
* Fix organized click resetting scene form state
* Fix state resetting for images/galleries
* Fix favoriting a performer resetting edit state
2021-02-11 14:33:16 +11:00
WithoutPants
bbc34bd1bf Exclude media in generated directory (#1118) 2021-02-11 11:06:04 +11:00
WithoutPants
7609969491 Add donate button to navbar (#1117) 2021-02-11 09:12:35 +11:00
InfiniteTF
8d8a8530e8 Migrate generated files when a scene is rescanned (#1106) 2021-02-10 10:50:34 +11:00
bnkai
bcbbd1474c fix check version (#1103) 2021-02-09 21:00:27 +11:00
bnkai
984a0c9247 Tweak scraper script error printing (#1107) 2021-02-09 19:07:53 +11:00
SpedNSFW
714ae541d4 fix json unmarshal error return (#1109) 2021-02-09 19:04:42 +11:00
JoeSmithStarkers
9da11603c2 Add selective cross compile (#921) 2021-02-04 09:41:43 +11:00
bnkai
918b739b6c Add checksums for releases (#1093) 2021-02-04 09:41:05 +11:00
InfiniteTF
25b600f768 Fix tagger performance issue and date parsing (#1096) 2021-02-04 08:22:52 +11:00
WithoutPants
0dd2e269ee Don't delete downloads directory at startup if generated not set (#1098)
* Don't empty directories if generated not set
* Rename downloads to download_stage
2021-02-02 20:32:37 +11:00
WithoutPants
6d48cd1c97 Minor UI tweaks (#1099)
* Show one decimal point for sizes GB and over
* Don't open 0.4 version section by default
2021-02-02 20:25:36 +11:00
WithoutPants
4e9ebe055b Fix scene filename parser Q filter value (#1100) 2021-02-02 20:25:08 +11:00
WithoutPants
e4d91a0226 String regex filter criteria and selective autotag (#1082)
* Add regex string filter criterion
* Use query interface for auto tagging
* Use Query interface for filename parser
* Remove query regex interfaces
* Add selective auto tag
* Use page size 0 as no limit
2021-02-02 07:57:56 +11:00
InfiniteTF
4fd022a93b Decouple galleries from scenes (#1057) 2021-02-02 07:56:54 +11:00
Belley
86bfb64a0d Fix freeones scraper (#1091) 2021-02-01 08:15:50 +11:00
InfiniteTF
6114caa938 Fix import file copying (#1085) 2021-02-01 08:15:10 +11:00
InfiniteTF
23d2668b38 Update javascript polyfills (#1083) 2021-01-30 09:16:28 +11:00
InfiniteTF
89fcd6d775 Make file upload limits configurable (#1079) 2021-01-29 20:27:02 +11:00
bnkai
df8675c2e7 Add Dry Run option to clean task (#1081) 2021-01-29 15:03:34 +11:00
InfiniteTF
203fc3e4b2 Force reload of custom css if it's updated (#1073) 2021-01-29 15:00:47 +11:00
InfiniteTF
0d7663c13d Fix performer scrape select, and studio/gallery select glitching (#1080) 2021-01-28 21:00:58 +01:00
InfiniteTF
d1274d559d Update javascript libraries (#1075) 2021-01-27 11:25:29 +11:00
bnkai
088f32a116 * fix database reset (#1076) 2021-01-26 10:37:42 +11:00
WithoutPants
3b41894dbd Add backup database functionality (#1069) 2021-01-21 22:02:09 +11:00
WithoutPants
093b997eb1 Login to docker before building image (#1067) 2021-01-19 11:11:10 +11:00
WithoutPants
1e04deb3d4 Data layer restructuring (#997)
* Move query builders to sqlite package
* Add transaction system
* Wrap model resolvers in transaction
* Add error return value for StringSliceToIntSlice
* Update/refactor mutation resolvers
* Convert query builders
* Remove unused join types
* Add stash id unit tests
* Use WAL journal mode
2021-01-18 12:23:20 +11:00
InfiniteTF
7bae990c67 Fix interface state storage (#1064) 2021-01-15 15:15:59 +11:00
bnkai
defb23aaa2 Fix vtt sprite generation ( issue #1033 ) (#1035) 2021-01-14 12:53:42 +11:00
InfiniteTF
aad4ddc46d Add batch delete for performers/tags/studios/movies (#1053)
* Add batch delete for performers/tags/studios/movies
* Fix ListFilter styling bug
2021-01-13 11:57:53 +11:00
InfiniteTF
8a3d940aa7 Prevent studio from being selected as its own parent (#1051) 2021-01-12 17:11:01 +11:00
InfiniteTF
3d83fa449d Fix z-index of performer scrape selector and correct no options message (#1050) 2021-01-12 15:05:01 +11:00
InfiniteTF
0a123548a0 Cache fixes (#1048)
* Bust cache for studio/tag image updates
* Reset cache when a scan/clean has been run
2021-01-12 15:03:50 +11:00
SpedNSFW
03a9d65cfe extend resolutions (#1036)
* extend resolutions
 - Simplifies logic
 - Adds more options including 540p, 1440p, and resolutions common to VR such as 1920p
 - Supports vertical/portrait videos and images
* implement new resolution filters
2021-01-07 16:10:59 +11:00
bnkai
1882b44951 Strip file extension from scene title when scanning (#1022) 2021-01-07 11:38:30 +11:00
dependabot[bot]
4fc0d47087 Bump axios from 0.21.0 to 0.21.1 in /ui/v2.5 (#1045)
Bumps [axios](https://github.com/axios/axios) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.0...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-06 13:18:44 +11:00
InfiniteTF
6b1d229a6d Equalise card styles for movies/galleries/images (#1015) 2021-01-05 13:12:16 +11:00
Jeremy Meyers
d84d48bc29 Update AutoTagging.md (#1037) 2021-01-04 09:02:10 +11:00
InfiniteTF
d50238cf41 Various bugfixes for scene tagger (#1014)
* Tagger fixes
* Validate stash-box endpoint pattern
2020-12-28 13:28:29 +11:00
InfiniteTF
5c10712aab Fix performer page lightbox (#1020) 2020-12-24 11:10:09 +01:00
InfiniteTF
232a69c518 Add gallery wall view, and new lightbox (#1008) 2020-12-24 11:17:15 +11:00
WithoutPants
c8bcaaf27d Fix scene file name parser update (#998)
* Fix conversion of input maps
* Only set changed scene values in parser update
2020-12-24 09:03:23 +11:00
InfiniteTF
e84c92355e Fix integer overflow for scene size on 32bit systems (#994)
* Fix integer overflow for scene size on 32bit systems
* Cast to double in sqlite to prevent potential overflow
* Add migration to reset scene sizes and scan logic to repopulate if empty
2020-12-22 10:29:53 +11:00
bnkai
e883e5fe27 Add Mouse Click support for the CDP scraper (#827) 2020-12-22 09:42:31 +11:00
InfiniteTF
dd2086a912 Fix galleryCreate mutation (#1004) 2020-12-20 12:35:41 +01:00
WithoutPants
aadbcaeec2 Organised flag (#988)
* Add organized boolean to scene model (#729)
* Add organized button to scene page
* Add flag to galleries and images
* Import/export changes
* Make organized flag not null
* Ignore organized scenes for autotag

Co-authored-by: com1234 <com1234@notarealemail.com>
2020-12-18 08:06:49 +11:00
InfiniteTF
99bd7bc750 Fix broken isMissing filters (#1000) 2020-12-17 20:27:44 +01:00
InfiniteTF
f264baa330 Add includes/excludes modifiers to path filter (#996) 2020-12-17 14:00:07 +11:00
WithoutPants
931d3a0974 Fix login redirect to remember current page (#989) 2020-12-13 14:00:01 +11:00
InfiniteTF
4a08bd351a Fix scene gallery selection (#990) 2020-12-11 11:15:32 +11:00
aGlkZGVu
fad64ba126 Implement user customizable menu items (#974) 2020-12-09 11:59:09 +11:00
bnkai
938559ca11 Fix studio logo getting nulled when editing a studio (#986) 2020-12-07 08:08:49 +11:00
WithoutPants
86747acc78 Use changesets correctly when updating objects (#976) 2020-12-04 12:42:56 +11:00
WithoutPants
6eea33aec9 Fix manual tables (#978) 2020-12-02 08:26:49 +11:00
SpedNSFW
c45780dc59 Add missing gallery components (#969)
* add gallery scrapers to settings page
* add galleries to performers page
* add galleries to studios page
2020-12-01 19:38:53 +11:00
bnkai
a96ab9ce6f Add support for setting cookies in the scraper (#934) 2020-12-01 16:34:09 +11:00
bnkai
aecbd236bc Tune image referrer path (#968) 2020-11-30 10:50:43 +11:00
InfiniteTF
a7d333786f Fix studio parent getting wiped in edit mode (#973) 2020-11-30 10:50:04 +11:00
InfiniteTF
a45c1111be Add TruncatedText component (#932) 2020-11-27 13:01:37 +11:00
WithoutPants
54c9f167ba Show studio as text in scene cards where studio image isn't set (#965) 2020-11-27 08:01:56 +11:00
WithoutPants
1d910419d1 Replace natural_sort with third party call (#964) 2020-11-27 08:01:36 +11:00
peolic
89277f1e25 Fix oversized movie images on scrape dialog (#961) 2020-11-26 10:33:41 +11:00
InfiniteTF
7d37e3e564 Add type policies for entity fetch queries (#958) 2020-11-26 09:50:36 +11:00
JoeSmithStarkers
df37ddcc2c Added natural sort for scene and image titles (#943)
* Added natural sort for scene and images
* Use natural sort for movie names

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-11-25 20:09:07 +11:00
InfiniteTF
794ea00d37 Upgrade create-react-app and assorted libraries (#914)
* Update create-react-app, react, typescript and eslint versions
* Various library updates
2020-11-25 13:20:48 +11:00
JoeSmithStarkers
e3eb550a7d Parallel scanning/generation, and combined scanning/preview/sprite (#820)
* Implement parallel scanning and generation, and combined scanning/preview/sprite generation.
* Added UI component for preview/sprite generation during scan, and configurable number of parallel tasks.
* Add v050 changelog entry
2020-11-25 12:45:10 +11:00
WithoutPants
502e9297ad Merge pull request #960 from stashapp/master
Merge master to develop for tag
2020-11-24 22:24:49 +11:00
WithoutPants
56b47ff9be Merge pull request #959 from stashapp/develop 2020-11-24 09:50:55 +11:00
JoeSmithStarkers
f0ec37c343 Added screenshots/previews to tagger list (#939)
* Added screenshots/previews to tagger list
* Move errors and stashids to subcontent container, and tweak layout
* Fix search-result margin
Co-authored-by: Infinite <infinitekittens@protonmail.com>
2020-11-24 08:02:31 +11:00
WithoutPants
93068e1ded Merge pull request #955 from stashapp/develop
0.4 release
2020-11-23 09:40:54 +11:00
WithoutPants
abf0281903 Merge branch 'master' into develop 2020-11-23 08:36:07 +11:00
WithoutPants
bbf7dbe98f Changelog for 0.4 release (#951)
* 0.4 changelog
* Updated galleries manual page
2020-11-23 08:19:54 +11:00
InfiniteTF
21806decca Expand tagger manual entry and add link to tagger interface (#947) 2020-11-20 12:07:36 +11:00
bnkai
e62e74bff4 Use symwalk for scrapers (#938) 2020-11-16 09:21:26 +11:00
WithoutPants
0a098b1d63 Selective scan (#940) 2020-11-16 09:20:04 +11:00
InfiniteTF
ba8b3b29a4 Update findGalleries to only fetch imageCount instead of all images (#941) 2020-11-15 14:40:47 +11:00
WithoutPants
c74f145224 Only set stash from env if not set (#937) 2020-11-12 10:05:09 +11:00
WithoutPants
beb84b8e94 Fix image memory issue (#935)
* Return slim images from mutations
* Fix potential memory leaks
2020-11-10 20:19:13 +11:00
peolic
ceb888958e Update scraping docs (#929)
* update editorconfig to ignore trailing whitespaces in markdown
* fix incorrect code example
* add missing genders
* add gallery to scraping docs
* reorder Scraping.md
2020-11-10 13:03:44 +11:00
InfiniteTF
6bb24d1744 Prevent movie link from being wiped when tagging scene (#933) 2020-11-09 12:29:13 +11:00
WithoutPants
8097cd39d2 Lightbox keybinds (#928)
* Add mousetrap-pause
* Disable keybinds while lightbox open
2020-11-08 11:22:38 +11:00
Belley
94392c7c4d Fixing image for Freeones Scrapers (#930) 2020-11-07 09:36:26 +11:00
bnkai
ccc2df7315 Abort PR upload if no server is available (#922)
* gracefully abort PR upload
* timeout if api doesn't respond in 15secs
2020-11-06 13:15:50 +11:00
InfiniteTF
8a04e5df62 Prevent studio parent id and scene rating from being unset when tagged (#927) 2020-11-06 13:14:17 +11:00
WithoutPants
5f482b7b8a Handle zip file modification (#877)
* Rescan zip if updating mod time
* Use inequality for mod time comparison
* Add sort by file_mod_time (fixes #469)
2020-11-05 10:26:51 +11:00
InfiniteTF
9ec762ae9a Fix outstanding tagger issues (#912)
* Fix potential image errors
* Fix issue preventing favoriting of tagged performers
* Add error handling in case of network issues
* Show individual search errors
* Unset scene results if query fails
* Don't abort scene submission if scene id isn't found
2020-11-05 08:28:58 +11:00
WithoutPants
66c7af62f6 Add gallery file info (#919) 2020-11-05 08:18:57 +11:00
bnkai
cc81d0b3ee Migrate PR build storage from transfer.sh to Gofile.io (#916) 2020-11-03 11:54:59 +11:00
WithoutPants
cbfd9e82b7 Fix image clean (#913)
* Use correct regex when cleaning images
* Clarify video exclusion pattern heading
2020-11-03 09:34:53 +11:00
WithoutPants
bae82513eb Add equals/not equals string criteria (#917)
* Fix encoding of string criteria
* Add equals and includes (and not) string criteria
2020-11-03 09:26:07 +11:00
WithoutPants
8e75a8fff5 Add selection and export for all list pages (#873)
* Include studios in movie export
* Generalise cards
* Add selection and export for movies
* Refactor gallery card
* Refactor export dialogs
* Add performer selection and export
* Add selection and export for studios
* Add selection and export of tags
* Include movie scenes and gallery images
2020-10-31 09:41:12 +11:00
WithoutPants
07212dbea9 Fix image thumbnail display (#907) 2020-10-30 09:52:53 +11:00
WithoutPants
90c5a9dd4a Add page sizes up to 1000 (#904) 2020-10-29 09:27:56 +11:00
WithoutPants
01227ceb85 Fix recursive loop (#905) 2020-10-27 12:53:34 +11:00
WithoutPants
c75b5c204d Don't set default studio image (#887) 2020-10-27 09:35:50 +11:00
SpedNSFW
cfbffb3b96 Add basic i18n implementation (#879)
To be used as a reference point for any future i18n additions for either new languages or more translatable content.
2020-10-27 09:35:25 +11:00
JoeSmithStarkers
47468fe122 Cache generated regex for each path (#891) 2020-10-26 15:57:58 +11:00
dullcibus
b3906f4b97 Add languages to code blocks for syntax highlight (#896)
Added languages to code blocks to get colored syntax highlighting for easier reading.
2020-10-26 15:12:42 +11:00
InfiniteTF
77da544e98 Wire up Tagger set tags checkbox (#889) 2020-10-24 19:19:45 +11:00
InfiniteTF
3346f8dcca Stash-box tagger integration (#454) 2020-10-24 14:31:39 +11:00
WithoutPants
70f73ecf4a Update freeones scraper (#881) 2020-10-24 13:12:21 +11:00
JoeSmithStarkers
2987b7f3d2 Video filters and transforms (#826) 2020-10-22 15:45:05 +11:00
JoeSmithStarkers
71c814c116 Added streaming quality options (#790) 2020-10-22 15:02:27 +11:00
InfiniteTF
a31c8ccd33 Fix new performer scraping (#880) 2020-10-22 12:30:22 +11:00
WithoutPants
109e55a25a Query url parameters (#878) 2020-10-22 11:56:04 +11:00
WithoutPants
228a5c5537 Use temp redirects for setup (#875) 2020-10-22 08:17:15 +11:00
com1234475
3832c8505a Make performer name mandatory in graphQL (#273) (#841) 2020-10-21 11:27:16 +11:00
SpedNSFW
147d0067f5 Add gallery scraping (#862) 2020-10-21 09:24:32 +11:00
WithoutPants
872bb70f6e Fix scan issue when encountering invalid symlinks (#871)
* Implement fixed symwalk algorithm
* Remove dependency
2020-10-20 17:00:23 +11:00
WithoutPants
8eda72ad89 Image improvements (#847)
* Fix image performer filtering
* Add performer images tab
* Add studio images tab
* Rename interface
* Add tag images tab
* Add path filtering for images
* Show image stats on stats page
* Fix incorrect scan counts after timeout
* Add gallery filters
* Relax scene gallery selector
2020-10-20 10:11:15 +11:00
InfiniteTF
80199f79f3 Persist gallery list state (#864) 2020-10-19 08:21:38 +11:00
InfiniteTF
cf003a55bf Fix loading issue in galleries and redirect on gallery creation (#857)
* Fix loading issue in galleries and redirect on gallery creation
* Add error messages when image/galleries aren't found
* Clean up gallery/image/performer/scene view states
* Simplify error messages
2020-10-17 09:57:02 +11:00
InfiniteTF
bbc6c43201 Add shim for String.prototype.replaceAll (#858) 2020-10-17 09:46:24 +11:00
InfiniteTF
d9270dd7c3 Treat empty string columns as missing (#852) 2020-10-16 11:14:48 +11:00
InfiniteTF
528b32d1b7 Sort images in galleries by path (#855) 2020-10-16 10:56:24 +11:00
InfiniteTF
73eb5c7a1f Add image-count sorting, and image deletion on gallery deletion (#853) 2020-10-16 10:35:50 +11:00
WithoutPants
d0f1ad3c24 Show static image if wall video missing (#849) 2020-10-15 10:53:00 +11:00
InfiniteTF
482f8cbd92 Fix clean and scan bugs (#846) 2020-10-14 10:51:36 +11:00
WithoutPants
12cba97192 Add scraper list page (#833) 2020-10-13 15:19:54 +11:00
WithoutPants
aca2c7c5f4 Images section (#813)
* Add new configuration options
* Refactor scan/clean
* Schema changes
* Add details to galleries
* Remove redundant code
* Refine thumbnail generation
* Gallery overhaul
* Don't allow modifying zip gallery images
* Show gallery card overlays
* Hide zoom slider when not in grid mode
2020-10-13 10:12:46 +11:00
InfiniteTF
df3252e24f Fix folder selector link color (#842) 2020-10-12 08:26:20 +11:00
WithoutPants
4f9af6ba27 Example python plugin (#825)
* Add example python plugin
* Fix log incorrectly detecting as progress level
2020-10-12 08:20:20 +11:00
WithoutPants
ade109d9e4 Path filter for scenes and galleries (#834) 2020-10-12 08:19:51 +11:00
WithoutPants
08276ac616 Add collapse button to scene page (#838) 2020-10-11 18:35:34 +11:00
WithoutPants
98dda782aa Prevent invalid date tag in video file from aborting scan (#836)
* Give more context when ffprobe fails
* Suppress JSONTime unmarshal error
* Tidy scan logging
2020-10-11 12:02:41 +11:00
InfiniteTF
ca14859339 Fix filter layout on devices (#840) 2020-10-11 12:02:01 +11:00
WithoutPants
d55177c170 Parent studio link in studio page (#835) 2020-10-10 10:29:31 +11:00
WithoutPants
c3a7d30a33 Convert scraped movie create data (#832) 2020-10-09 11:43:54 +11:00
bnkai
94dc74f4a8 Fix ffmpeg/ffprobe downloads (#824) 2020-10-03 17:28:02 +10:00
JoeSmithStarkers
30e88b96ee Added ffmpeg/ffprobe permission correction code for linux/osx (#814) 2020-10-03 16:59:23 +10:00
WithoutPants
8866670e53 Add partial import functionality (#812) 2020-09-20 18:36:02 +10:00
WithoutPants
7a45943e8e Stash box client interface (#751)
* Add gql client generation files
* Update dependencies
* Add stash-box client generation to the makefile
* Move scraped scene object matchers to models
* Add stash-box to scrape with dropdown
* Add scrape scene from fingerprint in UI
2020-09-17 19:57:18 +10:00
WithoutPants
b0b5621337 Add 0.4.0 changelog, convert to md (#803)
* Turn page into shared component
* Add v0.4.0 changelog markdown page
* Rename version files
* Markdown conversion
2020-09-16 09:54:24 +10:00
WithoutPants
03d4826c85 Selective export (#770) 2020-09-15 17:28:53 +10:00
InfiniteTF
03f5e1a442 Config for stash-box instances (#748) 2020-09-14 17:13:35 +10:00
bnkai
b527a8d137 Update makefile for static build (#808) 2020-09-14 11:12:36 +10:00
caustico
5df1e0025f Add filter on Movie section "Is Missing is scenes" (#800) 2020-09-14 10:35:54 +10:00
caustico
933d6d0bd5 Add gallery icon in "scene card" and "scene list table" (#799) 2020-09-12 19:11:43 +10:00
caustico
981b2622a5 Add filter country when click the performer flag (#795) 2020-09-12 17:55:02 +10:00
InfiniteTF
b541322d0a Hotfix for theme preview bug (#804) 2020-09-12 17:53:37 +10:00
InfiniteTF
7f907fdf41 Fix date timezone bug (#807) 2020-09-12 17:52:48 +10:00
InfiniteTF
147f50de6b Update gallery view layout and switch libraries (#793)
* Update gallery view layout and switch libraries
* Tweak gallery grid layout
2020-09-11 18:18:31 +10:00
InfiniteTF
5cd7dcaeb2 Library updates (#792)
* Upgrade Typescript to 4.0
* Update i18n-iso-countries to 6.0
* Update react-intl to 5.8.0
* Update jimp to 0.16.1
* Update apollo and graphql libraries
* Update various libraries and fix linting/type errors
* Refactor cache invalidation
* Codegen refetch queries
2020-09-11 13:01:00 +10:00
InfiniteTF
5ba11e0e93 Persist studio/gallery/marker list filters (#802) 2020-09-11 11:08:59 +10:00
InfiniteTF
9095ba21dc Scene card preview refactor (#787)
* Refactor scene card preview
* Add delay to video preview
2020-09-11 10:52:36 +10:00
WithoutPants
629126df98 Update to golang 1.13 (#754)
* Update to use golang 1.13
* Update node and ubuntu versions
* Update compiler version
2020-09-11 10:43:41 +10:00
WithoutPants
19d1d30946 Change cross compiler to fixed version (#794)
* Change cross compiler to fixed version
* Update compiler version in travis
2020-09-10 16:22:20 +10:00
InfiniteTF
19dfa571da Fix erroneous pending thumbnail on wall items (#784) 2020-09-09 08:37:19 +10:00
WithoutPants
a26151c9a3 Merge master back to develop for tagging 2020-09-03 10:07:15 +10:00
WithoutPants
fb5867ba59 Fix travis script error 2020-09-02 13:23:17 +10:00
WithoutPants
2638a9d5e1 Merge pull request #782 from stashapp/develop
Version 0.3 release
2020-09-02 12:21:05 +10:00
InfiniteTF
5d9cc09fca Allow updating tag name capitalization (#781) 2020-09-02 10:30:37 +10:00
WithoutPants
16ea6abf91 Fix age filtering regression (#778)
* Show filter control in loading/error
* Add performer age unit tests
* Fix addWhere regression
2020-08-31 18:17:17 +10:00
bnkai
b437425a41 Add cbz to supported extensions as gallery (#774) 2020-08-31 14:21:49 +10:00
WithoutPants
c1d22f60a9 Add skip cleanup to docker releases 2020-08-31 13:35:05 +10:00
WithoutPants
1258a5e3f4 Remove echo statements 2020-08-31 13:13:10 +10:00
WithoutPants
0345bc9ef7 Make travis build and push docker images (#772) 2020-08-31 12:04:05 +10:00
InfiniteTF
cb753f28f6 Fix scene markers (#769) 2020-08-30 14:51:40 +10:00
WithoutPants
ae24fc2d3c Fix prod dockerfile to not pick up arm builds 2020-08-30 14:50:36 +10:00
WithoutPants
ba442e8d90 Fix develop dockerfile to not pick up arm binaries 2020-08-30 14:42:46 +10:00
InfiniteTF
fe6afca3c2 Fix scene-card jumping (#764) 2020-08-28 16:37:12 +10:00
InfiniteTF
fef16d7e09 Fix various console errors and graphql loading issues (#760)
* Refactor listhook to resolve loading issues
* Fix graphql loading race conditions
* Various console spam
* Fix scene card overlay hierarchy
* Fix modal and manual borders
2020-08-28 16:33:19 +10:00
WithoutPants
9a84726128 Fix xpath comment element parsing (#759) 2020-08-23 17:39:15 +10:00
peolic
165a0d4398 MovieScrapeDialog tweaks (#753)
* MovieScrapeDialog: use TextArea for Synopsis
* MovieScrapeDialog: add Date placeholder
2020-08-23 13:14:16 +10:00
InfiniteTF
ffa1fbda7f Fix infinite load issue (#756)
* Remove subcomponents to resolve infinite load issue
2020-08-23 12:54:52 +10:00
WithoutPants
1fd3fcc6a8 Show and allow creation of unknown performers/tags/studios/movies from scraper dialog (#741)
* Fix toast container z-index
* Make scrape operations network only
* Add CollapseButton component
2020-08-22 18:12:39 +10:00
WithoutPants
2cdec6bde1 Update changelog for #745 2020-08-21 17:58:20 +10:00
JoeSmithStarkers
85aa1d8790 Replace os.Rename with util.SafeMove to allow cross device moving to not fail. (#745)
Fixed annoyingly noisy transcoding progress log messages.
Fixed minor minor issue with directories than are named "blah.mp4" being detected as files to be scanned.
2020-08-21 17:57:07 +10:00
WithoutPants
6a3588e4e0 Fix compiler version in cross-compile.sh 2020-08-20 11:47:27 +10:00
WithoutPants
f7a45f9d10 Cross compile for armv7/armv8, raise raspi variant to armv6 (only) (#737)
* Add arm cross compile changes from #602
* Bump pi arm version
* Prevent caching of yarn key
* Add dockerignore for build dockerfile
2020-08-20 09:27:54 +10:00
InfiniteTF
ecf745162f Upgrade doublestar to v2.0.1 (#742) 2020-08-17 12:06:40 +10:00
JoeSmithStarkers
ecc42e4e24 Preview generation fallback (#725)
* Added preview generation fallback feature.
When a preview generation fails (often for wmv/avi files), the new code tries with less stricted (no xerror) and more time consuming options (slow+fast seek).
Fix a minor issue when stash downloads ffmpeg/ffprobe, but doesn't re-detect their paths.
2020-08-17 09:21:58 +10:00
WithoutPants
44c32a91d3 UI improvements (#726)
* Use rating stars in movie
* Make multiset button more obvious
* Hide select all/none buttons where not selectable
* Make add the default multi-set mode
2020-08-14 09:41:01 +10:00
WithoutPants
a39666467e Fix import performers/movies/studios with no image (#732) 2020-08-14 09:40:25 +10:00
woodgen
e3ea3ea85e scraper/mapped: Add feetToCm post process. (#711)
This patch adds a feetToCm post process that converts imperial feet and
inches to centimeters.
2020-08-12 11:17:43 +10:00
WithoutPants
551c13bbc8 Update changelog 2020-08-12 09:21:39 +10:00
WithoutPants
e16118f551 Clear image (#722)
* Allow clearing of tag images
* Allow clearing of studio images
* Allow clearing of performer images
* Allow clearing of movie images
* Add filtering for missing images
2020-08-12 09:19:27 +10:00
WithoutPants
c0afd31b1c Make scene movie number freely editable (#721) 2020-08-12 08:45:48 +10:00
peolic
39bc8b7351 update scraping docs (#720) 2020-08-11 08:44:57 +10:00
woodgen
4045ddf3e9 Implement scraping movies by URL (#709)
* api/urlbuilders/movie: Auto format.

* graphql+pkg+ui: Implement scraping movies by URL.

This patch implements the missing required boilerplate for scraping
movies by URL, using performers and scenes as a reference.

Although this patch contains a big chunck of ground work for enabling
scraping movies by fragment, the feature would require additional
changes to be completely implemented and was not tested.

* graphql+pkg+ui: Scrape movie studio.

Extends and corrects the movie model for the ability to store and
dereference studio IDs with received studio string from the scraper.
This was done with Scenes as a reference. For simplicity the duplication
of having `ScrapedMovieStudio` and `ScrapedSceneStudio` was kept, which
should probably be refactored to be the same type in the model in the
future.

* ui/movies: Add movie scrape dialog.

Adds possibility to update existing movie entries with the URL scraper.

For this the MovieScrapeDialog.tsx was implemented with Performers and
Scenes as a reference. In addition DurationUtils needs to be called one
time for converting seconds from the model to the string that is
displayed in the component. This seemed the least intrusive to me as it
kept a ScrapeResult<string> type compatible with ScrapedInputGroupRow.
2020-08-10 15:34:15 +10:00
WithoutPants
7158e83b75 Add JSON scrape support (#717)
* Add support for scene fragment scrape in xpath
2020-08-10 14:21:50 +10:00
WithoutPants
470a2b5833 Fix sprite vtt panic (#718) 2020-08-10 09:20:04 +10:00
WithoutPants
711fadcffd Don't play after failover (#716) 2020-08-10 09:19:37 +10:00
WithoutPants
634b892df1 Fix typo 2020-08-09 13:48:47 +10:00
WithoutPants
0ffefa6e16 Add plugin tasks (#651) 2020-08-08 12:05:35 +10:00
WithoutPants
0874852fa8 Improve oshash collision detection and logging (#713)
* Log colliding file when setting hash
* Check for existing using both hashes
2020-08-08 11:22:25 +10:00
WithoutPants
5992ff8706 Add oshash support (#667) 2020-08-06 11:21:14 +10:00
friendlycrab
f59ad0ca2b Allow adding performer & studio from scenes page (#663)
* Allow adding performer & studio from scenes page

Adds "create" options for performer and studio select in SceneEditPanel.
Adds new FilterSelectComponent to reduce duplicate logic in selects.

Make invalidateQueries case insensitive so we can pass upper-case query
names that also work with refetchQueries
2020-08-05 15:38:11 +10:00
WithoutPants
f5c3cafa63 Fix markers not autoplaying (#698)
* Fix marker seeking
* Autostart when loading from marker
2020-08-05 09:48:23 +10:00
WithoutPants
b166abfa7b Fix scraping error (#704) 2020-08-04 20:43:56 +10:00
bnkai
4373f9bf01 Add cdp support for xpath scrapers (#625)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-08-04 10:42:40 +10:00
Nic Patterson
1b4a9eed36 add non-binary option for gender (#695) 2020-08-01 16:26:00 +10:00
WithoutPants
b465c36fc7 Fix tag delete (#690)
* Fix tag list delete button not working
* Fix tags not refreshed when created/deleted
2020-07-29 11:51:20 +10:00
WithoutPants
278be33618 Fix marker seeking (#689) 2020-07-29 11:50:58 +10:00
WithoutPants
c327a98ecb Fix missing video player poster (#686) 2020-07-24 10:45:48 +10:00
InfiniteTF
116f61b6bf Fix potential sprite generation segfault (#685) 2020-07-24 09:20:34 +10:00
WithoutPants
a2341f0819 Allow customisation of preview generation (#673)
* Add generate-specific options
* Include no-cache in preview response
2020-07-23 12:51:35 +10:00
WithoutPants
37be146a9d Transcode stream refactor (#609)
* Remove forceMkv and forceHEVC
* Add HLS support and refactor
* Add new streaming endpoints
2020-07-23 11:56:08 +10:00
InfiniteTF
274d84ce93 Fix performer card name truncation (#682) 2020-07-22 11:21:14 +10:00
WithoutPants
2b9215702e Refactor xpath scraper code. Add fixed and map (#616)
* Refactor xpath scraper code
* Make post-process a list
* Add map post-process action
* Add fixed xpath values
* Refactor scrapers into cache
* Refactor into mapped config
* Trim test html
2020-07-21 14:06:25 +10:00
InfiniteTF
f4ae9b09a6 Fix FormattedDate timezone bug (#681) 2020-07-21 13:04:56 +10:00
WithoutPants
c1be600b01 Don't show dialog when setting front movie image (#678) 2020-07-21 08:30:26 +10:00
WithoutPants
c104c6d075 Generate content for specific scenes (#672)
* Add UI dialog for scene(s)
* Move preview preset to config
2020-07-19 11:59:18 +10:00
WithoutPants
8e4945325d Fix badly sized performer container (#670) 2020-07-19 11:10:52 +10:00
WithoutPants
8076405965 Support random performer sort (#666) 2020-07-14 08:51:39 +10:00
bnkai
ec2bcc7a74 Scan for files with ALLCAPS extensions (#650) 2020-07-11 17:22:36 +10:00
bnkai
56210cf456 Use referer on xpath getImage, apply printHTML to subscraper also (#661) 2020-07-10 08:42:06 +10:00
WithoutPants
e9141b5dfc Fix tag query performance problems (#657)
* Fix sql tracing
* Disable query by marker count
* Disable unit test
2020-07-09 08:42:07 +10:00
WithoutPants
f5784246ce Add website link to readme 2020-07-08 13:05:55 +10:00
WithoutPants
244ae54f3f Add grid view, image to tag (#641)
* Add grid view for tags
* Add tag page
* Import/export tags
* Add tag name uniqueness checks
* Fix styling on missing marker previews
* Add trace loglevel
* Add SQL trace
* Add filter options for tags
* Add tag sort by options
* Add tag page keyboard shortcuts
2020-07-07 10:35:43 +10:00
WithoutPants
54430dbc11 Fix travis not validating UI (#653)
* Fix travis not validating UI
* Fix lint errors
2020-07-06 08:58:35 +10:00
WithoutPants
f1c544affb Scrape dialog (#644)
* Fix performer page button spacing
* Improve scene URL scrape button styling
2020-07-02 10:10:29 +10:00
WithoutPants
bfeb7d1824 Add keyboard shortcuts (#637)
* Add documentation
* Fix manual styling
* Add dialog for setting Movie images
* Mention manual in README
2020-07-02 08:45:14 +10:00
WithoutPants
3157d748bc Scene selection improvements (#642)
* Select subsequent scenes on card click
2020-07-01 11:50:43 +10:00
WithoutPants
d516947af2 Strip debug symbols from release builds (#636)
* Add release target that strips debug symbols
* Make cross compile use make
2020-06-29 14:44:21 +10:00
WithoutPants
56b9c081ec [RFC] Fix query toolbar, performer/studio/movie page styling improvements (#632)
* Fix query toolbar
* Fix performer page styling
* Improve studio page styling
* Improve movie page styling
2020-06-26 07:49:00 +10:00
WithoutPants
883a0e785a Fix UI not showing version (#631) 2020-06-24 15:46:37 +10:00
WithoutPants
455e16ece9 Support deleting multiple scenes (#630)
* Improve layout and add buttons
* Move functionality into ListFilter
* Make modal style dark
* Convert scene options into edit scenes dialog
* Add delete scenes dialog
* Clear selected ids on delete
* Refetch after update/delete
* Use DeleteScenesDialog in Scene page
* Show scene check boxes in small screens
* Change default multi-set mode to set
2020-06-23 10:40:11 +10:00
WithoutPants
83f8bc0832 In-app help manual (#628) 2020-06-23 10:15:02 +10:00
WithoutPants
534d47500b Integrate build version in UI (#629) 2020-06-23 09:51:57 +10:00
WithoutPants
aa57b75243 Add changelog entry for #618 2020-06-23 09:21:18 +10:00
WithoutPants
7a74658a73 Move image blobs into separate tables (#618)
* Scene cover fallback to database
* Fix panic if studio not found
* Fix movie studio not being imported/exported
2020-06-23 09:19:19 +10:00
bnkai
f8048dc27c Increase xpath redirects, use cookies (#624) 2020-06-22 12:18:02 +10:00
WithoutPants
77a5b1d814 Add custom served folders (#620) 2020-06-21 22:25:13 +10:00
WithoutPants
d3ababf0a1 Gallery list improvement (#622)
* Add grid view to galleries
* Show scene in gallery card
* Add is missing scene gallery filter
* Don't store galleries with no images
2020-06-21 21:43:57 +10:00
WithoutPants
e50f1d01be Fix scene delete (#621) 2020-06-19 23:09:47 +10:00
WithoutPants
f60ce01fec Render pagination at top as well (#614) 2020-06-18 11:10:00 +10:00
bnkai
9d0522f62d Add "split" xpath in post-processing , newlines in replace support (#579) 2020-06-18 10:47:10 +10:00
WithoutPants
3fbb4cdc32 [RFC] Revamp scene page (#562)
* Don't show scrubber on small height device
* Move operations into ellipsis menu
* Hide scrubber in mobile devices
* Add delete scene to operations drop down
* Remove redundant panels
* Fix video height on smaller devices
* Adjust player aspect ratio for portrait videos
2020-06-18 10:26:05 +10:00
WithoutPants
1ca5f357e9 Remove v2 UI (#613) 2020-06-17 11:02:47 +10:00
bnkai
a7ac02fb50 freeones fixes (#615) 2020-06-17 11:02:06 +10:00
bnkai
f40e234748 Apply xpath parseDate after subScraper (#606) 2020-06-15 21:38:59 +10:00
WithoutPants
96e6e16507 Parent studios (#595)
* Refactor getMultiCriterionClause
Co-authored-by: Anon247 <61889302+Anon247@users.noreply.github.com>
2020-06-15 21:34:39 +10:00
WithoutPants
a77fea5724 Rating stars (#567)
* Add ratings stars control. Add to scene details
* Replace rating with stars on edit panel
* Add changelog entry
2020-06-12 09:11:39 +10:00
WithoutPants
d8ce137086 Reload scrapers button (#592)
* Add reload scraper option to performer details
* Add scraper reload to scene edit page
* Show scene scraper menu when no queryable scrapers
* Add 0.3 changelog
2020-06-10 13:43:17 +10:00
WithoutPants
32011aa86f Merge master to develop for 0.2.1 2020-06-10 11:51:31 +10:00
WithoutPants
9f335b8ea2 Add 0.2.1 changelog (#607) 2020-06-10 10:53:49 +10:00
WithoutPants
d168df1ad6 Fix max loop duration not working (#604) 2020-06-10 10:15:02 +10:00
peolic
ef8cba804f Fix sanitizeURL (#599) 2020-06-10 10:13:57 +10:00
WithoutPants
bd5ee9ba6b Merge pull request #597 from stashapp/develop
Merge 0.2 to master
2020-06-06 14:59:07 +10:00
WithoutPants
caef648659 Update changelog for 0.2 release (#582) 2020-06-06 14:38:19 +10:00
bnkai
f255104951 Upgrade bmatcuk/doublestar to 1.3.1 (#586) 2020-06-03 11:46:12 +10:00
InfiniteTF
dcf7233e38 Add polyfill for number unit formatting (#589) 2020-06-02 10:19:27 +10:00
bnkai
b89956de25 freeones scraper fixes/tweaking (#584) 2020-06-02 09:45:37 +10:00
InfiniteTF
d1e6858c11 Remove German language option (#590) 2020-06-02 09:37:13 +10:00
bnkai
dc5efb9e31 Unit testing for performers,studios,movies FindByName/s (#581) 2020-05-27 10:48:45 +10:00
WithoutPants
1110e9c311 Fix date rendering for missing date values (#580)
* Fix date rendering when missing date value
* Move scene studio overlay within scene card
2020-05-27 09:34:46 +10:00
InfiniteTF
4ec6d62e01 Selectable wall preview type (#510)
* Add optional image preview generation
* Add setting for video preview encoding preset
2020-05-27 09:33:49 +10:00
InfiniteTF
197918d13c Localize dates and numbers (#574) 2020-05-25 15:49:13 +10:00
bnkai
ccd75731b7 Change scrape matching (studio, movies, tag, performers) to case insensitive (#556)
* Change scrape matching (studio, movies, tag, performers) to case insensitive
* * fix collate order
* * make filename parser findbyname calls case insensitive
* * add unit testing for Tags GetFindbyName/s
2020-05-24 16:19:22 +10:00
InfiniteTF
32fce9ac6f Speed up tag count queries (#570)
* Speed up tag count queries
* Add test for marker CountByTagID

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-05-24 16:18:02 +10:00
WithoutPants
95a6d3ea2f Fix sceneTags filter selection (#564) 2020-05-20 22:47:16 +10:00
WithoutPants
1a31ca3e33 Set performers/tags/studios in parser (#563) 2020-05-20 22:47:01 +10:00
InfiniteTF
5c68c70216 Encode pasted images to jpegs (#560) 2020-05-20 22:46:38 +10:00
WithoutPants
ec420df871 Add debug logging for xpath scraping (#555)
* Add debug logging for xpath scraping
* Add logging for processing scene members
2020-05-20 22:46:00 +10:00
WithoutPants
ad374dc7f5 Include stash version in bug report template 2020-05-20 14:41:02 +10:00
WithoutPants
05488d59c3 Find scrapers in subdirectories (#554) 2020-05-19 08:44:33 +10:00
InfiniteTF
5450fe8e9a Changelog (#531) 2020-05-19 08:37:56 +10:00
bnkai
0fc57ce1e0 Fix xpath comments text (#550) 2020-05-18 12:26:20 +10:00
WithoutPants
46746e6848 Fix redirect loops in login, migrate and setup pages (#549)
* Fix redirect loop when setup and migrate required
* Fix redirect loop between setup and login
2020-05-18 08:55:01 +10:00
WithoutPants
215c4e3bde Change builtin freeones scraper to community yml (#542) 2020-05-15 20:10:20 +10:00
WithoutPants
50530ae85d Fix setting release notes for development build 2020-05-15 16:52:03 +10:00
WithoutPants
7fd9428cbb Fix stash directory with spaces (#541) 2020-05-15 09:12:17 +10:00
WithoutPants
93d6cc43c5 Add details to is missing filter (#533) 2020-05-15 09:11:28 +10:00
bnkai
d74a303c00 Fix 0 undefined library size on empty db (#535) 2020-05-14 10:44:13 +10:00
WithoutPants
fde02425ab Make auto tagger matching more flexible (#534) 2020-05-13 17:32:39 +10:00
WithoutPants
41c6d9e681 Revert preview generation change 2020-05-13 17:23:23 +10:00
bnkai
4829b4b214 Fix edge case preview generation, tweak preview generation preset (#528) 2020-05-11 17:24:52 +10:00
bnkai
bd45daacf3 Add a cache for gallery thumbnails (#496) 2020-05-11 17:20:08 +10:00
InfiniteTF
8ba76783b0 Query optimizations (#478)
* Remove slow and largely pointless groupbys
* Change scene.query to use querybuilder
2020-05-11 15:19:11 +10:00
InfiniteTF
328db57d6c Overhaul look and feel of folder select (#527) 2020-05-09 13:08:00 +10:00
InfiniteTF
e9c68897d7 Add is-missing tags filter (#526) 2020-05-09 12:21:08 +10:00
InfiniteTF
0d9dcdd1e9 Add title to flags, and alias for Slovakia (#519) 2020-05-09 10:38:11 +10:00
InfiniteTF
df2d2c2d09 Upgrade javascript libraries (#516)
* Bump react-bootstrap
* Bump library versions and clean up hooks
* Bump intl libraries
* Fix image pasting
2020-05-08 12:06:07 +10:00
WithoutPants
99f88b8d73 Querybuilder integration tests (#513)
* Vet fixes
* Change low resolution to < 480
2020-05-04 17:02:49 +10:00
bnkai
0b50e83dbf freeones scraper tweaks (#509) 2020-05-04 14:11:49 +10:00
InfiniteTF
a4edd21072 Add flags for performer countries (#508) 2020-05-03 18:15:24 +10:00
InfiniteTF
2c85aeedf4 Set movies studioId on create mutation (#511) 2020-05-03 08:54:02 +10:00
bnkai
0e01177440 Merge pull request #515 from stashapp/revert-514-travis_body
Revert "travis fix"
2020-05-03 00:23:04 +03:00
bnkai
db716d2e17 Revert "travis fix" 2020-05-03 00:22:11 +03:00
bnkai
5370870598 Merge pull request #514 from bnkai/travis_body
Travis release date fix
2020-05-03 00:03:00 +03:00
bnkai
461127c8c4 travis fix 2020-05-02 23:28:35 +03:00
WithoutPants
2166caf322 Update xpath dependency (#507) 2020-04-30 08:32:33 +10:00
WithoutPants
3d22d5a742 Refactor build (#493)
* Add lint/format checks to build
* Make travis get full repo to get tags
* Run packr2 once in cross-compile
* Fix quotes in package.json
* Fix linting issues
* Formatting
* Fix vet issue
* Fix go lint issues
* Show start of each platform compilation
* Add validate target
* Set gitattributes for go fmt and mod vendor
* Fix tag name
* Add fmt-ui target
2020-04-29 12:13:08 +10:00
InfiniteTF
a0306bfbd2 Remove hotkeys and fix tag selection (#505)
* Remove broken scene player hotkeys
* Disable closing tag select menu after a select
2020-04-29 09:55:34 +10:00
bnkai
52a1059380 Make image extension check in zip files case insensitive (#501) 2020-04-29 09:24:01 +10:00
FleetingOrchard
1513617f95 Add index/total count to end of pagination buttons (#490) 2020-04-29 08:28:57 +10:00
bnkai
f933a28e5a Fix yarn extract error due to "library size" (#494) 2020-04-26 16:49:55 +10:00
WithoutPants
309f7047b0 Add parser support for 3-letter month (#500) 2020-04-26 16:29:53 +10:00
bnkai
5e84c0922d Add random male performer image (#491)
* Add random male performer image during performer creation if gender is male
2020-04-25 09:54:42 +10:00
WithoutPants
5923917e6c Clean missing galleries (#489)
* Clean missing galleries
* Refactor matchFile
2020-04-25 09:32:55 +10:00
FleetingOrchard
8a4d853a5d Add "reshuffle button" when sortby is random (#497) 2020-04-24 14:30:41 +10:00
bnkai
29336d1ee0 Move image with cover.jpg in name to first place in Galleries (#477) 2020-04-24 12:53:18 +10:00
bnkai
9b1518beae Export performance optimization (#475)
* recreate metadata path if needed, before exporting data
2020-04-24 12:52:21 +10:00
WithoutPants
ba09bfa64a Include scene o-counter in import/export (#488)
* Include o-counter in import/export
* Fix scene card o-counter display
2020-04-23 09:14:58 +10:00
WithoutPants
eee7adfb85 Add Studio to movie and fix movie schema (#458)
* Add movie migration
* Update server and UI code for type changes
* Add studio to movies
* Movie blobs to end
* Document movie duration
* Add filtering on movie studio
2020-04-22 11:22:14 +10:00
InfiniteTF
f21e04dcbc Fix navbar height (#481) 2020-04-21 09:30:15 +10:00
WithoutPants
7e747fd8a9 Restore movie/studio data on edit cancel (#476) 2020-04-20 12:46:55 +10:00
WithoutPants
359e80f364 Don't error in IsStreamable resolver (#482) 2020-04-20 12:42:56 +10:00
InfiniteTF
2a3c9742cc Add slim endpoints for entities to speed up filters (#460)
* Move performers image column to end of table
* Remove redundant index
2020-04-19 12:03:51 +10:00
f1delio
7ef0000744 Update README.md (#474)
Added Customization section with links toward Custom CSS snippets, CSS Tweaks, and Plex Theme.
2020-04-18 09:47:40 +10:00
bnkai
e2f66a3492 Fix editing movie name (#470) 2020-04-15 08:36:16 +10:00
InfiniteTF
7cd682e59c Fix log population (#459) 2020-04-13 11:49:23 +10:00
InfiniteTF
a5a264dcc2 Fix tag creation (#461) 2020-04-13 11:45:45 +10:00
bnkai
424e62a226 Fix studio edit name (#465) 2020-04-13 11:42:02 +10:00
WithoutPants
849a5261a3 Include gender in performer scraper results (#448)
* Fix scraper gender
* Set scraped gender in the UI
* Match gender on enum or case insensitive string
2020-04-11 13:26:53 +10:00
WithoutPants
6764c1f545 Performer and Movie UI fixes and improvements (#447)
* Improve gender display
* Sanitise performer URLs
* Refactor editable text into separate module
* Make movie duration DurationInput
* Fix clearing sometimes not firing onChange
* Set movie duration as string
* Fix TextUtil.fileSize
* Improve scene URL
2020-04-11 13:23:31 +10:00
WithoutPants
aef31c8b50 Don't redirect login to migrate page (#453) 2020-04-10 08:41:09 +10:00
WithoutPants
2ab45848a5 Prefer modified performer image over scraped one (#449) 2020-04-10 08:40:45 +10:00
WithoutPants
91d842ed47 Delete marker preview on marker change or delete (#446) 2020-04-10 08:39:41 +10:00
bnkai
d5617307f1 Add detection of container/video_codec/audio_codec compatibility for live file streaming or transcoding (#384)
* add forceMKV, forceHEVC config options
* drop audio stream instead of trying to transcode for ffmpeg unsupported/unknown audio codecs
2020-04-10 08:38:34 +10:00
Anon247
dc37a3045b Added various missing filters to performer page (#438) 2020-04-08 14:21:05 +10:00
WithoutPants
15e7756d33 Replace basic auth with cookie authentication (#440)
* Add logout functionality and button
* Make session age configurable
2020-04-08 12:51:12 +10:00
WithoutPants
b3e8d1e8dd Add scene rating to scene filename parser (#432)
* Fix scene parser display issues in 2.5
* Dropdown menu presentation improvements
* Fix refresh on parser apply
* Ignore line endings on scss files
2020-04-05 07:59:57 +10:00
bnkai
18dc5e85fa Don't run travis for the latest_develop tag (#439) 2020-04-04 09:27:49 +11:00
bnkai
8a6fff61ae Enable sorting for galleries (#437) 2020-04-03 14:40:37 +11:00
bnkai
e58c311ddd Add library size to main stats page (#427) 2020-04-03 13:44:17 +11:00
InfiniteTF
66cb7f4928 Update prettier to v2.0.1 and enable for SCSS (#420) 2020-04-03 10:29:33 +11:00
Anon247
ad7bfb8dbf Fix to filter on movies from performer filter to movie filter (#435) 2020-04-03 10:28:15 +11:00
InfiniteTF
0f622c84f4 Add SVG studio image support, and studio image caching (#418)
* Support SVGs for studio images and add ETAGs
* Add SVG to studio image input
* Update content sniffing
2020-04-03 09:11:48 +11:00
InfiniteTF
aee9df966b Cut over to v2.5 UI (#433)
* Cut over to v2.5 UI
* Use node 12 in travis
* Remove unnecessary `nvm use`
* Update docker file
2020-04-03 08:46:23 +11:00
ueaslsef
10b6d4b579 [Feature] Add image count to gallery list (#429)
* Add imagecount to gallery list
* Port to 2.5
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-04-02 08:27:33 +11:00
WithoutPants
82201e23e0 Make ethnicity freetext and fix freeones ethnicity panic (#431)
* Make ethnicity free text

* Fix panic in freeones scraper for other ethnicity
2020-04-02 08:25:39 +11:00
Anon247
0bbb2bd1d0 Fix to allow scene to be removed when attached to a movie (#421) 2020-04-01 12:07:43 +11:00
InfiniteTF
2a8e5d5b9b UI fixes (#419) 2020-04-01 11:08:01 +11:00
dependabot[bot]
777cd96759 Bump acorn from 5.7.3 to 5.7.4 in /ui/v2 (#399)
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-04-01 09:57:38 +11:00
hiddenpants255
494b794228 Add gender support for performer (#371)
Co-authored-by: HiddenPants255 <>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-04-01 09:36:38 +11:00
WithoutPants
d886012d74 Add modes for performer/tag for bulk scene editing (#412) 2020-03-23 08:16:11 +11:00
bnkai
acb7260824 Backup database if a migration is needed (#415)
* Confirm before migrating database

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-03-23 08:07:15 +11:00
WithoutPants
abf2b49803 Configurable scraper user agent string (#409)
* Add debug scrape option.

Co-authored-by: HiddenPants255 <>
2020-03-21 08:55:15 +11:00
WithoutPants
ff495361d9 Port Movies UI to v2.5 (#397)
* Ignore generated-graphql.tsx in 2.5
* Make movie name mandatory
* Port #395 fix to v2.5
* Differentiate front/back image browse buttons
* Move URL, Synopsis to separate rows
* Fix unknown query params crashing UI
2020-03-21 08:21:49 +11:00
WithoutPants
5aa6dec8dc Fix random sort not changing seed (#411) 2020-03-21 08:07:46 +11:00
bnkai
9dacad70a1 Autoassociate galleries to scenes when scanning (#405) 2020-03-19 12:36:00 +11:00
WithoutPants
1a6374fae9 Fix error when viewing scenes related to objects with illegal characters in name (#395)
* Fix gitattributes for v2.5
2020-03-14 08:06:55 +11:00
WithoutPants
6f5f3112e1 Fix marker time setting in v2.5 UI (#396) 2020-03-13 19:58:13 +11:00
WithoutPants
3de6955a9e Generate cover image (#376)
* Make mutating metadata ops mutation
* Implement scene generate screenshot
* Remove fetch policy on metadata mutations
* Port UI changes to v2.5
* Set generated image in database
2020-03-12 08:34:04 +11:00
WithoutPants
34d829338d Add image scraping support (#370)
* Add sub-scraper functionality
* Add scraping of performer image
* Add scene cover image scraping
* Port UI changes to v2.5
* Fix v2.5 dialog suggest color
* Don't convert eol of UI to support pretty
2020-03-11 11:41:55 +11:00
caustico
5fb8bbf768 Movies Section (#338)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-03-10 14:28:15 +11:00
InfiniteTF
b3fab3cfef Make mobile menu behavior more consistent, and stats styles responsive (#391) 2020-03-08 12:55:42 +11:00
WithoutPants
cb594f0e43 Fix various issues with v2.5 UI (#390)
* Fix navbar collapse breakpoint
* Fix list filter colors and height
* Make styling similar to v2
* Fix scene card zoom and orientation
* Keep p tag even without details
* Fix custom css
* Default settings tab to tasks
* Fix flickering progress bar. Fix percentage.
* Fix unsetting studio
* Fix scene gallery select
* Don't hide edit on small devices
* Fix log dropdown style
* Use monospace for custom css input
2020-03-06 20:02:02 +11:00
WithoutPants
088ddc9df4 Add new v2.5 UI (#357) 2020-03-05 10:15:02 +11:00
Infinite
c875515730 Fix localForage infinite loop, set base font-size to 14px, and tweak
styles
2020-03-04 09:21:30 +01:00
InfiniteTF
c8a56aea40 Update browser cache when images are updated (#389) 2020-03-03 10:33:41 +11:00
InfiniteTF
41f7a46ac1 Fix performer height filter error (#388)
* Fix performer height filter error
* Make all performer columns qualified

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-03-03 10:30:29 +11:00
InfiniteTF
80a8d2de97 Use parameter binding for all queries (#387) 2020-03-03 09:18:14 +11:00
Infinite
2fac50ba3e Formatting and list hook fix 2020-03-01 21:30:40 +01:00
Infinite
85936edb9c Fix list filter default values and error state 2020-03-01 21:04:38 +01:00
Infinite
429df84479 Update stats page #366 2020-03-01 21:04:38 +01:00
Infinite
93c7ad7559 Make performer image position top #358 2020-03-01 21:04:38 +01:00
Infinite
5b6a3a7732 Fix scene edit page for smaller screens 2020-03-01 21:04:38 +01:00
Infinite
dba4b350a0 Fix scene list view 2020-03-01 21:04:38 +01:00
Infinite
ac4f4972b6 Correct scene card truncation 2020-03-01 21:04:38 +01:00
Infinite
8672c4437e Navbar key 2020-03-01 21:04:38 +01:00
Infinite
6d71663f99 Fix whitespace 2020-03-01 21:04:37 +01:00
Infinite
195158db8b Fix performer scrape menu layout 2020-03-01 21:04:37 +01:00
Infinite
857b2cc78d Lint + prettier 2020-03-01 21:04:37 +01:00
Infinite
960d5c7000 Switch to hamburger menu for phones 2020-03-01 21:04:37 +01:00
Infinite
49354d571b Small fixes 2020-03-01 21:04:37 +01:00
Infinite
6f07380628 Fix list hook state 2020-03-01 21:04:37 +01:00
Infinite
151d69632e Refactor list hook filter storag 2020-03-01 21:04:37 +01:00
Infinite
6df25a2c5e Fix toast styles 2020-03-01 21:04:37 +01:00
Infinite
750759e4bf Prettier 2020-03-01 21:04:37 +01:00
Infinite
8fe6eb0d77 Update jwplayer 2020-03-01 21:04:37 +01:00
Infinite
cdadb66d85 Remove or exempt all uses of 'any
* Refactored LocalForage
* Refactored SceneFilenameParser
2020-03-01 21:04:37 +01:00
Infinite
a60c89ceb1 Fix localForage initialization when stored config is undefined 2020-03-01 21:04:37 +01:00
Infinite
a43cae43c0 Prettier 2020-03-01 21:04:37 +01:00
Infinite
a7df23c54d Fix 2020-03-01 21:04:37 +01:00
Infinite
3a0420b79e Change i18n to just use single language setting 2020-03-01 21:04:37 +01:00
Infinite
e6d9d385a7 Add O-counter (#334) 2020-03-01 21:04:37 +01:00
Infinite
f23247d9c8 Delete swp 2020-03-01 21:04:37 +01:00
Infinite
a5036ebe5f Prevent flashing when filter is updated 2020-03-01 21:04:37 +01:00
Infinite
4e5c65f90d Maintain filter parameters in session (#326) 2020-03-01 21:04:37 +01:00
Infinite
690596aa34 Refactor list filter query functionality 2020-03-01 21:04:37 +01:00
Infinite
0fdde7726b i18n 2020-03-01 21:04:37 +01:00
Infinite
fb5a49e58c Add Xpath post processing and performer name query (#333) 2020-03-01 21:04:36 +01:00
Infinite
3bbcc65521 Accept random seed from UI for random sorting (#328) 2020-03-01 21:04:36 +01:00
Infinite
79f69b4d61 Allow path separator in scene filename parser pattern (#327) 2020-03-01 21:04:36 +01:00
Infinite
c33639d824 Rename component folders, and prune styles 2020-03-01 21:04:36 +01:00
Infinite
fd560c2147 Switch back to CRA 2020-03-01 21:04:36 +01:00
Infinite
49a6076500 Style changes 2020-03-01 21:04:36 +01:00
Infinite
4a32f90382 Styles 2020-03-01 21:04:36 +01:00
Infinite
c1ce6d539d Prettier 2020-03-01 21:04:35 +01:00
Infinite
3f9f32c356 Responsive styles for portrait orientation phones 2020-03-01 21:04:35 +01:00
Infinite
1ccf8d1586 Changes 2020-03-01 21:04:35 +01:00
Infinite
247ad0a470 Style fixes 2020-03-01 21:04:35 +01:00
Infinite
ac3d03715f Styling 2020-03-01 21:04:35 +01:00
Infinite
3fa3f61d93 Styling 2020-03-01 21:04:35 +01:00
Infinite
c2544fee98 Prettier 2020-03-01 21:04:35 +01:00
Infinite
e1a1914d16 Fixes 2020-03-01 21:04:35 +01:00
Infinite
d1ffc0be0a Optimization 2020-03-01 21:04:35 +01:00
Infinite
1928f09dcf Eject create-react-app 2020-03-01 21:04:35 +01:00
Infinite
95bedc77fe Styling 2020-03-01 21:04:34 +01:00
Infinite
71251ee6a8 Add useful links to about page (#322) 2020-03-01 21:04:34 +01:00
Infinite
498491e82d Fix react warnings (#317) 2020-03-01 21:04:34 +01:00
Infinite
71dd939806 Add scene duration filter (#313) 2020-03-01 21:04:34 +01:00
Infinite
dda36f6b09 Add check version functionality (#296) 2020-03-01 21:04:34 +01:00
Infinite
63cc97d199 Add scenes tab to performer page (#280) 2020-03-01 21:04:33 +01:00
Infinite
dcfd445040 Library updates 2020-03-01 21:04:33 +01:00
Infinite
111d5dd7c5 Fixes 2020-03-01 21:04:33 +01:00
Infinite
244c8ff234 Prettier 2020-03-01 21:04:32 +01:00
Infinite
9827647122 Linting update 2020-03-01 21:04:32 +01:00
Infinite
c83e0898f9 Linting config 2020-03-01 21:04:32 +01:00
Infinite
c31205c47f Linting 2020-03-01 21:04:31 +01:00
Infinite
0cb61d14be Remove final react-router props 2020-03-01 21:04:31 +01:00
Infinite
0e717d6aae Fixes 2020-03-01 21:04:31 +01:00
Infinite
129dcecdef Blueprint removed 2020-03-01 21:04:31 +01:00
Infinite
e18e67b512 Toast 2020-03-01 21:04:31 +01:00
Infinite
cb1fa323e4 Changes 2020-03-01 21:04:30 +01:00
Infinite
f50cb45ca5 Changes 2020-03-01 21:04:30 +01:00
Infinite
4d44deff64 WIP 2020-03-01 21:04:30 +01:00
Infinite
716c33fc8e Use parameter binding for all queries 2020-02-29 16:18:59 +01:00
WithoutPants
97ab40595e Merge master back to develop 2020-02-25 12:43:42 +11:00
bnkai
6578cfb843 Fix version check for v0.1.0+ (#382)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2020-02-25 11:59:08 +11:00
WithoutPants
acc8a126d0 Fix travis tagged build (#379)
* Fix tagged master build on travis
* Remove branches section
2020-02-24 17:05:08 +11:00
2057 changed files with 368895 additions and 157191 deletions

63
.dockerignore Normal file
View File

@@ -0,0 +1,63 @@
####
# Go
####
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Packr2 artifacts
**/*-packr.go
# GraphQL generated output
pkg/models/generated_*.go
ui/v2.5/src/core/generated-*.tsx
# packr generated files
*-packr.go
####
# Jetbrains
####
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
####
# Random
####
ui/v2.5/node_modules
ui/v2.5/build
*.db
stash
dist
docker

6
.gitattributes vendored
View File

@@ -1,2 +1,6 @@
go.mod text eol=lf
go.sum text eol=lf
go.sum text eol=lf
*.go text eol=lf
vendor/** -text
ui/v2.5/**/*.ts* text eol=lf
ui/v2.5/**/*.scss text eol=lf

View File

@@ -23,6 +23,8 @@ 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]

6
.gitignore vendored
View File

@@ -20,7 +20,7 @@
# GraphQL generated output
pkg/models/generated_*.go
ui/v2/src/core/generated-*.tsx
ui/v2.5/src/core/generated-*.tsx
# packr generated files
*-packr.go
@@ -48,6 +48,9 @@ ui/v2/src/core/generated-*.tsx
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Goland Junk
pkg/pkg
####
# Random
####
@@ -58,3 +61,4 @@ node_modules
stash
dist
.DS_Store

13
.gqlgenc.yml Normal file
View File

@@ -0,0 +1,13 @@
model:
filename: ./pkg/scraper/stashbox/graphql/generated_models.go
client:
filename: ./pkg/scraper/stashbox/graphql/generated_client.go
models:
Date:
model: github.com/99designs/gqlgen/graphql.String
endpoint:
# This points to stashdb.org currently, but can be directed at any stash-box
# instance. It is used for generation only.
url: https://stashdb.org/graphql
query:
- "./graphql/stash-box/*.graphql"

7
.idea/go.iml generated
View File

@@ -4,11 +4,10 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/certs" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/ui/v1/dist" />
<excludeFolder url="file://$MODULE_DIR$/ui/v2/build" />
<excludeFolder url="file://$MODULE_DIR$/ui/v2/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/ui/v2.5/build" />
<excludeFolder url="file://$MODULE_DIR$/ui/v2.5/node_modules" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
</module>

View File

@@ -1,25 +1,50 @@
if: tag != latest_develop # dont build for the latest_develop tagged version
dist: xenial
git:
depth: false
language: go
go:
- 1.11.x
- 1.13.x
services:
- docker
env:
global:
- GO111MODULE=on
before_install:
- set -e
# Configure environment so changes are picked up when the Docker daemon is restarted after upgrading
- echo '{"experimental":true}' | sudo tee /etc/docker/daemon.json
- export DOCKER_CLI_EXPERIMENTAL=enabled
# Upgrade to Docker CE 19.03 for BuildKit support
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
- sudo apt-get update
- sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
# install binfmt docker container, this container uses qemu to run arm programs transparently allowng docker to build arm 6,7,8 containers.
- docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
# Show info to simplify debugging and create a builder that can build the platforms we need
- docker info
- docker buildx create --name builder --use
- docker buildx inspect --bootstrap
- docker buildx ls
install:
- echo -e "machine github.com\n login $CI_USER_TOKEN" > ~/.netrc
- travis_retry yarn --cwd ui/v2 install --frozen-lockfile
- nvm install 12
- travis_retry make pre-ui
- make generate
- CI=false yarn --cwd ui/v2 build # TODO: Fix warnings
- CI=false make ui-validate ui-only
#- go get -v github.com/mgechev/revive
script:
# left lint off to avoid getting extra dependency
#- make lint
#- make vet
- make it
- make fmt-check vet it
after_success:
- docker pull stashapp/compiler:develop
- docker pull stashapp/compiler:4
- sh ./scripts/cross-compile.sh
- git describe --tags --exclude latest_develop | tee CHECKSUMS_SHA1
- sha1sum dist/stash-* | sed 's/dist\///g' | tee -a CHECKSUMS_SHA1
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then sh ./scripts/upload-pull-request.sh; fi'
before_deploy:
# push the latest tag when on the develop branch
@@ -27,49 +52,67 @@ before_deploy:
- export RELEASE_DATE=$(date +'%Y-%m-%d %H:%M:%S %Z')
- export STASH_VERSION=$(git describe --tags --exclude latest_develop)
# set TRAVIS_TAG explcitly to the version so that it doesn't pick up latest_develop
- if [ "$TRAVIS_BRANCH" = "master"]; then export TRAVIS_TAG=${STASH_VERSION}; fi
- if [ "$TRAVIS_BRANCH" = "master" ]; then export TRAVIS_TAG=${STASH_VERSION}; fi
deploy:
# latest develop release
- provider: releases
# use the v2 release provider for proper release note setting
edge: true
api_key:
secure: tGJ2q62CfPdayid2qEtW2aGRhMgCl3lBXYYQqp3eH0vFgIIf6cs7IDX7YC/x3XKMEQ/iMLZmtCXZvSTqNrD6Sk7MSnt30GIs+4uxIZDnnd8mV5X3K4n4gjD+NAORc4DrQBvUGrYMKJsR5gtkH0nu6diWb1o1If7OiJEuCPRhrmQYcza7NUdABnA9Z2wn2RNUV9Ga33WUCqLMEU5GtNBlfQPiP/khCQrqn/ocR6wUjYut3J6YagzqH4wsfJi3glHyWtowcNIw1LZi5zFxHD/bRBT4Tln7yypkjWNq9eQILA6i6kRUGf7ggyTx26/k8n4tnu+QD0vVh4EcjlThpU/LGyUXzKrrxjRwaDZnM0oYxg5AfHcBuAiAdo0eWnV3lEWRfTJMIVb9MPf4qDmzR4RREfB5OXOxwq3ODeCcJE8sTIMD/wBPZrlqS/QrRpND2gn2X4snkVukN9t9F4CMTFMtVSzFV7TDJW5E5Lq6VEExulteQhs6kcK9NRPNAaLgRQAw7X9kVWfDtiGUP+fE2i8F9Bo8bm7sOT5O5VPMPykx3EgeNg1IqIgMTCsMlhMJT4xBJoQUgmd2wWyf3Ryw+P+sFgdb5Sd7+lFgJBjMUUoOxMxAOiEgdFvCXcr+/Udyz2RdtetU1/6VzXzLPcKOw0wubZeBkISqu7o9gpfdMP9Eq00=
file:
- dist/stash-osx
- dist/stash-win.exe
- dist/stash-linux
- dist/stash-pi
- dist/stash-osx
- dist/stash-win.exe
- dist/stash-linux
- dist/stash-linux-arm64v8
- dist/stash-linux-arm32v7
- dist/stash-pi
- CHECKSUMS_SHA1
skip_cleanup: true
overwrite: true
name: "${STASH_VERSION}: Latest development build"
body: ${RELEASE_DATE}\n This is always the latest committed version on the develop branch. Use as your own risk!
release_notes: "**${RELEASE_DATE}**\n This is always the latest committed version on the develop branch. Use as your own risk!"
prerelease: true
on:
repo: stashapp/stash
branch: develop
# docker image build for develop release
- provider: script
skip_cleanup: true
script: bash ./docker/ci/x86_64/docker_push.sh development
on:
repo: stashapp/stash
branch: develop
# official master release - only build when tagged
- provider: releases
api_key:
secure: tGJ2q62CfPdayid2qEtW2aGRhMgCl3lBXYYQqp3eH0vFgIIf6cs7IDX7YC/x3XKMEQ/iMLZmtCXZvSTqNrD6Sk7MSnt30GIs+4uxIZDnnd8mV5X3K4n4gjD+NAORc4DrQBvUGrYMKJsR5gtkH0nu6diWb1o1If7OiJEuCPRhrmQYcza7NUdABnA9Z2wn2RNUV9Ga33WUCqLMEU5GtNBlfQPiP/khCQrqn/ocR6wUjYut3J6YagzqH4wsfJi3glHyWtowcNIw1LZi5zFxHD/bRBT4Tln7yypkjWNq9eQILA6i6kRUGf7ggyTx26/k8n4tnu+QD0vVh4EcjlThpU/LGyUXzKrrxjRwaDZnM0oYxg5AfHcBuAiAdo0eWnV3lEWRfTJMIVb9MPf4qDmzR4RREfB5OXOxwq3ODeCcJE8sTIMD/wBPZrlqS/QrRpND2gn2X4snkVukN9t9F4CMTFMtVSzFV7TDJW5E5Lq6VEExulteQhs6kcK9NRPNAaLgRQAw7X9kVWfDtiGUP+fE2i8F9Bo8bm7sOT5O5VPMPykx3EgeNg1IqIgMTCsMlhMJT4xBJoQUgmd2wWyf3Ryw+P+sFgdb5Sd7+lFgJBjMUUoOxMxAOiEgdFvCXcr+/Udyz2RdtetU1/6VzXzLPcKOw0wubZeBkISqu7o9gpfdMP9Eq00=
file:
- dist/stash-osx
- dist/stash-win.exe
- dist/stash-linux
- dist/stash-pi
- dist/stash-osx
- dist/stash-win.exe
- dist/stash-linux
- dist/stash-linux-arm64v8
- dist/stash-linux-arm32v7
- dist/stash-pi
- CHECKSUMS_SHA1
# make the release a draft so the maintainers can confirm before releasing
draft: true
skip_cleanup: true
overwrite: true
# don't write the body. To be done manually for now. In future we might
# don't write the body. To be done manually for now. In future we might
# want to generate the changelog or get it from a file
name: ${STASH_VERSION}
on:
repo: stashapp/stash
branch: master
tags: true
# make sure we don't release using the latest_develop tag
condition: $TRAVIS_TAG != latest_develop
branches:
only:
- master
- develop
# docker image build for master release
- provider: script
skip_cleanup: true
script: bash ./docker/ci/x86_64/docker_push.sh latest
on:
repo: stashapp/stash
tags: true
# make sure we don't release using the latest_develop tag
condition: $TRAVIS_TAG != latest_develop

113
Makefile
View File

@@ -1,15 +1,55 @@
ifeq ($(OS),Windows_NT)
SEPARATOR := &&
SET := set
IS_WIN =
ifeq (${SHELL}, sh.exe)
IS_WIN = true
endif
ifeq (${SHELL}, cmd)
IS_WIN = true
endif
release: generate ui build
ifdef IS_WIN
SEPARATOR := &&
SET := set
else
SEPARATOR := ;
SET := export
endif
build:
$(eval DATE := $(shell go run scripts/getDate.go))
# set LDFLAGS environment variable to any extra ldflags required
# set OUTPUT to generate a specific binary name
LDFLAGS := $(LDFLAGS)
ifdef OUTPUT
OUTPUT := -o $(OUTPUT)
endif
.PHONY: release pre-build install clean
release: generate ui build-release
pre-build:
ifndef BUILD_DATE
$(eval BUILD_DATE := $(shell go run -mod=vendor scripts/getDate.go))
endif
ifndef GITHASH
$(eval GITHASH := $(shell git rev-parse --short HEAD))
endif
ifndef STASH_VERSION
$(eval STASH_VERSION := $(shell git describe --tags --exclude latest_develop))
$(SET) CGO_ENABLED=1 $(SEPARATOR) go build -mod=vendor -v -ldflags "-X 'github.com/stashapp/stash/pkg/api.version=$(STASH_VERSION)' -X 'github.com/stashapp/stash/pkg/api.buildstamp=$(DATE)' -X 'github.com/stashapp/stash/pkg/api.githash=$(GITHASH)'"
endif
build: pre-build
$(eval LDFLAGS := $(LDFLAGS) -X 'github.com/stashapp/stash/pkg/api.version=$(STASH_VERSION)' -X 'github.com/stashapp/stash/pkg/api.buildstamp=$(BUILD_DATE)' -X 'github.com/stashapp/stash/pkg/api.githash=$(GITHASH)')
$(SET) CGO_ENABLED=1 $(SEPARATOR) go build $(OUTPUT) -mod=vendor -v -tags "sqlite_omit_load_extension osusergo netgo" -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS)"
# strips debug symbols from the release build
# consider -trimpath in go build if we move to go 1.13+
build-release: EXTRA_LDFLAGS := -s -w
build-release: build
build-release-static: EXTRA_LDFLAGS := -extldflags=-static -s -w
build-release-static: build
install:
packr2 install
@@ -21,13 +61,23 @@ clean:
.PHONY: generate
generate:
go generate -mod=vendor
cd ui/v2 && yarn run gqlgen
cd ui/v2.5 && yarn run gqlgen
# Regenerates stash-box client files
.PHONY: generate-stash-box-client
generate-stash-box-client:
go run -mod=vendor github.com/Yamashou/gqlgenc
# Runs gofmt -w on the project's source code, modifying any files that do not match its style.
.PHONY: fmt
fmt:
go fmt ./...
# Ensures that changed files have had gofmt run on them
.PHONY: fmt-check
fmt-check:
sh ./scripts/check-gofmt.sh
# Runs go vet on the project's source code.
.PHONY: vet
vet:
@@ -47,7 +97,50 @@ test:
it:
go test -mod=vendor -tags=integration ./...
# generates test mocks
.PHONY: generate-test-mocks
generate-test-mocks:
go run -mod=vendor github.com/vektra/mockery/v2 --dir ./pkg/models --name '.*ReaderWriter' --outpkg mocks --output ./pkg/models/mocks
# installs UI dependencies. Run when first cloning repository, or if UI
# dependencies have changed
.PHONY: pre-ui
pre-ui:
cd ui/v2.5 && yarn install --frozen-lockfile
.PHONY: ui-only
ui-only: pre-build
$(SET) REACT_APP_DATE="$(BUILD_DATE)" $(SEPARATOR) \
$(SET) REACT_APP_GITHASH=$(GITHASH) $(SEPARATOR) \
$(SET) REACT_APP_STASH_VERSION=$(STASH_VERSION) $(SEPARATOR) \
cd ui/v2.5 && yarn build
.PHONY: ui
ui:
cd ui/v2 && yarn build
ui: ui-only
packr2
.PHONY: ui-start
ui-start: pre-build
$(SET) REACT_APP_DATE="$(BUILD_DATE)" $(SEPARATOR) \
$(SET) REACT_APP_GITHASH=$(GITHASH) $(SEPARATOR) \
$(SET) REACT_APP_STASH_VERSION=$(STASH_VERSION) $(SEPARATOR) \
cd ui/v2.5 && yarn start
.PHONY: fmt-ui
fmt-ui:
cd ui/v2.5 && yarn format
# runs tests and checks on the UI and builds it
.PHONY: ui-validate
ui-validate:
cd ui/v2.5 && yarn run validate
# just repacks the packr files - use when updating migrations and packed files without
# rebuilding the UI
.PHONY: packr
packr:
packr2
# runs all of the tests and checks required for a PR to be accepted
.PHONY: validate
validate: ui-validate fmt-check vet lint it

View File

@@ -4,10 +4,14 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/stashapp/stash)](https://goreportcard.com/report/github.com/stashapp/stash)
[![Discord](https://img.shields.io/discord/559159668438728723.svg?logo=discord)](https://discord.gg/2TsNFKt)
https://stashapp.cc
**Stash is a Go app which organizes and serves your porn.**
See a demo [here](https://vimeo.com/275537038) (password is stashapp).
An in-app manual is available, and the manual pages can be viewed [here](https://github.com/stashapp/stash/tree/develop/ui/v2.5/src/docs/en).
# Docker install
Follow [this README.md in the docker directory.](docker/production/README.md)
@@ -69,7 +73,7 @@ Join the [Discord server](https://discord.gg/2TsNFKt).
* Go Install: `go get github.com/gobuffalo/packr/v2/packr2@v2.0.2`
* [Binary Download](https://github.com/gobuffalo/packr/releases)
* [Yarn](https://yarnpkg.com/en/docs/install) - Yarn package manager
* Run `yarn install --frozen-lockfile` in the `stash/ui/v2` folder (before running make generate for first time).
* Run `yarn install --frozen-lockfile` in the `stash/ui/v2.5` folder (before running make generate for first time).
NOTE: You may need to run the `go get` commands outside the project directory to avoid modifying the projects module file.
@@ -92,11 +96,18 @@ NOTE: The `make` command in Windows will be `mingw32-make` with MingW.
## Commands
* `make generate` - Generate Go GraphQL and packr2 files
* `make generate` - Generate Go and UI GraphQL files
* `make build` - Builds the binary (make sure to build the UI as well... see below)
* `make ui` - Builds the frontend
* `make pre-ui` - Installs the UI dependencies. Only needs to be run once before building the UI for the first time, or if the dependencies are updated
* `make fmt-ui` - Formats the UI source code.
* `make ui` - Builds the frontend and the packr2 files
* `make packr` - Generate packr2 files (sub-target of `ui`. Use to regenerate packr2 files without rebuilding UI)
* `make vet` - Run `go vet`
* `make lint` - Run the linter
* `make fmt` - Run `go fmt`
* `make fmt-check` - Ensure changed files are formatted correctly
* `make it` - Run the unit and integration tests
* `make validate` - Run all of the tests and checks required to submit a PR
## Building a release
@@ -111,3 +122,10 @@ where the app can be cross-compiled. This process is kicked off by CI via the `
command to open a bash shell to the container to poke around:
`docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -i -t stashappdev/compiler:latest /bin/bash`
## Customization
You can make Stash interface fit your desired style with [Custom CSS snippets](https://github.com/stashapp/stash/wiki/Custom-CSS-snippets) and [CSS Tweaks](https://github.com/stashapp/stash/wiki/CSS-Tweaks).
[Stash Plex Theme](https://github.com/stashapp/stash/wiki/Stash-Plex-Theme) is a community created theme inspired by popular Plex Interface.

View File

@@ -2,12 +2,16 @@
# ie from top=level stash:
# docker build -t stash/build -f docker/build/x86_64/Dockerfile .
FROM golang:1.11.13 as compiler
FROM golang:1.13.15 as compiler
RUN apt-get update && apt-get install -y apt-transport-https
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
# prevent caching of the key
ADD https://dl.yarnpkg.com/debian/pubkey.gpg yarn.gpg
RUN cat yarn.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
rm yarn.gpg
RUN apt-get update && \
apt-get install -y nodejs yarn xz-utils --no-install-recommends || exit 1; \
@@ -32,10 +36,10 @@ RUN wget -O /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-rele
mv /ffmpeg*/ /ffmpeg/
# copy the ui yarn stuff so that it doesn't get rebuilt every time
COPY ./ui/v2/package.json ./ui/v2/yarn.lock /stash/ui/v2/
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
WORKDIR /stash
RUN yarn --cwd ui/v2 install --frozen-lockfile
RUN yarn --cwd ui/v2.5 install --frozen-lockfile
COPY . /stash/
ENV GO111MODULE=on
@@ -44,7 +48,7 @@ RUN make generate
RUN make ui
RUN make build
FROM ubuntu:19.10 as app
FROM ubuntu:20.04 as app
RUN apt-get update && apt-get -y install ca-certificates
COPY --from=compiler /stash/stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/

View File

@@ -0,0 +1,17 @@
FROM --platform=$BUILDPLATFORM ubuntu:20.04 AS prep
ARG TARGETPLATFORM
WORKDIR /
COPY stash-* /
RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then BIN=stash-pi; \
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then BIN=stash-linux-arm32v7; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then BIN=stash-linux-arm64v8; \
elif [ "$TARGETPLATFORM" = "linux/amd64" ]; then BIN=stash-linux; \
fi; \
mv $BIN /stash
FROM ubuntu:20.04 as app
run apt update && apt install -y python3 python3 python-is-python3 python3-requests ffmpeg && rm -rf /var/lib/apt/lists/*
COPY --from=prep /stash /usr/bin/
EXPOSE 9999
CMD ["stash"]

View File

@@ -0,0 +1 @@
This dockerfile is used by travis to build the stash image. It must be run after cross-compiling - that is, `stash-linux` must exist in the `dist` directory. This image must be built from the `dist` directory.

View File

@@ -0,0 +1,8 @@
#!/bin/bash
DOCKER_TAG=$1
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
# must build the image from dist directory
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 --push --output type=image,name=stashapp/stash:$DOCKER_TAG,push=true -f docker/ci/x86_64/Dockerfile dist/

View File

@@ -1,4 +1,4 @@
FROM golang:1.11.5
FROM golang:1.13.15
LABEL maintainer="stashappdev@gmail.com"
@@ -9,17 +9,22 @@ ENV PACKR2_DOWNLOAD_URL=https://github.com/gobuffalo/packr/releases/download/v${
# Install tools
RUN apt-get update && apt-get install -y apt-transport-https
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
# prevent caching of the key
ADD https://dl.yarnpkg.com/debian/pubkey.gpg yarn.gpg
RUN cat yarn.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
rm yarn.gpg
RUN apt-get update && \
apt-get install -y automake autogen \
libtool libxml2-dev uuid-dev libssl-dev bash \
patch make tar xz-utils bzip2 gzip sed cpio \
gcc-6-multilib g++-6-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \
gcc-8-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \
gcc-arm-linux-gnueabi libc-dev-armel-cross linux-libc-dev-armel-cross \
gcc-arm-linux-gnueabihf libc-dev-armhf-cross \
gcc-aarch64-linux-gnu libc-dev-arm64-cross \
nodejs yarn --no-install-recommends || exit 1; \
rm -rf /var/lib/apt/lists/*;

View File

@@ -1,6 +1,6 @@
user=stashappdev
user=stashapp
repo=compiler
version=2
version=4
latest:
docker build -t ${user}/${repo}:latest .

View File

@@ -1 +1,3 @@
Modified from https://github.com/bep/dockerfiles/tree/master/ci-goreleaser
Modified from https://github.com/bep/dockerfiles/tree/master/ci-goreleaser
When the dockerfile is changed, the version number should be incremented in the Makefile and the new version tag should be pushed to docker hub. The `scripts/cross-compile.sh` script should also be updated to use the new version number tag, and `.travis.yml` needs to be updated to pull the correct image tag.

View File

@@ -1,4 +1,4 @@
FROM ubuntu:18.04 as prep
FROM ubuntu:20.04 as prep
LABEL MAINTAINER="https://discord.gg/Uz29ny"
RUN apt-get update && \
@@ -7,14 +7,17 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*
WORKDIR /
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN curl -L -o /stash $(curl -s https://api.github.com/repos/stashapp/stash/releases/tags/latest_develop | awk '/browser_download_url/ && /stash-linux/' | sed -e 's/.*: "\(.*\)"/\1/') && \
chmod +x /stash && \
curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
# added " to end of stash-linux clause so that it doesn't pick up the arm builds
RUN curl -L -o /stash $(curl -s https://api.github.com/repos/stashapp/stash/releases/tags/latest_develop | awk '/browser_download_url/ && /stash-linux"/' | sed -e 's/.*: "\(.*\)"/\1/') && \
chmod +x /stash
RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
tar xf /ffmpeg.tar.xz && \
rm ffmpeg.tar.xz && \
mv /ffmpeg*/ /ffmpeg/
FROM ubuntu:18.04 as app
FROM ubuntu:20.04 as app
RUN apt-get update && apt-get -y install ca-certificates
COPY --from=prep /stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
EXPOSE 9999

View File

@@ -1,4 +1,4 @@
FROM ubuntu:18.04 as prep
FROM ubuntu:20.04 as prep
LABEL MAINTAINER="leopere [at] nixc [dot] us"
RUN apt-get update && \
@@ -7,14 +7,17 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*
WORKDIR /
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN curl -L -o /stash $(curl -s https://api.github.com/repos/stashapp/stash/releases/latest | awk '/browser_download_url/ && /stash-linux/' | sed -e 's/.*: "\(.*\)"/\1/') && \
chmod +x /stash && \
curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
# added " to end of stash-linux clause so that it doesn't pick up the arm builds
RUN curl -L -o /stash $(curl -s https://api.github.com/repos/stashapp/stash/releases/latest | awk '/browser_download_url/ && /stash-linux/"' | sed -e 's/.*: "\(.*\)"/\1/') && \
chmod +x /stash
RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
tar xf /ffmpeg.tar.xz && \
rm ffmpeg.tar.xz && \
mv /ffmpeg*/ /ffmpeg/
FROM ubuntu:18.04 as app
FROM ubuntu:20.04 as app
RUN apt-get update && apt-get -y install ca-certificates
COPY --from=prep /stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
EXPOSE 9999

41
go.mod
View File

@@ -1,33 +1,42 @@
module github.com/stashapp/stash
require (
github.com/99designs/gqlgen v0.9.0
github.com/PuerkitoBio/goquery v1.5.0
github.com/antchfx/htmlquery v1.2.0
github.com/antchfx/xpath v1.1.2 // indirect
github.com/bmatcuk/doublestar v1.1.5
github.com/99designs/gqlgen v0.12.2
github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953
github.com/antchfx/htmlquery v1.2.3
github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c
github.com/chromedp/chromedp v0.5.3
github.com/disintegration/imaging v1.6.0
github.com/fvbommel/sortorder v1.0.2
github.com/go-chi/chi v4.0.2+incompatible
github.com/gobuffalo/packr/v2 v2.0.2
github.com/golang-migrate/migrate/v4 v4.3.1
github.com/gorilla/websocket v1.4.0
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.0
github.com/gorilla/websocket v1.4.2
github.com/h2non/filetype v1.0.8
// this is required for generate
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/jmoiron/sqlx v1.2.0
github.com/mattn/go-sqlite3 v1.10.0
github.com/json-iterator/go v1.1.9
github.com/mattn/go-sqlite3 v1.13.0
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/cors v1.6.0
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
github.com/sirupsen/logrus v1.4.2
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.4.0
github.com/vektah/gqlparser v1.1.2
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 // indirect
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
gopkg.in/yaml.v2 v2.2.2
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.5.1
github.com/tidwall/gjson v1.6.0
github.com/vektah/gqlparser/v2 v2.0.1
github.com/vektra/mockery/v2 v2.2.1
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/tools v0.0.0-20200915031644-64986481280e // indirect
gopkg.in/yaml.v2 v2.3.0
)
replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999
go 1.11
go 1.13

313
go.sum
View File

@@ -3,60 +3,89 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
github.com/99designs/gqlgen v0.9.0 h1:g1arBPML74Vqv0L3Q+TqIhGXLspV+2MYtRLkBxuZrlE=
github.com/99designs/gqlgen v0.9.0/go.mod h1:HrrG7ic9EgLPsULxsZh/Ti+p0HNWgR3XRuvnD0pb5KY=
github.com/99designs/gqlgen v0.12.2 h1:aOdpsiCycFtCnAv8CAI1exnKrIDHMqtMzQoXeTziY4o=
github.com/99designs/gqlgen v0.12.2/go.mod h1:7zdGo6ry9u1YBp/qlb2uxSU5Mt2jQKLcBETQiKk+Bxo=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
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/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953 h1:+iPJDL28FxZhEdtJ9qykrMt/oDiOvlzTa0zV06nUcFM=
github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953/go.mod h1:kaTsk10p2hJWwrB2t7vMsk1lXj9KAHaDYRtJQiB+Ick=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
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/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antchfx/htmlquery v1.2.0 h1:oKShnsGlnOHX6t4uj5OHgLKkABcJoqnXpqnscoi9Lpw=
github.com/antchfx/htmlquery v1.2.0/go.mod h1:MS9yksVSQXls00iXkiMqXr0J+umL/AmxXKuP28SUJM8=
github.com/antchfx/xpath v1.1.2 h1:YziPrtM0gEJBnhdUGxYcIVYXZ8FXbtbovxOi+UW/yWQ=
github.com/antchfx/xpath v1.1.2/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M=
github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0=
github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0=
github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk=
github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c h1:qM1xzKK8kc93zKPkxK4iqtjksqDDrU6g9wGnr30jyLo=
github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c/go.mod h1:E6LPWRdIJc11h/di5p0rwvRmUYbhGpBEH7ZbPfzDIOE=
github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg=
github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.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 v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
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.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg=
github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=
@@ -72,6 +101,9 @@ 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc=
github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA=
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
@@ -81,6 +113,7 @@ github.com/docker/docker v0.7.3-0.20190108045446-77df18c24acf/go.mod h1:eEKB0N0r
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
@@ -93,11 +126,14 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo=
github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
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=
@@ -313,6 +349,12 @@ github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw
github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM=
github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc=
github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@@ -325,12 +367,16 @@ github.com/golang-migrate/migrate/v4 v4.3.1/go.mod h1:mJ89KBgbXmM3P49BqOxRL3riNF
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/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=
@@ -338,13 +384,21 @@ github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
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/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ -352,13 +406,16 @@ github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@@ -367,22 +424,39 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14=
github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
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.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
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/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
@@ -391,7 +465,12 @@ github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
@@ -401,6 +480,8 @@ github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46s
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -416,8 +497,14 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c=
@@ -439,24 +526,50 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc=
github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
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-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mongodb/mongo-go-driver v0.3.0/go.mod h1:NK/HWDIIZkaYsnYa0hmtP443T5ELr0KDecmIioVuuyU=
github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
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/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
@@ -472,14 +585,18 @@ github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKw
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.8.0/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 v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
@@ -497,19 +614,35 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
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/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.1.0 h1:g0fH8RicVgNl+zVZDCDfbdWxAWoAEJyI7I3TZYXFiig=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2 h1:J7U/N7eRtzjhs26d6GqMh2HBuXP8/Z64Densiiieafo=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.18.0 h1:CbAm3kP2Tptby1i9sYy2MGRg0uxIN9cyDb59Ys7W8z8=
github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
@@ -535,6 +668,8 @@ github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5J
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
@@ -547,6 +682,10 @@ github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
@@ -559,6 +698,8 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -568,35 +709,58 @@ github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaN
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA=
github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/vektra/mockery/v2 v2.2.1 h1:EYgPvxyYkm/0JKs62qlVc9pO+ljb8biPbDWabk5/PmI=
github.com/vektra/mockery/v2 v2.2.1/go.mod h1:rBZUbbhMbiSX1WlCGsOgAi6xjuJGxB7KKbnoL0XNYW8=
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@@ -610,6 +774,7 @@ golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181025113841-85e1b3f9139a/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -622,18 +787,38 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 h1:FNSSV4jv1PrPsiM2iKGpqLPPgYACqh9Muav7Pollk1k=
golang.org/x/image v0.0.0-20190118043309-183bebdce1b2/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -649,6 +834,7 @@ golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
@@ -658,14 +844,26 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/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-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -674,7 +872,11 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -699,10 +901,19 @@ golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190116161447-11f53e031339 h1:g/Jesu8+QLnA0CPzF3E1pURg0Byr7i6jLoX5sqjcAh0=
golang.org/x/sys v0.0.0-20190116161447-11f53e031339/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA=
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
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=
@@ -748,16 +959,51 @@ golang.org/x/tools v0.0.0-20190219185102-9394956cfdc5/go.mod h1:E6PF97AdD6v0s+fP
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e h1:ssd5ulOvVWlh4kDSUF2SqzmMeWfjmwDXM+uGw/aQjRE=
golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3 h1:OjYQxZBKJFs+sJbHkvSGIKNMkZXDJQ9JsMpebGhkafI=
golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200915031644-64986481280e h1:tfSNPIxC48Azhz4nLSPskz/yE9R6ftFRK8pfgfqWUAc=
golang.org/x/tools v0.0.0-20200915031644-64986481280e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -765,6 +1011,8 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -772,24 +1020,35 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
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-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-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
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.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -797,11 +1056,19 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=

View File

@@ -16,6 +16,10 @@ struct_tag: gqlgen
models:
Gallery:
model: github.com/stashapp/stash/pkg/models.Gallery
Image:
model: github.com/stashapp/stash/pkg/models.Image
ImageFileType:
model: github.com/stashapp/stash/pkg/models.ImageFileType
Performer:
model: github.com/stashapp/stash/pkg/models.Performer
Scene:
@@ -26,6 +30,8 @@ models:
model: github.com/stashapp/stash/pkg/models.ScrapedItem
Studio:
model: github.com/stashapp/stash/pkg/models.Studio
Movie:
model: github.com/stashapp/stash/pkg/models.Movie
Tag:
model: github.com/stashapp/stash/pkg/models.Tag
ScrapedPerformer:
@@ -36,7 +42,15 @@ models:
model: github.com/stashapp/stash/pkg/models.ScrapedScenePerformer
ScrapedSceneStudio:
model: github.com/stashapp/stash/pkg/models.ScrapedSceneStudio
ScrapedSceneMovie:
model: github.com/stashapp/stash/pkg/models.ScrapedSceneMovie
ScrapedSceneTag:
model: github.com/stashapp/stash/pkg/models.ScrapedSceneTag
SceneFileType:
model: github.com/stashapp/stash/pkg/models.SceneFileType
ScrapedMovie:
model: github.com/stashapp/stash/pkg/models.ScrapedMovie
ScrapedMovieStudio:
model: github.com/stashapp/stash/pkg/models.ScrapedMovieStudio
StashID:
model: github.com/stashapp/stash/pkg/models.StashID

View File

@@ -1,26 +1,55 @@
fragment ConfigGeneralData on ConfigGeneralResult {
stashes
stashes {
path
excludeVideo
excludeImage
}
databasePath
generatedPath
cachePath
calculateMD5
videoFileNamingAlgorithm
parallelTasks
previewSegments
previewSegmentDuration
previewExcludeStart
previewExcludeEnd
previewPreset
maxTranscodeSize
maxStreamingTranscodeSize
username
password
maxSessionAge
logFile
logOut
logLevel
logAccess
createGalleriesFromFolders
videoExtensions
imageExtensions
galleryExtensions
excludes
imageExcludes
scraperUserAgent
scraperCDPPath
stashBoxes {
name
endpoint
api_key
}
}
fragment ConfigInterfaceData on ConfigInterfaceResult {
menuItems
soundOnPreview
wallShowTitle
wallPlayback
maximumLoopDuration
autostartVideo
showStudioAsText
css
cssEnabled
language
}
fragment ConfigData on ConfigResult {

View File

@@ -0,0 +1,29 @@
fragment GallerySlimData on Gallery {
id
checksum
path
title
date
url
details
rating
organized
image_count
cover {
...SlimImageData
}
studio {
...StudioData
}
tags {
...TagData
}
performers {
...PerformerData
}
scenes {
id
title
path
}
}

View File

@@ -3,9 +3,28 @@ fragment GalleryData on Gallery {
checksum
path
title
files {
index
name
path
date
url
details
rating
organized
images {
...SlimImageData
}
cover {
...SlimImageData
}
studio {
...StudioData
}
tags {
...TagData
}
performers {
...PerformerData
}
scenes {
...SceneData
}
}

View File

@@ -0,0 +1,44 @@
fragment SlimImageData on Image {
id
checksum
title
rating
organized
o_counter
path
file {
size
width
height
}
paths {
thumbnail
image
}
galleries {
id
path
title
}
studio {
id
name
image_path
}
tags {
id
name
}
performers {
id
name
favorite
image_path
}
}

View File

@@ -0,0 +1,36 @@
fragment ImageData on Image {
id
checksum
title
rating
organized
o_counter
path
file {
size
width
height
}
paths {
thumbnail
image
}
galleries {
...GalleryData
}
studio {
...StudioData
}
tags {
...TagData
}
performers {
...PerformerData
}
}

View File

@@ -0,0 +1,5 @@
fragment SlimMovieData on Movie {
id
name
front_image_path
}

View File

@@ -0,0 +1,20 @@
fragment MovieData on Movie {
id
checksum
name
aliases
duration
date
rating
director
studio {
...StudioData
}
synopsis
url
front_image_path
back_image_path
scene_count
}

View File

@@ -1,5 +1,10 @@
fragment SlimPerformerData on Performer {
id
name
gender
image_path
stash_ids {
endpoint
stash_id
}
}

View File

@@ -3,6 +3,7 @@ fragment PerformerData on Performer {
checksum
name
url
gender
twitter
instagram
birthdate
@@ -19,4 +20,8 @@ fragment PerformerData on Performer {
favorite
image_path
scene_count
stash_ids {
stash_id
endpoint
}
}

View File

@@ -1,12 +1,14 @@
fragment SlimSceneData on Scene {
id
checksum
oshash
title
details
url
date
rating
o_counter
organized
path
file {
@@ -35,7 +37,7 @@ fragment SlimSceneData on Scene {
seconds
}
gallery {
galleries {
id
path
title
@@ -47,6 +49,15 @@ fragment SlimSceneData on Scene {
image_path
}
movies {
movie {
id
name
front_image_path
}
scene_index
}
tags {
id
name
@@ -58,4 +69,9 @@ fragment SlimSceneData on Scene {
favorite
image_path
}
stash_ids {
endpoint
stash_id
}
}

View File

@@ -1,12 +1,14 @@
fragment SceneData on Scene {
id
checksum
oshash
title
details
url
date
rating
o_counter
organized
path
file {
@@ -33,15 +35,20 @@ fragment SceneData on Scene {
...SceneMarkerData
}
is_streamable
gallery {
...GalleryData
galleries {
...GallerySlimData
}
studio {
...StudioData
}
movies {
movie {
...MovieData
}
scene_index
}
tags {
...TagData
@@ -50,4 +57,9 @@ fragment SceneData on Scene {
performers {
...PerformerData
}
stash_ids {
endpoint
stash_id
}
}

View File

@@ -1,5 +1,6 @@
fragment ScrapedPerformerData on ScrapedPerformer {
name
gender
url
twitter
instagram
@@ -14,11 +15,13 @@ fragment ScrapedPerformerData on ScrapedPerformer {
tattoos
piercings
aliases
image
}
fragment ScrapedScenePerformerData on ScrapedScenePerformer {
id
stored_id
name
gender
url
twitter
instagram
@@ -33,16 +36,54 @@ fragment ScrapedScenePerformerData on ScrapedScenePerformer {
tattoos
piercings
aliases
remote_site_id
images
}
fragment ScrapedSceneStudioData on ScrapedSceneStudio {
fragment ScrapedMovieStudioData on ScrapedMovieStudio {
id
name
url
}
fragment ScrapedMovieData on ScrapedMovie {
name
aliases
duration
date
rating
director
url
synopsis
front_image
back_image
studio {
...ScrapedMovieStudioData
}
}
fragment ScrapedSceneMovieData on ScrapedSceneMovie {
stored_id
name
aliases
duration
date
rating
director
url
synopsis
}
fragment ScrapedSceneStudioData on ScrapedSceneStudio {
stored_id
name
url
remote_site_id
}
fragment ScrapedSceneTagData on ScrapedSceneTag {
id
stored_id
name
}
@@ -51,6 +92,7 @@ fragment ScrapedSceneData on ScrapedScene {
details
url
date
image
file {
size
@@ -74,4 +116,70 @@ fragment ScrapedSceneData on ScrapedScene {
performers {
...ScrapedScenePerformerData
}
}
movies {
...ScrapedSceneMovieData
}
}
fragment ScrapedGalleryData on ScrapedGallery {
title
details
url
date
studio {
...ScrapedSceneStudioData
}
tags {
...ScrapedSceneTagData
}
performers {
...ScrapedScenePerformerData
}
}
fragment ScrapedStashBoxSceneData on ScrapedScene {
title
details
url
date
image
remote_site_id
duration
file {
size
duration
video_codec
audio_codec
width
height
framerate
bitrate
}
fingerprints {
hash
algorithm
duration
}
studio {
...ScrapedSceneStudioData
}
tags {
...ScrapedSceneTagData
}
performers {
...ScrapedScenePerformerData
}
movies {
...ScrapedSceneMovieData
}
}

View File

@@ -2,4 +2,11 @@ fragment SlimStudioData on Studio {
id
name
image_path
}
stash_ids {
endpoint
stash_id
}
parent_studio {
id
}
}

View File

@@ -3,6 +3,26 @@ fragment StudioData on Studio {
checksum
name
url
parent_studio {
id
checksum
name
url
image_path
scene_count
}
child_studios {
id
checksum
name
url
image_path
scene_count
}
image_path
scene_count
stash_ids {
stash_id
endpoint
}
}

View File

@@ -1,6 +1,7 @@
fragment TagData on Tag {
id
name
image_path
scene_count
scene_marker_count
}

View File

@@ -0,0 +1,41 @@
mutation GalleryCreate(
$input: GalleryCreateInput!) {
galleryCreate(input: $input) {
...GalleryData
}
}
mutation GalleryUpdate(
$input: GalleryUpdateInput!) {
galleryUpdate(input: $input) {
...GalleryData
}
}
mutation BulkGalleryUpdate(
$input: BulkGalleryUpdateInput!) {
bulkGalleryUpdate(input: $input) {
...GalleryData
}
}
mutation GalleriesUpdate($input : [GalleryUpdateInput!]!) {
galleriesUpdate(input: $input) {
...GalleryData
}
}
mutation GalleryDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated : Boolean) {
galleryDestroy(input: {ids: $ids, delete_file: $delete_file, delete_generated: $delete_generated})
}
mutation AddGalleryImages($gallery_id: ID!, $image_ids: [ID!]!) {
addGalleryImages(input: {gallery_id: $gallery_id, image_ids: $image_ids})
}
mutation RemoveGalleryImages($gallery_id: ID!, $image_ids: [ID!]!) {
removeGalleryImages(input: {gallery_id: $gallery_id, image_ids: $image_ids})
}

View File

@@ -0,0 +1,41 @@
mutation ImageUpdate(
$input: ImageUpdateInput!) {
imageUpdate(input: $input) {
...SlimImageData
}
}
mutation BulkImageUpdate(
$input: BulkImageUpdateInput!) {
bulkImageUpdate(input: $input) {
...SlimImageData
}
}
mutation ImagesUpdate($input : [ImageUpdateInput!]!) {
imagesUpdate(input: $input) {
...SlimImageData
}
}
mutation ImageIncrementO($id: ID!) {
imageIncrementO(id: $id)
}
mutation ImageDecrementO($id: ID!) {
imageDecrementO(id: $id)
}
mutation ImageResetO($id: ID!) {
imageResetO(id: $id)
}
mutation ImageDestroy($id: ID!, $delete_file: Boolean, $delete_generated : Boolean) {
imageDestroy(input: {id: $id, delete_file: $delete_file, delete_generated: $delete_generated})
}
mutation ImagesDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated : Boolean) {
imagesDestroy(input: {ids: $ids, delete_file: $delete_file, delete_generated: $delete_generated})
}

View File

@@ -0,0 +1,43 @@
mutation MetadataImport {
metadataImport
}
mutation MetadataExport {
metadataExport
}
mutation ExportObjects($input: ExportObjectsInput!) {
exportObjects(input: $input)
}
mutation ImportObjects($input: ImportObjectsInput!) {
importObjects(input: $input)
}
mutation MetadataScan($input: ScanMetadataInput!) {
metadataScan(input: $input)
}
mutation MetadataGenerate($input: GenerateMetadataInput!) {
metadataGenerate(input: $input)
}
mutation MetadataAutoTag($input: AutoTagMetadataInput!) {
metadataAutoTag(input: $input)
}
mutation MetadataClean($input: CleanMetadataInput!) {
metadataClean(input: $input)
}
mutation MigrateHashNaming {
migrateHashNaming
}
mutation StopJob {
stopJob
}
mutation BackupDatabase($input: BackupDatabaseInput!) {
backupDatabase(input: $input)
}

View File

@@ -0,0 +1,31 @@
mutation MovieCreate(
$name: String!,
$aliases: String,
$duration: Int,
$date: String,
$rating: Int,
$studio_id: ID,
$director: String,
$synopsis: String,
$url: String,
$front_image: String,
$back_image: String) {
movieCreate(input: { name: $name, aliases: $aliases, duration: $duration, date: $date, rating: $rating, studio_id: $studio_id, director: $director, synopsis: $synopsis, url: $url, front_image: $front_image, back_image: $back_image }) {
...MovieData
}
}
mutation MovieUpdate($input: MovieUpdateInput!) {
movieUpdate(input: $input) {
...MovieData
}
}
mutation MovieDestroy($id: ID!) {
movieDestroy(input: { id: $id })
}
mutation MoviesDestroy($ids: [ID!]!) {
moviesDestroy(ids: $ids)
}

View File

@@ -1,6 +1,7 @@
mutation PerformerCreate(
$name: String,
$name: String!,
$url: String,
$gender: GenderEnum,
$birthdate: String,
$ethnicity: String,
$country: String,
@@ -15,11 +16,13 @@ mutation PerformerCreate(
$twitter: String,
$instagram: String,
$favorite: Boolean,
$stash_ids: [StashIDInput!],
$image: String) {
performerCreate(input: {
name: $name,
url: $url,
gender: $gender,
birthdate: $birthdate,
ethnicity: $ethnicity,
country: $country,
@@ -34,6 +37,7 @@ mutation PerformerCreate(
twitter: $twitter,
instagram: $instagram,
favorite: $favorite,
stash_ids: $stash_ids,
image: $image
}) {
...PerformerData
@@ -41,49 +45,17 @@ mutation PerformerCreate(
}
mutation PerformerUpdate(
$id: ID!,
$name: String,
$url: String,
$birthdate: String,
$ethnicity: String,
$country: String,
$eye_color: String,
$height: String,
$measurements: String,
$fake_tits: String,
$career_length: String,
$tattoos: String,
$piercings: String,
$aliases: String,
$twitter: String,
$instagram: String,
$favorite: Boolean,
$image: String) {
$input: PerformerUpdateInput!) {
performerUpdate(input: {
id: $id,
name: $name,
url: $url,
birthdate: $birthdate,
ethnicity: $ethnicity,
country: $country,
eye_color: $eye_color,
height: $height,
measurements: $measurements,
fake_tits: $fake_tits,
career_length: $career_length,
tattoos: $tattoos,
piercings: $piercings,
aliases: $aliases,
twitter: $twitter,
instagram: $instagram,
favorite: $favorite,
image: $image
}) {
performerUpdate(input: $input) {
...PerformerData
}
}
mutation PerformerDestroy($id: ID!) {
performerDestroy(input: { id: $id })
}
}
mutation PerformersDestroy($ids: [ID!]!) {
performersDestroy(ids: $ids)
}

View File

@@ -0,0 +1,7 @@
mutation ReloadPlugins {
reloadPlugins
}
mutation RunPluginTask($plugin_id: ID!, $task_name: String!, $args: [PluginArgInput!]) {
runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)
}

View File

@@ -1,58 +1,16 @@
mutation SceneUpdate(
$id: ID!,
$title: String,
$details: String,
$url: String,
$date: String,
$rating: Int,
$studio_id: ID,
$gallery_id: ID,
$performer_ids: [ID!] = [],
$tag_ids: [ID!] = [],
$cover_image: String) {
$input: SceneUpdateInput!) {
sceneUpdate(input: {
id: $id,
title: $title,
details: $details,
url: $url,
date: $date,
rating: $rating,
studio_id: $studio_id,
gallery_id: $gallery_id,
performer_ids: $performer_ids,
tag_ids: $tag_ids,
cover_image: $cover_image
}) {
...SceneData
sceneUpdate(input: $input) {
...SceneData
}
}
mutation BulkSceneUpdate(
$ids: [ID!] = [],
$title: String,
$details: String,
$url: String,
$date: String,
$rating: Int,
$studio_id: ID,
$gallery_id: ID,
$performer_ids: [ID!],
$tag_ids: [ID!]) {
$input: BulkSceneUpdateInput!) {
bulkSceneUpdate(input: {
ids: $ids,
title: $title,
details: $details,
url: $url,
date: $date,
rating: $rating,
studio_id: $studio_id,
gallery_id: $gallery_id,
performer_ids: $performer_ids,
tag_ids: $tag_ids
}) {
...SceneData
bulkSceneUpdate(input: $input) {
...SceneData
}
}
@@ -76,4 +34,12 @@ mutation SceneResetO($id: ID!) {
mutation SceneDestroy($id: ID!, $delete_file: Boolean, $delete_generated : Boolean) {
sceneDestroy(input: {id: $id, delete_file: $delete_file, delete_generated: $delete_generated})
}
}
mutation ScenesDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated : Boolean) {
scenesDestroy(input: {ids: $ids, delete_file: $delete_file, delete_generated: $delete_generated})
}
mutation SceneGenerateScreenshot($id: ID!, $at: Float) {
sceneGenerateScreenshot(id: $id, at: $at)
}

View File

@@ -0,0 +1,3 @@
mutation ReloadScrapers {
reloadScrapers
}

View File

@@ -0,0 +1,3 @@
mutation SubmitStashBoxFingerprints($input: StashBoxFingerprintSubmissionInput!) {
submitStashBoxFingerprints(input: $input)
}

View File

@@ -1,24 +1,27 @@
mutation StudioCreate(
$name: String!,
$url: String,
$image: String) {
$image: String,
$stash_ids: [StashIDInput!],
$parent_id: ID) {
studioCreate(input: { name: $name, url: $url, image: $image }) {
studioCreate(input: { name: $name, url: $url, image: $image, stash_ids: $stash_ids, parent_id: $parent_id }) {
...StudioData
}
}
mutation StudioUpdate(
$id: ID!
$name: String,
$url: String,
$image: String) {
$input: StudioUpdateInput!) {
studioUpdate(input: { id: $id, name: $name, url: $url, image: $image }) {
studioUpdate(input: $input) {
...StudioData
}
}
mutation StudioDestroy($id: ID!) {
studioDestroy(input: { id: $id })
}
}
mutation StudiosDestroy($ids: [ID!]!) {
studiosDestroy(ids: $ids)
}

View File

@@ -1,5 +1,5 @@
mutation TagCreate($name: String!) {
tagCreate(input: { name: $name }) {
mutation TagCreate($name: String!, $image: String) {
tagCreate(input: { name: $name, image: $image }) {
...TagData
}
}
@@ -8,8 +8,12 @@ mutation TagDestroy($id: ID!) {
tagDestroy(input: { id: $id })
}
mutation TagUpdate($id: ID!, $name: String!) {
tagUpdate(input: { id: $id, name: $name }) {
mutation TagsDestroy($ids: [ID!]!) {
tagsDestroy(ids: $ids)
}
mutation TagUpdate($input: TagUpdateInput!) {
tagUpdate(input: $input) {
...TagData
}
}
}

View File

@@ -1,8 +1,8 @@
query FindGalleries($filter: FindFilterType) {
findGalleries(filter: $filter) {
query FindGalleries($filter: FindFilterType, $gallery_filter: GalleryFilterType) {
findGalleries(gallery_filter: $gallery_filter, filter: $filter) {
count
galleries {
...GalleryData
...GallerySlimData
}
}
}
@@ -11,4 +11,4 @@ query FindGallery($id: ID!) {
findGallery(id: $id) {
...GalleryData
}
}
}

View File

@@ -0,0 +1,14 @@
query FindImages($filter: FindFilterType, $image_filter: ImageFilterType, $image_ids: [Int!]) {
findImages(filter: $filter, image_filter: $image_filter, image_ids: $image_ids) {
count
images {
...SlimImageData
}
}
}
query FindImage($id: ID!, $checksum: String) {
findImage(id: $id, checksum: $checksum) {
...ImageData
}
}

View File

@@ -1,9 +1,3 @@
query FindTag($id: ID!) {
findTag(id: $id) {
...TagData
}
}
query MarkerStrings($q: String, $sort: String) {
markerStrings(q: $q, sort: $sort) {
id
@@ -19,37 +13,39 @@ query AllTags {
}
query AllPerformersForFilter {
allPerformers {
allPerformersSlim {
...SlimPerformerData
}
}
query AllStudiosForFilter {
allStudios {
allStudiosSlim {
...SlimStudioData
}
}
query AllMoviesForFilter {
allMoviesSlim {
...SlimMovieData
}
}
query AllTagsForFilter {
allTags {
allTagsSlim {
id
name
}
}
query ValidGalleriesForScene($scene_id: ID!) {
validGalleriesForScene(scene_id: $scene_id) {
id
path
}
}
query Stats {
stats {
scene_count,
scenes_size,
image_count,
images_size,
gallery_count,
performer_count,
studio_count,
movie_count,
tag_count
}
}

View File

@@ -0,0 +1,14 @@
query FindMovies($filter: FindFilterType, $movie_filter: MovieFilterType) {
findMovies(filter: $filter, movie_filter: $movie_filter) {
count
movies {
...MovieData
}
}
}
query FindMovie($id: ID!) {
findMovie(id: $id) {
...MovieData
}
}

View File

@@ -11,4 +11,4 @@ query FindPerformer($id: ID!) {
findPerformer(id: $id) {
...PerformerData
}
}
}

View File

@@ -0,0 +1,25 @@
query Plugins {
plugins {
id
name
description
url
version
tasks {
name
description
}
}
}
query PluginTasks {
pluginTasks {
name
description
plugin {
id
name
}
}
}

View File

@@ -45,9 +45,20 @@ query ParseSceneFilenames($filter: FindFilterType!, $config: SceneParserInput!)
date
rating
studio_id
gallery_id
gallery_ids
movies {
movie_id
}
performer_ids
tag_ids
}
}
}
}
query SceneStreams($id: ID!) {
sceneStreams(id: $id) {
url
mime_type
label
}
}

View File

@@ -20,6 +20,28 @@ query ListSceneScrapers {
}
}
query ListGalleryScrapers {
listGalleryScrapers {
id
name
gallery {
urls
supported_scrapes
}
}
}
query ListMovieScrapers {
listMovieScrapers {
id
name
movie {
urls
supported_scrapes
}
}
}
query ScrapePerformerList($scraper_id: ID!, $query: String!) {
scrapePerformerList(scraper_id: $scraper_id, query: $query) {
...ScrapedPerformerData
@@ -48,4 +70,28 @@ query ScrapeSceneURL($url: String!) {
scrapeSceneURL(url: $url) {
...ScrapedSceneData
}
}
}
query ScrapeGallery($scraper_id: ID!, $gallery: GalleryUpdateInput!) {
scrapeGallery(scraper_id: $scraper_id, gallery: $gallery) {
...ScrapedGalleryData
}
}
query ScrapeGalleryURL($url: String!) {
scrapeGalleryURL(url: $url) {
...ScrapedGalleryData
}
}
query ScrapeMovieURL($url: String!) {
scrapeMovieURL(url: $url) {
...ScrapedMovieData
}
}
query QueryStashBoxScene($input: StashBoxQueryInput!) {
queryStashBoxScene(input: $input) {
...ScrapedStashBoxSceneData
}
}

View File

@@ -4,6 +4,10 @@ query Configuration {
}
}
query Directories($path: String) {
directories(path: $path)
}
query Directory($path: String) {
directory(path: $path) {
path
parent
directories
}
}

View File

@@ -1,27 +1,3 @@
query MetadataImport {
metadataImport
}
query MetadataExport {
metadataExport
}
query MetadataScan($input: ScanMetadataInput!) {
metadataScan(input: $input)
}
query MetadataGenerate($input: GenerateMetadataInput!) {
metadataGenerate(input: $input)
}
query MetadataAutoTag($input: AutoTagMetadataInput!) {
metadataAutoTag(input: $input)
}
query MetadataClean {
metadataClean
}
query JobStatus {
jobStatus {
progress
@@ -29,7 +5,3 @@ query JobStatus {
message
}
}
query StopJob {
stopJob
}

View File

@@ -1,5 +1,5 @@
query FindStudios($filter: FindFilterType) {
findStudios(filter: $filter) {
query FindStudios($filter: FindFilterType, $studio_filter: StudioFilterType ) {
findStudios(filter: $filter, studio_filter: $studio_filter) {
count
studios {
...StudioData
@@ -11,4 +11,4 @@ query FindStudio($id: ID!) {
findStudio(id: $id) {
...StudioData
}
}
}

View File

@@ -0,0 +1,14 @@
query FindTags($filter: FindFilterType, $tag_filter: TagFilterType ) {
findTags(filter: $filter, tag_filter: $tag_filter) {
count
tags {
...TagData
}
}
}
query FindTag($id: ID!) {
findTag(id: $id) {
...TagData
}
}

View File

@@ -2,16 +2,26 @@
type Query {
"""Find a scene by ID or Checksum"""
findScene(id: ID, checksum: String): Scene
findSceneByHash(input: SceneHashInput!): Scene
"""A function which queries Scene objects"""
findScenes(scene_filter: SceneFilterType, scene_ids: [Int!], filter: FindFilterType): FindScenesResultType!
findScenesByPathRegex(filter: FindFilterType): FindScenesResultType!
"""Return valid stream paths"""
sceneStreams(id: ID): [SceneStreamEndpoint!]!
parseSceneFilenames(filter: FindFilterType, config: SceneParserInput!): SceneParserResultType!
"""A function which queries SceneMarker objects"""
findSceneMarkers(scene_marker_filter: SceneMarkerFilterType filter: FindFilterType): FindSceneMarkersResultType!
findImage(id: ID, checksum: String): Image
"""A function which queries Scene objects"""
findImages(image_filter: ImageFilterType, image_ids: [Int!], filter: FindFilterType): FindImagesResultType!
"""Find a performer by ID"""
findPerformer(id: ID!): Performer
"""A function which queries Performer objects"""
@@ -20,12 +30,18 @@ type Query {
"""Find a studio by ID"""
findStudio(id: ID!): Studio
"""A function which queries Studio objects"""
findStudios(filter: FindFilterType): FindStudiosResultType!
findStudios(studio_filter: StudioFilterType, filter: FindFilterType): FindStudiosResultType!
"""Find a movie by ID"""
findMovie(id: ID!): Movie
"""A function which queries Movie objects"""
findMovies(movie_filter: MovieFilterType, filter: FindFilterType): FindMoviesResultType!
findGallery(id: ID!): Gallery
findGalleries(filter: FindFilterType): FindGalleriesResultType!
findGalleries(gallery_filter: GalleryFilterType, filter: FindFilterType): FindGalleriesResultType!
findTag(id: ID!): Tag
findTags(tag_filter: TagFilterType, filter: FindFilterType): FindTagsResultType!
"""Retrieve random scene markers for the wall"""
markerWall(q: String): [SceneMarker!]!
@@ -34,8 +50,6 @@ type Query {
"""Get marker strings"""
markerStrings(q: String, sort: String): [MarkerStringsResultType]!
"""Get the list of valid galleries for a given scene ID"""
validGalleriesForScene(scene_id: ID): [Gallery!]!
"""Get stats"""
stats: StatsResultType!
"""Organize scene markers by tag for a given scene ID"""
@@ -48,6 +62,9 @@ type Query {
"""List available scrapers"""
listPerformerScrapers: [Scraper!]!
listSceneScrapers: [Scraper!]!
listGalleryScrapers: [Scraper!]!
listMovieScrapers: [Scraper!]!
"""Scrape a list of performers based on name"""
scrapePerformerList(scraper_id: ID!, query: String!): [ScrapedPerformer!]!
"""Scrapes a complete performer record based on a scrapePerformerList result"""
@@ -58,42 +75,51 @@ type Query {
scrapeScene(scraper_id: ID!, scene: SceneUpdateInput!): ScrapedScene
"""Scrapes a complete performer record based on a URL"""
scrapeSceneURL(url: String!): ScrapedScene
"""Scrapes a complete gallery record based on an existing gallery"""
scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery
"""Scrapes a complete gallery record based on a URL"""
scrapeGalleryURL(url: String!): ScrapedGallery
"""Scrapes a complete movie record based on a URL"""
scrapeMovieURL(url: String!): ScrapedMovie
"""Scrape a performer using Freeones"""
scrapeFreeones(performer_name: String!): ScrapedPerformer
"""Scrape a list of performers from a query"""
scrapeFreeonesPerformerList(query: String!): [String!]!
"""Query StashBox for scenes"""
queryStashBoxScene(input: StashBoxQueryInput!): [ScrapedScene!]!
# Plugins
"""List loaded plugins"""
plugins: [Plugin!]
"""List available plugin operations"""
pluginTasks: [PluginTask!]
# Config
"""Returns the current, complete configuration"""
configuration: ConfigResult!
"""Returns an array of paths for the given path"""
directories(path: String): [String!]!
directory(path: String): Directory!
# Metadata
"""Start an import. Returns the job ID"""
metadataImport: String!
"""Start an export. Returns the job ID"""
metadataExport: String!
"""Start a scan. Returns the job ID"""
metadataScan(input: ScanMetadataInput!): String!
"""Start generating content. Returns the job ID"""
metadataGenerate(input: GenerateMetadataInput!): String!
"""Start auto-tagging. Returns the job ID"""
metadataAutoTag(input: AutoTagMetadataInput!): String!
"""Clean metadata. Returns the job ID"""
metadataClean: String!
jobStatus: MetadataUpdateStatus!
stopJob: Boolean!
# Get everything
allPerformers: [Performer!]!
allStudios: [Studio!]!
allMovies: [Movie!]!
allTags: [Tag!]!
# Get everything with minimal metadata
allPerformersSlim: [Performer!]!
allStudiosSlim: [Studio!]!
allMoviesSlim: [Movie!]!
allTagsSlim: [Tag!]!
# Version
version: Version!
@@ -105,6 +131,7 @@ type Mutation {
sceneUpdate(input: SceneUpdateInput!): Scene
bulkSceneUpdate(input: BulkSceneUpdateInput!): [Scene!]
sceneDestroy(input: SceneDestroyInput!): Boolean!
scenesDestroy(input: ScenesDestroyInput!): Boolean!
scenesUpdate(input: [SceneUpdateInput!]!): [Scene]
"""Increments the o-counter for a scene. Returns the new value"""
@@ -114,25 +141,94 @@ type Mutation {
"""Resets the o-counter for a scene to 0. Returns the new value"""
sceneResetO(id: ID!): Int!
"""Generates screenshot at specified time in seconds. Leave empty to generate default screenshot"""
sceneGenerateScreenshot(id: ID!, at: Float): String!
sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
sceneMarkerDestroy(id: ID!): Boolean!
imageUpdate(input: ImageUpdateInput!): Image
bulkImageUpdate(input: BulkImageUpdateInput!): [Image!]
imageDestroy(input: ImageDestroyInput!): Boolean!
imagesDestroy(input: ImagesDestroyInput!): Boolean!
imagesUpdate(input: [ImageUpdateInput!]!): [Image]
"""Increments the o-counter for an image. Returns the new value"""
imageIncrementO(id: ID!): Int!
"""Decrements the o-counter for an image. Returns the new value"""
imageDecrementO(id: ID!): Int!
"""Resets the o-counter for a image to 0. Returns the new value"""
imageResetO(id: ID!): Int!
galleryCreate(input: GalleryCreateInput!): Gallery
galleryUpdate(input: GalleryUpdateInput!): Gallery
bulkGalleryUpdate(input: BulkGalleryUpdateInput!): [Gallery!]
galleryDestroy(input: GalleryDestroyInput!): Boolean!
galleriesUpdate(input: [GalleryUpdateInput!]!): [Gallery]
addGalleryImages(input: GalleryAddInput!): Boolean!
removeGalleryImages(input: GalleryRemoveInput!): Boolean!
performerCreate(input: PerformerCreateInput!): Performer
performerUpdate(input: PerformerUpdateInput!): Performer
performerDestroy(input: PerformerDestroyInput!): Boolean!
performersDestroy(ids: [ID!]!): Boolean!
studioCreate(input: StudioCreateInput!): Studio
studioUpdate(input: StudioUpdateInput!): Studio
studioDestroy(input: StudioDestroyInput!): Boolean!
studiosDestroy(ids: [ID!]!): Boolean!
movieCreate(input: MovieCreateInput!): Movie
movieUpdate(input: MovieUpdateInput!): Movie
movieDestroy(input: MovieDestroyInput!): Boolean!
moviesDestroy(ids: [ID!]!): Boolean!
tagCreate(input: TagCreateInput!): Tag
tagUpdate(input: TagUpdateInput!): Tag
tagDestroy(input: TagDestroyInput!): Boolean!
tagsDestroy(ids: [ID!]!): Boolean!
"""Change general configuration options"""
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult!
"""Returns a link to download the result"""
exportObjects(input: ExportObjectsInput!): String
"""Performs an incremental import. Returns the job ID"""
importObjects(input: ImportObjectsInput!): String!
"""Start an full import. Completely wipes the database and imports from the metadata directory. Returns the job ID"""
metadataImport: String!
"""Start a full export. Outputs to the metadata directory. Returns the job ID"""
metadataExport: String!
"""Start a scan. Returns the job ID"""
metadataScan(input: ScanMetadataInput!): String!
"""Start generating content. Returns the job ID"""
metadataGenerate(input: GenerateMetadataInput!): String!
"""Start auto-tagging. Returns the job ID"""
metadataAutoTag(input: AutoTagMetadataInput!): String!
"""Clean metadata. Returns the job ID"""
metadataClean(input: CleanMetadataInput!): String!
"""Migrate generated files for the current hash naming"""
migrateHashNaming: String!
"""Reload scrapers"""
reloadScrapers: Boolean!
"""Run plugin task. Returns the job ID"""
runPluginTask(plugin_id: ID!, task_name: String!, args: [PluginArgInput!]): String!
reloadPlugins: Boolean!
stopJob: Boolean!
"""Submit fingerprints to stash-box instance"""
submitStashBoxFingerprints(input: StashBoxFingerprintSubmissionInput!): Boolean!
"""Backup the database. Optionally returns a link to download the database file"""
backupDatabase(input: BackupDatabaseInput!): String
}
type Subscription {

View File

@@ -7,13 +7,46 @@ enum StreamingResolutionEnum {
"Original", ORIGINAL
}
enum PreviewPreset {
"X264_ULTRAFAST", ultrafast
"X264_VERYFAST", veryfast
"X264_FAST", fast
"X264_MEDIUM", medium
"X264_SLOW", slow
"X264_SLOWER", slower
"X264_VERYSLOW", veryslow
}
enum HashAlgorithm {
MD5
"oshash", OSHASH
}
input ConfigGeneralInput {
"""Array of file paths to content"""
stashes: [String!]
stashes: [StashConfigInput!]
"""Path to the SQLite database"""
databasePath: String
"""Path to generated files"""
generatedPath: String
"""Path to cache"""
cachePath: String
"""Whether to calculate MD5 checksums for scene video files"""
calculateMD5: Boolean!
"""Hash algorithm to use for generated file naming"""
videoFileNamingAlgorithm: HashAlgorithm!
"""Number of parallel tasks to start during scan/generate"""
parallelTasks: Int
"""Number of segments in a preview file"""
previewSegments: Int
"""Preview segment duration, in seconds"""
previewSegmentDuration: Float
"""Duration of start of video to exclude when generating previews"""
previewExcludeStart: String
"""Duration of end of video to exclude when generating previews"""
previewExcludeEnd: String
"""Preset when generating preview"""
previewPreset: PreviewPreset
"""Max generated transcode size"""
maxTranscodeSize: StreamingResolutionEnum
"""Max streaming transcode size"""
@@ -22,6 +55,8 @@ input ConfigGeneralInput {
username: String
"""Password"""
password: String
"""Maximum session cookie age"""
maxSessionAge: Int
"""Name of the log file"""
logFile: String
"""Whether to also output to stderr"""
@@ -30,18 +65,52 @@ input ConfigGeneralInput {
logLevel: String!
"""Whether to log http access"""
logAccess: Boolean!
"""Array of file regexp to exclude from Scan"""
"""True if galleries should be created from folders with images"""
createGalleriesFromFolders: Boolean!
"""Array of video file extensions"""
videoExtensions: [String!]
"""Array of image file extensions"""
imageExtensions: [String!]
"""Array of gallery zip file extensions"""
galleryExtensions: [String!]
"""Array of file regexp to exclude from Video Scans"""
excludes: [String!]
"""Array of file regexp to exclude from Image Scans"""
imageExcludes: [String!]
"""Scraper user agent string"""
scraperUserAgent: String
"""Scraper CDP path. Path to chrome executable or remote address"""
scraperCDPPath: String
"""Stash-box instances used for tagging"""
stashBoxes: [StashBoxInput!]!
}
type ConfigGeneralResult {
"""Array of file paths to content"""
stashes: [String!]!
stashes: [StashConfig!]!
"""Path to the SQLite database"""
databasePath: String!
"""Path to generated files"""
generatedPath: String!
"""Max generated transcode size"""
"""Path to cache"""
cachePath: String!
"""Whether to calculate MD5 checksums for scene video files"""
calculateMD5: Boolean!
"""Hash algorithm to use for generated file naming"""
videoFileNamingAlgorithm: HashAlgorithm!
"""Number of parallel tasks to start during scan/generate"""
parallelTasks: Int!
"""Number of segments in a preview file"""
previewSegments: Int!
"""Preview segment duration, in seconds"""
previewSegmentDuration: Float!
"""Duration of start of video to exclude when generating previews"""
previewExcludeStart: String!
"""Duration of end of video to exclude when generating previews"""
previewExcludeEnd: String!
"""Preset when generating preview"""
previewPreset: PreviewPreset!
"""Max generated transcode size"""
maxTranscodeSize: StreamingResolutionEnum
"""Max streaming transcode size"""
maxStreamingTranscodeSize: StreamingResolutionEnum
@@ -49,6 +118,8 @@ type ConfigGeneralResult {
username: String!
"""Password"""
password: String!
"""Maximum session cookie age"""
maxSessionAge: Int!
"""Name of the log file"""
logFile: String
"""Whether to also output to stderr"""
@@ -57,15 +128,35 @@ type ConfigGeneralResult {
logLevel: String!
"""Whether to log http access"""
logAccess: Boolean!
"""Array of file regexp to exclude from Scan"""
"""Array of video file extensions"""
videoExtensions: [String!]!
"""Array of image file extensions"""
imageExtensions: [String!]!
"""Array of gallery zip file extensions"""
galleryExtensions: [String!]!
"""True if galleries should be created from folders with images"""
createGalleriesFromFolders: Boolean!
"""Array of file regexp to exclude from Video Scans"""
excludes: [String!]!
"""Array of file regexp to exclude from Image Scans"""
imageExcludes: [String!]!
"""Scraper user agent string"""
scraperUserAgent: String
"""Scraper CDP path. Path to chrome executable or remote address"""
scraperCDPPath: String
"""Stash-box instances used for tagging"""
stashBoxes: [StashBox!]!
}
input ConfigInterfaceInput {
"""Ordered list of items that should be shown in the menu"""
menuItems: [String!]
"""Enable sound on mouseover previews"""
soundOnPreview: Boolean
"""Show title and tags in wall view"""
wallShowTitle: Boolean
"""Wall playback type"""
wallPlayback: String
"""Maximum duration (in seconds) in which a scene video will loop in the scene player"""
maximumLoopDuration: Int
"""If true, video will autostart on load in the scene player"""
@@ -75,22 +166,30 @@ input ConfigInterfaceInput {
"""Custom CSS"""
css: String
cssEnabled: Boolean
"""Interface language"""
language: String
}
type ConfigInterfaceResult {
"""Ordered list of items that should be shown in the menu"""
menuItems: [String!]
"""Enable sound on mouseover previews"""
soundOnPreview: Boolean
"""Show title and tags in wall view"""
wallShowTitle: Boolean
"""Wall playback type"""
wallPlayback: String
"""Maximum duration (in seconds) in which a scene video will loop in the scene player"""
maximumLoopDuration: Int
"""If true, video will autostart on load in the scene player"""
autostartVideo: Boolean
"""If true, studio overlays will be shown as text instead of logo images"""
"""If true, studio overlays will be shown as text instead of logo images"""
showStudioAsText: Boolean
"""Custom CSS"""
css: String
cssEnabled: Boolean
"""Interface language"""
language: String
}
"""All configuration settings"""
@@ -98,3 +197,23 @@ type ConfigResult {
general: ConfigGeneralResult!
interface: ConfigInterfaceResult!
}
"""Directory structure of a path"""
type Directory {
path: String!
parent: String
directories: [String!]!
}
"""Stash configuration details"""
input StashConfigInput {
path: String!
excludeVideo: Boolean!
excludeImage: Boolean!
}
type StashConfig {
path: String!
excludeVideo: Boolean!
excludeImage: Boolean!
}

View File

@@ -6,17 +6,26 @@ enum SortDirectionEnum {
input FindFilterType {
q: String
page: Int
"""use per_page = 0 to indicate all results. Defaults to 25."""
per_page: Int
sort: String
direction: SortDirectionEnum
}
enum ResolutionEnum {
"144p", VERY_LOW
"240p", LOW
"360p", R360P
"480p", STANDARD
"540p", WEB_HD
"720p", STANDARD_HD
"1080p", FULL_HD
"1440p", QUAD_HD
"1920p", VR_HD
"4k", FOUR_K
"5k", FIVE_K
"6k", SIX_K
"8k", EIGHT_K
}
input PerformerFilterType {
@@ -46,6 +55,12 @@ input PerformerFilterType {
piercings: StringCriterionInput
"""Filter by aliases"""
aliases: StringCriterionInput
"""Filter by gender"""
gender: GenderCriterionInput
"""Filter to only include performers missing this property"""
is_missing: String
"""Filter by StashID"""
stash_id: String
}
input SceneMarkerFilterType {
@@ -60,8 +75,12 @@ input SceneMarkerFilterType {
}
input SceneFilterType {
"""Filter by path"""
path: StringCriterionInput
"""Filter by rating"""
rating: IntCriterionInput
"""Filter by organized"""
organized: Boolean
"""Filter by o-counter"""
o_counter: IntCriterionInput
"""Filter by resolution"""
@@ -74,10 +93,87 @@ input SceneFilterType {
is_missing: String
"""Filter to only include scenes with this studio"""
studios: MultiCriterionInput
"""Filter to only include scenes with this movie"""
movies: MultiCriterionInput
"""Filter to only include scenes with these tags"""
tags: MultiCriterionInput
"""Filter to only include scenes with these performers"""
performers: MultiCriterionInput
"""Filter by StashID"""
stash_id: String
}
input MovieFilterType {
"""Filter to only include movies with this studio"""
studios: MultiCriterionInput
"""Filter to only include movies missing this property"""
is_missing: String
}
input StudioFilterType {
"""Filter to only include studios with this parent studio"""
parents: MultiCriterionInput
"""Filter by StashID"""
stash_id: String
"""Filter to only include studios missing this property"""
is_missing: String
}
input GalleryFilterType {
"""Filter by path"""
path: StringCriterionInput
"""Filter to only include galleries missing this property"""
is_missing: String
"""Filter to include/exclude galleries that were created from zip"""
is_zip: Boolean
"""Filter by rating"""
rating: IntCriterionInput
"""Filter by organized"""
organized: Boolean
"""Filter by average image resolution"""
average_resolution: ResolutionEnum
"""Filter to only include scenes with this studio"""
studios: MultiCriterionInput
"""Filter to only include scenes with these tags"""
tags: MultiCriterionInput
"""Filter to only include scenes with these performers"""
performers: MultiCriterionInput
"""Filter by number of images in this gallery"""
image_count: IntCriterionInput
}
input TagFilterType {
"""Filter to only include tags missing this property"""
is_missing: String
"""Filter by number of scenes with this tag"""
scene_count: IntCriterionInput
"""Filter by number of markers with this tag"""
marker_count: IntCriterionInput
}
input ImageFilterType {
"""Filter by path"""
path: StringCriterionInput
"""Filter by rating"""
rating: IntCriterionInput
"""Filter by organized"""
organized: Boolean
"""Filter by o-counter"""
o_counter: IntCriterionInput
"""Filter by resolution"""
resolution: ResolutionEnum
"""Filter to only include images missing this property"""
is_missing: String
"""Filter to only include images with this studio"""
studios: MultiCriterionInput
"""Filter to only include images with these tags"""
tags: MultiCriterionInput
"""Filter to only include images with these performers"""
performers: MultiCriterionInput
"""Filter to only include images with these galleries"""
galleries: MultiCriterionInput
}
enum CriterionModifier {
@@ -97,6 +193,10 @@ enum CriterionModifier {
INCLUDES_ALL,
INCLUDES,
EXCLUDES,
"""MATCHES REGEX"""
MATCHES_REGEX,
"""NOT MATCHES REGEX"""
NOT_MATCHES_REGEX,
}
input StringCriterionInput {
@@ -112,4 +212,9 @@ input IntCriterionInput {
input MultiCriterionInput {
value: [ID!]
modifier: CriterionModifier!
}
}
input GenderCriterionInput {
value: GenderEnum
modifier: CriterionModifier!
}

View File

@@ -2,11 +2,22 @@
type Gallery {
id: ID!
checksum: String!
path: String!
path: String
title: String
url: String
date: String
details: String
rating: Int
organized: Boolean!
scenes: [Scene!]!
studio: Studio
image_count: Int!
tags: [Tag!]!
performers: [Performer!]!
"""The files in the gallery"""
files: [GalleryFilesType!]! # Resolver
"""The images in the gallery"""
images: [Image!]! # Resolver
cover: Image
}
type GalleryFilesType {
@@ -15,7 +26,65 @@ type GalleryFilesType {
path: String
}
input GalleryCreateInput {
title: String!
url: String
date: String
details: String
rating: Int
organized: Boolean
scene_ids: [ID!]
studio_id: ID
tag_ids: [ID!]
performer_ids: [ID!]
}
input GalleryUpdateInput {
clientMutationId: String
id: ID!
title: String
url: String
date: String
details: String
rating: Int
organized: Boolean
scene_ids: [ID!]
studio_id: ID
tag_ids: [ID!]
performer_ids: [ID!]
}
input BulkGalleryUpdateInput {
clientMutationId: String
ids: [ID!]
url: String
date: String
details: String
rating: Int
organized: Boolean
scene_ids: BulkUpdateIds
studio_id: ID
tag_ids: BulkUpdateIds
performer_ids: BulkUpdateIds
}
input GalleryDestroyInput {
ids: [ID!]!
delete_file: Boolean
delete_generated: Boolean
}
type FindGalleriesResultType {
count: Int!
galleries: [Gallery!]!
}
}
input GalleryAddInput {
gallery_id: ID!
image_ids: [ID!]!
}
input GalleryRemoveInput {
gallery_id: ID!
image_ids: [ID!]!
}

View File

@@ -0,0 +1,71 @@
type Image {
id: ID!
checksum: String
title: String
rating: Int
o_counter: Int
organized: Boolean!
path: String!
file: ImageFileType! # Resolver
paths: ImagePathsType! # Resolver
galleries: [Gallery!]!
studio: Studio
tags: [Tag!]!
performers: [Performer!]!
}
type ImageFileType {
size: Int
width: Int
height: Int
}
type ImagePathsType {
thumbnail: String # Resolver
image: String # Resolver
}
input ImageUpdateInput {
clientMutationId: String
id: ID!
title: String
rating: Int
organized: Boolean
studio_id: ID
performer_ids: [ID!]
tag_ids: [ID!]
gallery_ids: [ID!]
}
input BulkImageUpdateInput {
clientMutationId: String
ids: [ID!]
title: String
rating: Int
organized: Boolean
studio_id: ID
performer_ids: BulkUpdateIds
tag_ids: BulkUpdateIds
gallery_ids: BulkUpdateIds
}
input ImageDestroyInput {
id: ID!
delete_file: Boolean
delete_generated: Boolean
}
input ImagesDestroyInput {
ids: [ID!]!
delete_file: Boolean
delete_generated: Boolean
}
type FindImagesResultType {
count: Int!
images: [Image!]!
}

View File

@@ -1,15 +1,57 @@
scalar Upload
input GenerateMetadataInput {
sprites: Boolean!
previews: Boolean!
imagePreviews: Boolean!
previewOptions: GeneratePreviewOptionsInput
markers: Boolean!
transcodes: Boolean!
"""scene ids to generate for"""
sceneIDs: [ID!]
"""marker ids to generate for"""
markerIDs: [ID!]
"""overwrite existing media"""
overwrite: Boolean
}
input GeneratePreviewOptionsInput {
"""Number of segments in a preview file"""
previewSegments: Int
"""Preview segment duration, in seconds"""
previewSegmentDuration: Float
"""Duration of start of video to exclude when generating previews"""
previewExcludeStart: String
"""Duration of end of video to exclude when generating previews"""
previewExcludeEnd: String
"""Preset when generating preview"""
previewPreset: PreviewPreset
}
input ScanMetadataInput {
paths: [String!]
"""Set name, date, details from metadata (if present)"""
useFileMetadata: Boolean!
"""Strip file extension from title"""
stripFileExtension: Boolean!
"""Generate previews during scan"""
scanGeneratePreviews: Boolean!
"""Generate image previews during scan"""
scanGenerateImagePreviews: Boolean!
"""Generate sprites during scan"""
scanGenerateSprites: Boolean!
}
input CleanMetadataInput {
"""Do a dry run. Don't delete any files"""
dryRun: Boolean!
}
input AutoTagMetadataInput {
"""Paths to tag, null for all files"""
paths: [String!]
"""IDs of performers to tag files with, or "*" for all"""
performers: [String!]
"""IDs of studios to tag files with, or "*" for all"""
@@ -20,6 +62,44 @@ input AutoTagMetadataInput {
type MetadataUpdateStatus {
progress: Float!
status: String!
status: String!
message: String!
}
}
input ExportObjectTypeInput {
ids: [String!]
all: Boolean
}
input ExportObjectsInput {
scenes: ExportObjectTypeInput
images: ExportObjectTypeInput
studios: ExportObjectTypeInput
performers: ExportObjectTypeInput
tags: ExportObjectTypeInput
movies: ExportObjectTypeInput
galleries: ExportObjectTypeInput
includeDependencies: Boolean
}
enum ImportDuplicateEnum {
IGNORE
OVERWRITE
FAIL
}
enum ImportMissingRefEnum {
IGNORE
FAIL
CREATE
}
input ImportObjectsInput {
file: Upload!
duplicateBehaviour: ImportDuplicateEnum!
missingRefBehaviour: ImportMissingRefEnum!
}
input BackupDatabaseInput {
download: Boolean
}

View File

@@ -0,0 +1,59 @@
type Movie {
id: ID!
checksum: String!
name: String!
aliases: String
"""Duration in seconds"""
duration: Int
date: String
rating: Int
studio: Studio
director: String
synopsis: String
url: String
front_image_path: String # Resolver
back_image_path: String # Resolver
scene_count: Int # Resolver
}
input MovieCreateInput {
name: String!
aliases: String
"""Duration in seconds"""
duration: Int
date: String
rating: Int
studio_id: ID
director: String
synopsis: String
url: String
"""This should be base64 encoded"""
front_image: String
back_image: String
}
input MovieUpdateInput {
id: ID!
name: String
aliases: String
duration: Int
date: String
rating: Int
studio_id: ID
director: String
synopsis: String
url: String
"""This should be base64 encoded"""
front_image: String
back_image: String
}
input MovieDestroyInput {
id: ID!
}
type FindMoviesResultType {
count: Int!
movies: [Movie!]!
}

View File

@@ -1,8 +1,18 @@
enum GenderEnum {
MALE
FEMALE
TRANSGENDER_MALE
TRANSGENDER_FEMALE
INTERSEX
NON_BINARY
}
type Performer {
id: ID!
checksum: String!
name: String
url: String
gender: GenderEnum
twitter: String
instagram: String
birthdate: String
@@ -21,11 +31,13 @@ type Performer {
image_path: String # Resolver
scene_count: Int # Resolver
scenes: [Scene!]!
stash_ids: [StashID!]!
}
input PerformerCreateInput {
name: String
name: String!
url: String
gender: GenderEnum
birthdate: String
ethnicity: String
country: String
@@ -42,12 +54,14 @@ input PerformerCreateInput {
favorite: Boolean
"""This should be base64 encoded"""
image: String
stash_ids: [StashIDInput!]
}
input PerformerUpdateInput {
id: ID!
name: String
url: String
gender: GenderEnum
birthdate: String
ethnicity: String
country: String
@@ -64,6 +78,7 @@ input PerformerUpdateInput {
favorite: Boolean
"""This should be base64 encoded"""
image: String
stash_ids: [StashIDInput!]
}
input PerformerDestroyInput {
@@ -73,4 +88,4 @@ input PerformerDestroyInput {
type FindPerformersResultType {
count: Int!
performers: [Performer!]!
}
}

View File

@@ -0,0 +1,35 @@
type Plugin {
id: ID!
name: String!
description: String
url: String
version: String
tasks: [PluginTask!]
}
type PluginTask {
name: String!
description: String
plugin: Plugin!
}
type PluginResult {
error: String
result: String
}
input PluginArgInput {
key: String!
value: PluginValueInput
}
input PluginValueInput {
str: String
i: Int
b: Boolean
f: Float
o: [PluginArgInput!]
a: [PluginValueInput!]
}

View File

@@ -18,26 +18,39 @@ type ScenePathsType {
chapters_vtt: String # Resolver
}
type SceneMovie {
movie: Movie!
scene_index: Int
}
type Scene {
id: ID!
checksum: String!
checksum: String
oshash: String
title: String
details: String
url: String
date: String
rating: Int
organized: Boolean!
o_counter: Int
path: String!
file: SceneFileType! # Resolver
paths: ScenePathsType! # Resolver
is_streamable: Boolean! # Resolver
scene_markers: [SceneMarker!]!
gallery: Gallery
galleries: [Gallery!]!
studio: Studio
movies: [SceneMovie!]!
tags: [Tag!]!
performers: [Performer!]!
stash_ids: [StashID!]!
}
input SceneMovieInput {
movie_id: ID!
scene_index: Int
}
input SceneUpdateInput {
@@ -48,12 +61,26 @@ input SceneUpdateInput {
url: String
date: String
rating: Int
organized: Boolean
studio_id: ID
gallery_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
movies: [SceneMovieInput!]
tag_ids: [ID!]
"""This should be base64 encoded"""
cover_image: String
stash_ids: [StashIDInput!]
}
enum BulkUpdateIdMode {
SET
ADD
REMOVE
}
input BulkUpdateIds {
ids: [ID!]
mode: BulkUpdateIdMode!
}
input BulkSceneUpdateInput {
@@ -64,10 +91,11 @@ input BulkSceneUpdateInput {
url: String
date: String
rating: Int
organized: Boolean
studio_id: ID
gallery_id: ID
performer_ids: [ID!]
tag_ids: [ID!]
gallery_ids: BulkUpdateIds
performer_ids: BulkUpdateIds
tag_ids: BulkUpdateIds
}
input SceneDestroyInput {
@@ -76,6 +104,12 @@ input SceneDestroyInput {
delete_generated: Boolean
}
input ScenesDestroyInput {
ids: [ID!]!
delete_file: Boolean
delete_generated: Boolean
}
type FindScenesResultType {
count: Int!
scenes: [Scene!]!
@@ -87,6 +121,11 @@ input SceneParserInput {
capitalizeTitle: Boolean
}
type SceneMovieID {
movie_id: ID!
scene_index: String
}
type SceneParserResult {
scene: Scene!
title: String
@@ -95,12 +134,24 @@ type SceneParserResult {
date: String
rating: Int
studio_id: ID
gallery_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
movies: [SceneMovieID!]
tag_ids: [ID!]
}
type SceneParserResultType {
count: Int!
results: [SceneParserResult!]!
}
}
input SceneHashInput {
checksum: String
oshash: String
}
type SceneStreamEndpoint {
url: String!
mime_type: String
label: String
}

View File

@@ -0,0 +1,34 @@
type ScrapedMovieStudio {
"""Set if studio matched"""
id: ID
name: String!
url: String
}
"""A movie from a scraping operation..."""
type ScrapedMovie {
name: String
aliases: String
duration: String
date: String
rating: String
director: String
url: String
synopsis: String
studio: ScrapedMovieStudio
"""This should be base64 encoded"""
front_image: String
back_image: String
}
input ScrapedMovieInput {
name: String
aliases: String
duration: String
date: String
rating: String
director: String
url: String
synopsis: String
}

View File

@@ -1,6 +1,7 @@
"""A performer from a scraping operation..."""
type ScrapedPerformer {
name: String
gender: String
url: String
twitter: String
instagram: String
@@ -15,10 +16,14 @@ type ScrapedPerformer {
tattoos: String
piercings: String
aliases: String
"""This should be base64 encoded"""
image: String
}
input ScrapedPerformerInput {
name: String
gender: String
url: String
twitter: String
instagram: String
@@ -33,4 +38,6 @@ input ScrapedPerformerInput {
tattoos: String
piercings: String
aliases: String
# not including image for the input
}

View File

@@ -20,13 +20,17 @@ type Scraper {
performer: ScraperSpec
"""Details for scene scraper"""
scene: ScraperSpec
"""Details for gallery scraper"""
gallery: ScraperSpec
"""Details for movie scraper"""
movie: ScraperSpec
}
type ScrapedScenePerformer {
"""Set if performer matched"""
id: ID
stored_id: ID
name: String!
gender: String
url: String
twitter: String
instagram: String
@@ -41,18 +45,36 @@ type ScrapedScenePerformer {
tattoos: String
piercings: String
aliases: String
remote_site_id: String
images: [String!]
}
type ScrapedSceneMovie {
"""Set if movie matched"""
stored_id: ID
name: String!
aliases: String
duration: String
date: String
rating: String
director: String
synopsis: String
url: String
}
type ScrapedSceneStudio {
"""Set if studio matched"""
id: ID
stored_id: ID
name: String!
url: String
remote_site_id: String
}
type ScrapedSceneTag {
"""Set if tag matched"""
id: ID
stored_id: ID
name: String!
}
@@ -62,9 +84,43 @@ type ScrapedScene {
url: String
date: String
"""This should be base64 encoded"""
image: String
file: SceneFileType # Resolver
studio: ScrapedSceneStudio
tags: [ScrapedSceneTag!]
performers: [ScrapedScenePerformer!]
movies: [ScrapedSceneMovie!]
remote_site_id: String
duration: Int
fingerprints: [StashBoxFingerprint!]
}
type ScrapedGallery {
title: String
details: String
url: String
date: String
studio: ScrapedSceneStudio
tags: [ScrapedSceneTag!]
performers: [ScrapedScenePerformer!]
}
input StashBoxQueryInput {
"""Index of the configured stash-box instance to use"""
stash_box_index: Int!
"""Instructs query by scene fingerprints"""
scene_ids: [ID!]
"""Query by query string"""
q: String
}
type StashBoxFingerprint {
algorithm: String!
hash: String!
duration: Int!
}

View File

@@ -0,0 +1,26 @@
type StashBox {
endpoint: String!
api_key: String!
name: String!
}
input StashBoxInput {
endpoint: String!
api_key: String!
name: String!
}
type StashID {
endpoint: String!
stash_id: String!
}
input StashIDInput {
endpoint: String!
stash_id: String!
}
input StashBoxFingerprintSubmissionInput {
scene_ids: [String!]!
stash_box_index: Int!
}

View File

@@ -1,7 +1,11 @@
type StatsResultType {
scene_count: Int!
scenes_size: Float!
image_count: Int!
images_size: Float!
gallery_count: Int!
performer_count: Int!
studio_count: Int!
movie_count: Int!
tag_count: Int!
}
}

View File

@@ -3,24 +3,31 @@ type Studio {
checksum: String!
name: String!
url: String
parent_studio: Studio
child_studios: [Studio!]!
image_path: String # Resolver
scene_count: Int # Resolver
stash_ids: [StashID!]!
}
input StudioCreateInput {
name: String!
url: String
parent_id: ID
"""This should be base64 encoded"""
image: String
stash_ids: [StashIDInput!]
}
input StudioUpdateInput {
id: ID!
name: String
url: String
parent_id: ID,
"""This should be base64 encoded"""
image: String
stash_ids: [StashIDInput!]
}
input StudioDestroyInput {
@@ -30,4 +37,4 @@ input StudioDestroyInput {
type FindStudiosResultType {
count: Int!
studios: [Studio!]!
}
}

View File

@@ -2,19 +2,31 @@ type Tag {
id: ID!
name: String!
image_path: String # Resolver
scene_count: Int # Resolver
scene_marker_count: Int # Resolver
}
input TagCreateInput {
name: String!
"""This should be base64 encoded"""
image: String
}
input TagUpdateInput {
id: ID!
name: String!
"""This should be base64 encoded"""
image: String
}
input TagDestroyInput {
id: ID!
}
type FindTagsResultType {
count: Int!
tags: [Tag!]!
}

View File

@@ -0,0 +1,139 @@
fragment URLFragment on URL {
url
type
}
fragment ImageFragment on Image {
id
url
width
height
}
fragment StudioFragment on Studio {
name
id
urls {
...URLFragment
}
images {
...ImageFragment
}
}
fragment TagFragment on Tag {
name
id
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
waist
hip
}
fragment BodyModificationFragment on BodyModification {
location
description
}
fragment PerformerFragment on Performer {
id
name
disambiguation
aliases
gender
urls {
...URLFragment
}
images {
...ImageFragment
}
birthdate {
...FuzzyDateFragment
}
ethnicity
country
eye_color
hair_color
height
measurements {
...MeasurementsFragment
}
breast_type
career_start_year
career_end_year
tattoos {
...BodyModificationFragment
}
piercings {
...BodyModificationFragment
}
}
fragment PerformerAppearanceFragment on PerformerAppearance {
as
performer {
...PerformerFragment
}
}
fragment FingerprintFragment on Fingerprint {
algorithm
hash
duration
}
fragment SceneFragment on Scene {
id
title
details
duration
date
urls {
...URLFragment
}
images {
...ImageFragment
}
studio {
...StudioFragment
}
tags {
...TagFragment
}
performers {
...PerformerAppearanceFragment
}
fingerprints {
...FingerprintFragment
}
}
query FindSceneByFingerprint($fingerprint: FingerprintQueryInput!) {
findSceneByFingerprint(fingerprint: $fingerprint) {
...SceneFragment
}
}
query FindScenesByFingerprints($fingerprints: [String!]!) {
findScenesByFingerprints(fingerprints: $fingerprints) {
...SceneFragment
}
}
query SearchScene($term: String!) {
searchScene(term: $term) {
...SceneFragment
}
}
mutation SubmitFingerprint($input: FingerprintSubmission!) {
submitFingerprint(input: $input)
}

View File

@@ -13,7 +13,12 @@ import (
func main() {
manager.Initialize()
database.Initialize(config.GetDatabasePath())
// perform the post-migration for new databases
if database.Initialize(config.GetDatabasePath()) {
manager.GetInstance().PostMigrate()
}
api.Start()
blockForever()
}

View File

@@ -0,0 +1,141 @@
package api
import (
"context"
"database/sql"
"strconv"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/models"
)
const updateInputField = "input"
func getArgumentMap(ctx context.Context) map[string]interface{} {
rctx := graphql.GetResolverContext(ctx)
reqCtx := graphql.GetRequestContext(ctx)
return rctx.Field.ArgumentMap(reqCtx.Variables)
}
func getUpdateInputMap(ctx context.Context) map[string]interface{} {
args := getArgumentMap(ctx)
input, _ := args[updateInputField]
var ret map[string]interface{}
if input != nil {
ret, _ = input.(map[string]interface{})
}
if ret == nil {
ret = make(map[string]interface{})
}
return ret
}
func getUpdateInputMaps(ctx context.Context) []map[string]interface{} {
args := getArgumentMap(ctx)
input, _ := args[updateInputField]
var ret []map[string]interface{}
if input != nil {
// convert []interface{} into []map[string]interface{}
iSlice, _ := input.([]interface{})
for _, i := range iSlice {
m, _ := i.(map[string]interface{})
if m != nil {
ret = append(ret, m)
}
}
}
return ret
}
type changesetTranslator struct {
inputMap map[string]interface{}
}
func (t changesetTranslator) hasField(field string) bool {
if t.inputMap == nil {
return false
}
_, found := t.inputMap[field]
return found
}
func (t changesetTranslator) nullString(value *string, field string) *sql.NullString {
if !t.hasField(field) {
return nil
}
ret := &sql.NullString{}
if value != nil {
ret.String = *value
ret.Valid = true
}
return ret
}
func (t changesetTranslator) sqliteDate(value *string, field string) *models.SQLiteDate {
if !t.hasField(field) {
return nil
}
ret := &models.SQLiteDate{}
if value != nil {
ret.String = *value
ret.Valid = true
}
return ret
}
func (t changesetTranslator) nullInt64(value *int, field string) *sql.NullInt64 {
if !t.hasField(field) {
return nil
}
ret := &sql.NullInt64{}
if value != nil {
ret.Int64 = int64(*value)
ret.Valid = true
}
return ret
}
func (t changesetTranslator) nullInt64FromString(value *string, field string) *sql.NullInt64 {
if !t.hasField(field) {
return nil
}
ret := &sql.NullInt64{}
if value != nil {
ret.Int64, _ = strconv.ParseInt(*value, 10, 64)
ret.Valid = true
}
return ret
}
func (t changesetTranslator) nullBool(value *bool, field string) *sql.NullBool {
if !t.hasField(field) {
return nil
}
ret := &sql.NullBool{}
if value != nil {
ret.Bool = *value
ret.Valid = true
}
return ret
}

View File

@@ -2,6 +2,7 @@ package api
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@@ -14,8 +15,14 @@ import (
//we use the github REST V3 API as no login is required
const apiReleases string = "https://api.github.com/repos/stashapp/stash/releases"
const apiTags string = "https://api.github.com/repos/stashapp/stash/tags"
const apiAcceptHeader string = "application/vnd.github.v3+json"
const developmentTag string = "latest_develop"
const defaultSHLength int = 7 // default length of SHA short hash returned by <git rev-parse --short HEAD>
// ErrNoVersion indicates that no version information has been embedded in the
// stash binary
var ErrNoVersion = errors.New("no stash version")
var stashReleases = func() map[string]string {
return map[string]string{
@@ -84,6 +91,50 @@ type githubAsset struct {
Browser_download_url string
}
type githubTagResponse struct {
Name string
Zipball_url string
Tarball_url string
Commit struct {
Sha string
Url string
}
Node_id string
}
func makeGithubRequest(url string, output interface{}) error {
client := &http.Client{
Timeout: 3 * time.Second,
}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Accept", apiAcceptHeader) // gh api recommendation , send header with api version
response, err := client.Do(req)
if err != nil {
return fmt.Errorf("Github API request failed: %s", err)
}
if response.StatusCode != http.StatusOK {
return fmt.Errorf("Github API request failed: %s", response.Status)
}
defer response.Body.Close()
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("Github API read response failed: %s", err)
}
err = json.Unmarshal(data, output)
if err != nil {
return fmt.Errorf("Unmarshalling Github API response failed: %s", err)
}
return nil
}
// GetLatestVersion gets latest version (git commit hash) from github API
// If running a build from the "master" branch, then the latest full release
// is used, otherwise it uses the release that is tagged with "latest_develop"
@@ -95,7 +146,7 @@ func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease strin
version, _, _ := GetVersion()
if version == "" {
return "", "", fmt.Errorf("Stash doesn't have a version. Version check not supported.")
return "", "", ErrNoVersion
}
// if the version is suffixed with -x-xxxx, then we are running a development build
@@ -105,10 +156,6 @@ func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease strin
usePreRelease = true
}
client := &http.Client{
Timeout: 3 * time.Second,
}
url := apiReleases
if !usePreRelease {
// just get the latest full release
@@ -117,38 +164,17 @@ func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease strin
// get the release tagged with the development tag
url += "/tags/" + developmentTag
}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Accept", apiAcceptHeader) // gh api recommendation , send header with api version
response, err := client.Do(req)
release := githubReleasesResponse{}
err = makeGithubRequest(url, &release)
if err != nil {
return "", "", fmt.Errorf("Github API request failed: %s", err)
}
if response.StatusCode != http.StatusOK {
return "", "", fmt.Errorf("Github API request failed: %s", response.Status)
}
defer response.Body.Close()
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return "", "", fmt.Errorf("Github API read response failed: %s", err)
}
err = json.Unmarshal(data, &release)
if err != nil {
return "", "", fmt.Errorf("Unmarshalling Github API response failed: %s", err)
return "", "", err
}
if release.Prerelease == usePreRelease {
if shortHash {
latestVersion = release.Target_commitish[0:7] //shorthash is first 7 digits of git commit hash
} else {
latestVersion = release.Target_commitish
}
latestVersion = getReleaseHash(release, shortHash, usePreRelease)
if wantedRelease != "" {
for _, asset := range release.Assets {
if asset.Name == wantedRelease {
@@ -165,6 +191,26 @@ func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease strin
return latestVersion, latestRelease, nil
}
func getReleaseHash(release githubReleasesResponse, shortHash bool, usePreRelease bool) string {
shaLength := len(release.Target_commitish)
// the /latest API call doesn't return the hash in target_commitish
// also add sanity check in case Target_commitish is not 40 characters
if !usePreRelease || shaLength != 40 {
return getShaFromTags(shortHash, release.Tag_name)
}
if shortHash {
last := defaultSHLength // default length of git short hash
_, gitShort, _ := GetVersion() // retrieve it to check actual length
if len(gitShort) > last && len(gitShort) < shaLength { // sometimes short hash is longer
last = len(gitShort)
}
return release.Target_commitish[0:last]
}
return release.Target_commitish
}
func printLatestVersion() {
_, githash, _ = GetVersion()
latest, _, err := GetLatestVersion(true)
@@ -178,3 +224,37 @@ func printLatestVersion() {
}
}
}
// get sha from the github api tags endpoint
// returns the sha1 hash/shorthash or "" if something's wrong
func getShaFromTags(shortHash bool, name string) string {
url := apiTags
tags := []githubTagResponse{}
err := makeGithubRequest(url, &tags)
if err != nil {
logger.Errorf("Github Tags Api %v", err)
return ""
}
_, gitShort, _ := GetVersion() // retrieve short hash to check actual length
for _, tag := range tags {
if tag.Name == name {
shaLength := len(tag.Commit.Sha)
if shaLength != 40 {
return ""
}
if shortHash {
last := defaultSHLength // default length of git short hash
if len(gitShort) > last && len(gitShort) < shaLength { // sometimes short hash is longer
last = len(gitShort)
}
return tag.Commit.Sha[0:last]
}
return tag.Commit.Sha
}
}
return ""
}

View File

@@ -9,4 +9,9 @@ const (
performerKey key = 1
sceneKey key = 2
studioKey key = 3
movieKey key = 4
ContextUser key = 5
tagKey key = 6
downloadKey key = 7
imageKey key = 8
)

View File

@@ -2,18 +2,48 @@ package api
import (
"math/rand"
"strings"
"github.com/gobuffalo/packr/v2"
"github.com/stashapp/stash/pkg/utils"
)
var performerBox *packr.Box
var performerBoxMale *packr.Box
func initialiseImages() {
performerBox = packr.New("Performer Box", "../../static/performer")
performerBoxMale = packr.New("Male Performer Box", "../../static/performer_male")
}
func getRandomPerformerImage() ([]byte, error) {
imageFiles := performerBox.List()
func getRandomPerformerImage(gender string) ([]byte, error) {
var box *packr.Box
switch strings.ToUpper(gender) {
case "FEMALE":
box = performerBox
case "MALE":
box = performerBoxMale
default:
box = performerBox
}
imageFiles := box.List()
index := rand.Intn(len(imageFiles))
return performerBox.Find(imageFiles[index])
return box.Find(imageFiles[index])
}
func getRandomPerformerImageUsingName(name, gender string) ([]byte, error) {
var box *packr.Box
switch strings.ToUpper(gender) {
case "FEMALE":
box = performerBox
case "MALE":
box = performerBoxMale
default:
box = performerBox
}
imageFiles := box.List()
index := utils.IntFromString(name) % uint64(len(imageFiles))
return box.Find(imageFiles[index])
}

96
pkg/api/migrate.go Normal file
View File

@@ -0,0 +1,96 @@
package api
import (
"fmt"
"html/template"
"net/http"
"os"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager"
)
type migrateData struct {
ExistingVersion uint
MigrateVersion uint
BackupPath string
}
func getMigrateData() migrateData {
return migrateData{
ExistingVersion: database.Version(),
MigrateVersion: database.AppSchemaVersion(),
BackupPath: database.DatabaseBackupPath(),
}
}
func getMigrateHandler(w http.ResponseWriter, r *http.Request) {
if !database.NeedsMigration() {
http.Redirect(w, r, "/", 301)
return
}
data, _ := setupUIBox.Find("migrate.html")
templ, err := template.New("Migrate").Parse(string(data))
if err != nil {
http.Error(w, fmt.Sprintf("error: %s", err), 500)
return
}
err = templ.Execute(w, getMigrateData())
if err != nil {
http.Error(w, fmt.Sprintf("error: %s", err), 500)
}
}
func doMigrateHandler(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
http.Error(w, fmt.Sprintf("error: %s", err), 500)
}
formBackupPath := r.Form.Get("backuppath")
// always backup so that we can roll back to the previous version if
// migration fails
backupPath := formBackupPath
if formBackupPath == "" {
backupPath = database.DatabaseBackupPath()
}
// perform database backup
if err = database.Backup(database.DB, backupPath); err != nil {
http.Error(w, fmt.Sprintf("error backing up database: %s", err), 500)
return
}
err = database.RunMigrations()
if err != nil {
errStr := fmt.Sprintf("error performing migration: %s", err)
// roll back to the backed up version
restoreErr := database.RestoreFromBackup(backupPath)
if restoreErr != nil {
errStr = fmt.Sprintf("ERROR: unable to restore database from backup after migration failure: %s\n%s", restoreErr.Error(), errStr)
} else {
errStr = "An error occurred migrating the database to the latest schema version. The backup database file was automatically renamed to restore the database.\n" + errStr
}
http.Error(w, errStr, 500)
return
}
// perform post-migration operations
manager.GetInstance().PostMigrate()
// if no backup path was provided, then delete the created backup
if formBackupPath == "" {
err = os.Remove(backupPath)
if err != nil {
logger.Warnf("error removing unwanted database backup (%s): %s", backupPath, err.Error())
}
}
http.Redirect(w, r, "/", 301)
}

View File

@@ -5,12 +5,13 @@ import (
"sort"
"strconv"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)
type Resolver struct{}
type Resolver struct {
txnManager models.TransactionManager
}
func (r *Resolver) Gallery() models.GalleryResolver {
return &galleryResolver{r}
@@ -27,12 +28,18 @@ func (r *Resolver) Query() models.QueryResolver {
func (r *Resolver) Scene() models.SceneResolver {
return &sceneResolver{r}
}
func (r *Resolver) Image() models.ImageResolver {
return &imageResolver{r}
}
func (r *Resolver) SceneMarker() models.SceneMarkerResolver {
return &sceneMarkerResolver{r}
}
func (r *Resolver) Studio() models.StudioResolver {
return &studioResolver{r}
}
func (r *Resolver) Movie() models.MovieResolver {
return &movieResolver{r}
}
func (r *Resolver) Subscription() models.SubscriptionResolver {
return &subscriptionResolver{r}
}
@@ -40,6 +47,22 @@ func (r *Resolver) Tag() models.TagResolver {
return &tagResolver{r}
}
func (r *Resolver) ScrapedSceneTag() models.ScrapedSceneTagResolver {
return &scrapedSceneTagResolver{r}
}
func (r *Resolver) ScrapedSceneMovie() models.ScrapedSceneMovieResolver {
return &scrapedSceneMovieResolver{r}
}
func (r *Resolver) ScrapedScenePerformer() models.ScrapedScenePerformerResolver {
return &scrapedScenePerformerResolver{r}
}
func (r *Resolver) ScrapedSceneStudio() models.ScrapedSceneStudioResolver {
return &scrapedSceneStudioResolver{r}
}
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type subscriptionResolver struct{ *Resolver }
@@ -48,62 +71,93 @@ type galleryResolver struct{ *Resolver }
type performerResolver struct{ *Resolver }
type sceneResolver struct{ *Resolver }
type sceneMarkerResolver struct{ *Resolver }
type imageResolver struct{ *Resolver }
type studioResolver struct{ *Resolver }
type movieResolver struct{ *Resolver }
type tagResolver struct{ *Resolver }
type scrapedSceneTagResolver struct{ *Resolver }
type scrapedSceneMovieResolver struct{ *Resolver }
type scrapedScenePerformerResolver struct{ *Resolver }
type scrapedSceneStudioResolver struct{ *Resolver }
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) ([]*models.SceneMarker, error) {
qb := models.NewSceneMarkerQueryBuilder()
return qb.Wall(q)
func (r *Resolver) withTxn(ctx context.Context, fn func(r models.Repository) error) error {
return r.txnManager.WithTxn(ctx, fn)
}
func (r *queryResolver) SceneWall(ctx context.Context, q *string) ([]*models.Scene, error) {
qb := models.NewSceneQueryBuilder()
return qb.Wall(q)
func (r *Resolver) withReadTxn(ctx context.Context, fn func(r models.ReaderRepository) error) error {
return r.txnManager.WithReadTxn(ctx, fn)
}
func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *string) ([]*models.MarkerStringsResultType, error) {
qb := models.NewSceneMarkerQueryBuilder()
return qb.GetMarkerStrings(q, sort)
}
func (r *queryResolver) ValidGalleriesForScene(ctx context.Context, scene_id *string) ([]*models.Gallery, error) {
if scene_id == nil {
panic("nil scene id") // TODO make scene_id mandatory
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*models.SceneMarker, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.SceneMarker().Wall(q)
return err
}); err != nil {
return nil, err
}
sceneID, _ := strconv.Atoi(*scene_id)
sqb := models.NewSceneQueryBuilder()
scene, err := sqb.Find(sceneID)
if err != nil {
return ret, nil
}
func (r *queryResolver) SceneWall(ctx context.Context, q *string) (ret []*models.Scene, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Scene().Wall(q)
return err
}); err != nil {
return nil, err
}
qb := models.NewGalleryQueryBuilder()
validGalleries, err := qb.ValidGalleriesForScenePath(scene.Path)
sceneGallery, _ := qb.FindBySceneID(sceneID, nil)
if sceneGallery != nil {
validGalleries = append(validGalleries, sceneGallery)
return ret, nil
}
func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *string) (ret []*models.MarkerStringsResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.SceneMarker().GetMarkerStrings(q, sort)
return err
}); err != nil {
return nil, err
}
return validGalleries, nil
return ret, nil
}
func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, error) {
scenesQB := models.NewSceneQueryBuilder()
scenesCount, _ := scenesQB.Count()
galleryQB := models.NewGalleryQueryBuilder()
galleryCount, _ := galleryQB.Count()
performersQB := models.NewPerformerQueryBuilder()
performersCount, _ := performersQB.Count()
studiosQB := models.NewStudioQueryBuilder()
studiosCount, _ := studiosQB.Count()
tagsQB := models.NewTagQueryBuilder()
tagsCount, _ := tagsQB.Count()
return &models.StatsResultType{
SceneCount: scenesCount,
GalleryCount: galleryCount,
PerformerCount: performersCount,
StudioCount: studiosCount,
TagCount: tagsCount,
}, nil
var ret models.StatsResultType
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
scenesQB := repo.Scene()
imageQB := repo.Image()
galleryQB := repo.Gallery()
studiosQB := repo.Studio()
performersQB := repo.Performer()
moviesQB := repo.Movie()
tagsQB := repo.Tag()
scenesCount, _ := scenesQB.Count()
scenesSize, _ := scenesQB.Size()
imageCount, _ := imageQB.Count()
imageSize, _ := imageQB.Size()
galleryCount, _ := galleryQB.Count()
performersCount, _ := performersQB.Count()
studiosCount, _ := studiosQB.Count()
moviesCount, _ := moviesQB.Count()
tagsCount, _ := tagsQB.Count()
ret = models.StatsResultType{
SceneCount: scenesCount,
ScenesSize: scenesSize,
ImageCount: imageCount,
ImagesSize: imageSize,
GalleryCount: galleryCount,
PerformerCount: performersCount,
StudioCount: studiosCount,
MovieCount: moviesCount,
TagCount: tagsCount,
}
return nil
}); err != nil {
return nil, err
}
return &ret, nil
}
func (r *queryResolver) Version(ctx context.Context) (*models.Version, error) {
@@ -133,31 +187,41 @@ func (r *queryResolver) Latestversion(ctx context.Context) (*models.ShortVersion
// Get scene marker tags which show up under the video.
func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([]*models.SceneMarkerTag, error) {
sceneID, _ := strconv.Atoi(scene_id)
sqb := models.NewSceneMarkerQueryBuilder()
sceneMarkers, err := sqb.FindBySceneID(sceneID, nil)
sceneID, err := strconv.Atoi(scene_id)
if err != nil {
return nil, err
}
tags := make(map[int]*models.SceneMarkerTag)
var keys []int
tqb := models.NewTagQueryBuilder()
for _, sceneMarker := range sceneMarkers {
markerPrimaryTag, err := tqb.Find(sceneMarker.PrimaryTagID, nil)
tags := make(map[int]*models.SceneMarkerTag)
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
sceneMarkers, err := repo.SceneMarker().FindBySceneID(sceneID)
if err != nil {
return nil, err
return err
}
_, hasKey := tags[markerPrimaryTag.ID]
var sceneMarkerTag *models.SceneMarkerTag
if !hasKey {
sceneMarkerTag = &models.SceneMarkerTag{Tag: markerPrimaryTag}
tags[markerPrimaryTag.ID] = sceneMarkerTag
keys = append(keys, markerPrimaryTag.ID)
} else {
sceneMarkerTag = tags[markerPrimaryTag.ID]
tqb := repo.Tag()
for _, sceneMarker := range sceneMarkers {
markerPrimaryTag, err := tqb.Find(sceneMarker.PrimaryTagID)
if err != nil {
return err
}
_, hasKey := tags[markerPrimaryTag.ID]
var sceneMarkerTag *models.SceneMarkerTag
if !hasKey {
sceneMarkerTag = &models.SceneMarkerTag{Tag: markerPrimaryTag}
tags[markerPrimaryTag.ID] = sceneMarkerTag
keys = append(keys, markerPrimaryTag.ID)
} else {
sceneMarkerTag = tags[markerPrimaryTag.ID]
}
tags[markerPrimaryTag.ID].SceneMarkers = append(tags[markerPrimaryTag.ID].SceneMarkers, sceneMarker)
}
tags[markerPrimaryTag.ID].SceneMarkers = append(tags[markerPrimaryTag.ID].SceneMarkers, sceneMarker)
return nil
}); err != nil {
return nil, err
}
// Sort so that primary tags that show up earlier in the video are first.
@@ -174,13 +238,3 @@ func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([
return result, nil
}
// wasFieldIncluded returns true if the given field was included in the request.
// Slices are unmarshalled to empty slices even if the field was omitted. This
// method determines if it was omitted altogether.
func wasFieldIncluded(ctx context.Context, field string) bool {
rctx := graphql.GetRequestContext(ctx)
_, ret := rctx.Variables[field]
return ret
}

View File

@@ -2,14 +2,154 @@ package api
import (
"context"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*string, error) {
return nil, nil // TODO remove this from schema
func (r *galleryResolver) Path(ctx context.Context, obj *models.Gallery) (*string, error) {
if obj.Path.Valid {
return &obj.Path.String, nil
}
return nil, nil
}
func (r *galleryResolver) Files(ctx context.Context, obj *models.Gallery) ([]*models.GalleryFilesType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
return obj.GetFiles(baseURL), nil
func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*string, error) {
if obj.Title.Valid {
return &obj.Title.String, nil
}
return nil, nil
}
func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) (ret []*models.Image, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
ret, err = repo.Image().FindByGalleryID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *galleryResolver) Cover(ctx context.Context, obj *models.Gallery) (ret *models.Image, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
imgs, err := repo.Image().FindByGalleryID(obj.ID)
if err != nil {
return err
}
if len(imgs) > 0 {
ret = imgs[0]
}
for _, img := range imgs {
if image.IsCover(img) {
ret = img
break
}
}
return nil
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *galleryResolver) Date(ctx context.Context, obj *models.Gallery) (*string, error) {
if obj.Date.Valid {
result := utils.GetYMDFromDatabaseDate(obj.Date.String)
return &result, nil
}
return nil, nil
}
func (r *galleryResolver) URL(ctx context.Context, obj *models.Gallery) (*string, error) {
if obj.URL.Valid {
return &obj.URL.String, nil
}
return nil, nil
}
func (r *galleryResolver) Details(ctx context.Context, obj *models.Gallery) (*string, error) {
if obj.Details.Valid {
return &obj.Details.String, nil
}
return nil, nil
}
func (r *galleryResolver) Rating(ctx context.Context, obj *models.Gallery) (*int, error) {
if obj.Rating.Valid {
rating := int(obj.Rating.Int64)
return &rating, nil
}
return nil, nil
}
func (r *galleryResolver) Scenes(ctx context.Context, obj *models.Gallery) (ret []*models.Scene, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
ret, err = repo.Scene().FindByGalleryID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *galleryResolver) Studio(ctx context.Context, obj *models.Gallery) (ret *models.Studio, err error) {
if !obj.StudioID.Valid {
return nil, nil
}
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *galleryResolver) Tags(ctx context.Context, obj *models.Gallery) (ret []*models.Tag, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
ret, err = repo.Tag().FindByGalleryID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *galleryResolver) Performers(ctx context.Context, obj *models.Gallery) (ret []*models.Performer, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
ret, err = repo.Performer().FindByGalleryID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *galleryResolver) ImageCount(ctx context.Context, obj *models.Gallery) (ret int, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
ret, err = repo.Image().CountByGalleryID(obj.ID)
return err
}); err != nil {
return 0, err
}
return ret, nil
}

View File

@@ -0,0 +1,93 @@
package api
import (
"context"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
)
func (r *imageResolver) Title(ctx context.Context, obj *models.Image) (*string, error) {
ret := image.GetTitle(obj)
return &ret, nil
}
func (r *imageResolver) Rating(ctx context.Context, obj *models.Image) (*int, error) {
if obj.Rating.Valid {
rating := int(obj.Rating.Int64)
return &rating, nil
}
return nil, nil
}
func (r *imageResolver) File(ctx context.Context, obj *models.Image) (*models.ImageFileType, error) {
width := int(obj.Width.Int64)
height := int(obj.Height.Int64)
size := int(obj.Size.Int64)
return &models.ImageFileType{
Size: &size,
Width: &width,
Height: &height,
}, nil
}
func (r *imageResolver) Paths(ctx context.Context, obj *models.Image) (*models.ImagePathsType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
builder := urlbuilders.NewImageURLBuilder(baseURL, obj.ID)
thumbnailPath := builder.GetThumbnailURL()
imagePath := builder.GetImageURL()
return &models.ImagePathsType{
Image: &imagePath,
Thumbnail: &thumbnailPath,
}, nil
}
func (r *imageResolver) Galleries(ctx context.Context, obj *models.Image) (ret []*models.Gallery, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
ret, err = repo.Gallery().FindByImageID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *imageResolver) Studio(ctx context.Context, obj *models.Image) (ret *models.Studio, err error) {
if !obj.StudioID.Valid {
return nil, nil
}
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *imageResolver) Tags(ctx context.Context, obj *models.Image) (ret []*models.Tag, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Tag().FindByImageID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *imageResolver) Performers(ctx context.Context, obj *models.Image) (ret []*models.Performer, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Performer().FindByImageID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}

View File

@@ -0,0 +1,107 @@
package api
import (
"context"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *movieResolver) Name(ctx context.Context, obj *models.Movie) (string, error) {
if obj.Name.Valid {
return obj.Name.String, nil
}
return "", nil
}
func (r *movieResolver) URL(ctx context.Context, obj *models.Movie) (*string, error) {
if obj.URL.Valid {
return &obj.URL.String, nil
}
return nil, nil
}
func (r *movieResolver) Aliases(ctx context.Context, obj *models.Movie) (*string, error) {
if obj.Aliases.Valid {
return &obj.Aliases.String, nil
}
return nil, nil
}
func (r *movieResolver) Duration(ctx context.Context, obj *models.Movie) (*int, error) {
if obj.Duration.Valid {
rating := int(obj.Duration.Int64)
return &rating, nil
}
return nil, nil
}
func (r *movieResolver) Date(ctx context.Context, obj *models.Movie) (*string, error) {
if obj.Date.Valid {
result := utils.GetYMDFromDatabaseDate(obj.Date.String)
return &result, nil
}
return nil, nil
}
func (r *movieResolver) Rating(ctx context.Context, obj *models.Movie) (*int, error) {
if obj.Rating.Valid {
rating := int(obj.Rating.Int64)
return &rating, nil
}
return nil, nil
}
func (r *movieResolver) Studio(ctx context.Context, obj *models.Movie) (ret *models.Studio, err error) {
if obj.StudioID.Valid {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
return err
}); err != nil {
return nil, err
}
return ret, nil
}
return nil, nil
}
func (r *movieResolver) Director(ctx context.Context, obj *models.Movie) (*string, error) {
if obj.Director.Valid {
return &obj.Director.String, nil
}
return nil, nil
}
func (r *movieResolver) Synopsis(ctx context.Context, obj *models.Movie) (*string, error) {
if obj.Synopsis.Valid {
return &obj.Synopsis.String, nil
}
return nil, nil
}
func (r *movieResolver) FrontImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
frontimagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj.ID).GetMovieFrontImageURL()
return &frontimagePath, nil
}
func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
backimagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj.ID).GetMovieBackImageURL()
return &backimagePath, nil
}
func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret *int, err error) {
var res int
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
res, err = repo.Scene().CountByMovieID(obj.ID)
return err
}); err != nil {
return nil, err
}
return &res, err
}

View File

@@ -2,6 +2,7 @@ package api
import (
"context"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
)
@@ -20,6 +21,19 @@ func (r *performerResolver) URL(ctx context.Context, obj *models.Performer) (*st
return nil, nil
}
func (r *performerResolver) Gender(ctx context.Context, obj *models.Performer) (*models.GenderEnum, error) {
var ret models.GenderEnum
if obj.Gender.Valid {
ret = models.GenderEnum(obj.Gender.String)
if ret.IsValid() {
return &ret, nil
}
}
return nil, nil
}
func (r *performerResolver) Twitter(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Twitter.Valid {
return &obj.Twitter.String, nil
@@ -124,13 +138,36 @@ func (r *performerResolver) ImagePath(ctx context.Context, obj *models.Performer
return &imagePath, nil
}
func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (*int, error) {
qb := models.NewSceneQueryBuilder()
res, err := qb.CountByPerformerID(obj.ID)
return &res, err
func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
var res int
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
res, err = repo.Scene().CountByPerformerID(obj.ID)
return err
}); err != nil {
return nil, err
}
return &res, nil
}
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) ([]*models.Scene, error) {
qb := models.NewSceneQueryBuilder()
return qb.FindByPerformerID(obj.ID)
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) (ret []*models.Scene, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Scene().FindByPerformerID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer) (ret []*models.StashID, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Performer().GetStashIDs(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}

View File

@@ -4,11 +4,24 @@ import (
"context"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *sceneResolver) Checksum(ctx context.Context, obj *models.Scene) (*string, error) {
if obj.Checksum.Valid {
return &obj.Checksum.String, nil
}
return nil, nil
}
func (r *sceneResolver) Oshash(ctx context.Context, obj *models.Scene) (*string, error) {
if obj.OSHash.Valid {
return &obj.OSHash.String, nil
}
return nil, nil
}
func (r *sceneResolver) Title(ctx context.Context, obj *models.Scene) (*string, error) {
if obj.Title.Valid {
return &obj.Title.String, nil
@@ -81,31 +94,109 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*models.S
}, nil
}
func (r *sceneResolver) IsStreamable(ctx context.Context, obj *models.Scene) (bool, error) {
return manager.IsStreamable(obj)
func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) (ret []*models.SceneMarker, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.SceneMarker().FindBySceneID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) ([]*models.SceneMarker, error) {
qb := models.NewSceneMarkerQueryBuilder()
return qb.FindBySceneID(obj.ID, nil)
func (r *sceneResolver) Galleries(ctx context.Context, obj *models.Scene) (ret []*models.Gallery, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Gallery().FindBySceneID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *sceneResolver) Gallery(ctx context.Context, obj *models.Scene) (*models.Gallery, error) {
qb := models.NewGalleryQueryBuilder()
return qb.FindBySceneID(obj.ID, nil)
func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (ret *models.Studio, err error) {
if !obj.StudioID.Valid {
return nil, nil
}
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (*models.Studio, error) {
qb := models.NewStudioQueryBuilder()
return qb.FindBySceneID(obj.ID)
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*models.SceneMovie, err error) {
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Scene()
mqb := repo.Movie()
sceneMovies, err := qb.GetMovies(obj.ID)
if err != nil {
return err
}
for _, sm := range sceneMovies {
movie, err := mqb.Find(sm.MovieID)
if err != nil {
return err
}
sceneIdx := sm.SceneIndex
sceneMovie := &models.SceneMovie{
Movie: movie,
}
if sceneIdx.Valid {
var idx int
idx = int(sceneIdx.Int64)
sceneMovie.SceneIndex = &idx
}
ret = append(ret, sceneMovie)
}
return nil
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) ([]*models.Tag, error) {
qb := models.NewTagQueryBuilder()
return qb.FindBySceneID(obj.ID, nil)
func (r *sceneResolver) Tags(ctx context.Context, obj *models.Scene) (ret []*models.Tag, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Tag().FindBySceneID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *sceneResolver) Performers(ctx context.Context, obj *models.Scene) ([]*models.Performer, error) {
qb := models.NewPerformerQueryBuilder()
return qb.FindBySceneID(obj.ID, nil)
func (r *sceneResolver) Performers(ctx context.Context, obj *models.Scene) (ret []*models.Performer, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Performer().FindBySceneID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *sceneResolver) StashIds(ctx context.Context, obj *models.Scene) (ret []*models.StashID, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Scene().GetStashIDs(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}

View File

@@ -2,29 +2,47 @@ package api
import (
"context"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
)
func (r *sceneMarkerResolver) Scene(ctx context.Context, obj *models.SceneMarker) (*models.Scene, error) {
func (r *sceneMarkerResolver) Scene(ctx context.Context, obj *models.SceneMarker) (ret *models.Scene, err error) {
if !obj.SceneID.Valid {
panic("Invalid scene id")
}
qb := models.NewSceneQueryBuilder()
sceneID := int(obj.SceneID.Int64)
scene, err := qb.Find(sceneID)
return scene, err
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
sceneID := int(obj.SceneID.Int64)
ret, err = repo.Scene().Find(sceneID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *sceneMarkerResolver) PrimaryTag(ctx context.Context, obj *models.SceneMarker) (*models.Tag, error) {
qb := models.NewTagQueryBuilder()
tag, err := qb.Find(obj.PrimaryTagID, nil)
return tag, err
func (r *sceneMarkerResolver) PrimaryTag(ctx context.Context, obj *models.SceneMarker) (ret *models.Tag, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Tag().Find(obj.PrimaryTagID)
return err
}); err != nil {
return nil, err
}
return ret, err
}
func (r *sceneMarkerResolver) Tags(ctx context.Context, obj *models.SceneMarker) ([]*models.Tag, error) {
qb := models.NewTagQueryBuilder()
return qb.FindBySceneMarkerID(obj.ID, nil)
func (r *sceneMarkerResolver) Tags(ctx context.Context, obj *models.SceneMarker) (ret []*models.Tag, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Tag().FindBySceneMarkerID(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, err
}
func (r *sceneMarkerResolver) Stream(ctx context.Context, obj *models.SceneMarker) (string, error) {

View File

@@ -0,0 +1,23 @@
package api
import (
"context"
"github.com/stashapp/stash/pkg/models"
)
func (r *scrapedSceneTagResolver) StoredID(ctx context.Context, obj *models.ScrapedSceneTag) (*string, error) {
return obj.ID, nil
}
func (r *scrapedSceneMovieResolver) StoredID(ctx context.Context, obj *models.ScrapedSceneMovie) (*string, error) {
return obj.ID, nil
}
func (r *scrapedScenePerformerResolver) StoredID(ctx context.Context, obj *models.ScrapedScenePerformer) (*string, error) {
return obj.ID, nil
}
func (r *scrapedSceneStudioResolver) StoredID(ctx context.Context, obj *models.ScrapedSceneStudio) (*string, error) {
return obj.ID, nil
}

View File

@@ -2,6 +2,7 @@ package api
import (
"context"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
)
@@ -23,11 +24,69 @@ func (r *studioResolver) URL(ctx context.Context, obj *models.Studio) (*string,
func (r *studioResolver) ImagePath(ctx context.Context, obj *models.Studio) (*string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
imagePath := urlbuilders.NewStudioURLBuilder(baseURL, obj.ID).GetStudioImageURL()
var hasImage bool
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
hasImage, err = repo.Studio().HasImage(obj.ID)
return err
}); err != nil {
return nil, err
}
// indicate that image is missing by setting default query param to true
if !hasImage {
imagePath = imagePath + "?default=true"
}
return &imagePath, nil
}
func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio) (*int, error) {
qb := models.NewSceneQueryBuilder()
res, err := qb.CountByStudioID(obj.ID)
func (r *studioResolver) SceneCount(ctx context.Context, obj *models.Studio) (ret *int, err error) {
var res int
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
res, err = repo.Scene().CountByStudioID(obj.ID)
return err
}); err != nil {
return nil, err
}
return &res, err
}
func (r *studioResolver) ParentStudio(ctx context.Context, obj *models.Studio) (ret *models.Studio, err error) {
if !obj.ParentID.Valid {
return nil, nil
}
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Studio().Find(int(obj.ParentID.Int64))
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *studioResolver) ChildStudios(ctx context.Context, obj *models.Studio) (ret []*models.Studio, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Studio().FindChildren(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *studioResolver) StashIds(ctx context.Context, obj *models.Studio) (ret []*models.StashID, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Studio().GetStashIDs(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}

View File

@@ -2,23 +2,37 @@ package api
import (
"context"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
)
func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag) (*int, error) {
qb := models.NewSceneQueryBuilder()
if obj == nil {
return nil, nil
func (r *tagResolver) SceneCount(ctx context.Context, obj *models.Tag) (ret *int, err error) {
var count int
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
count, err = repo.Scene().CountByTagID(obj.ID)
return err
}); err != nil {
return nil, err
}
count, err := qb.CountByTagID(obj.ID)
return &count, err
}
func (r *tagResolver) SceneMarkerCount(ctx context.Context, obj *models.Tag) (*int, error) {
qb := models.NewSceneMarkerQueryBuilder()
if obj == nil {
return nil, nil
func (r *tagResolver) SceneMarkerCount(ctx context.Context, obj *models.Tag) (ret *int, err error) {
var count int
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
count, err = repo.SceneMarker().CountByTagID(obj.ID)
return err
}); err != nil {
return nil, err
}
count, err := qb.CountByTagID(obj.ID)
return &count, err
}
func (r *tagResolver) ImagePath(ctx context.Context, obj *models.Tag) (*string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
imagePath := urlbuilders.NewTagURLBuilder(baseURL, obj.ID).GetTagImageURL()
return &imagePath, nil
}

View File

@@ -2,6 +2,7 @@ package api
import (
"context"
"errors"
"fmt"
"path/filepath"
@@ -14,8 +15,8 @@ import (
func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.ConfigGeneralInput) (*models.ConfigGeneralResult, error) {
if len(input.Stashes) > 0 {
for _, stashPath := range input.Stashes {
exists, err := utils.DirExists(stashPath)
for _, s := range input.Stashes {
exists, err := utils.DirExists(s.Path)
if !exists {
return makeConfigGeneralResult(), err
}
@@ -38,6 +39,47 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
config.Set(config.Generated, input.GeneratedPath)
}
if input.CachePath != nil {
if err := utils.EnsureDir(*input.CachePath); err != nil {
return makeConfigGeneralResult(), err
}
config.Set(config.Cache, input.CachePath)
}
if !input.CalculateMd5 && input.VideoFileNamingAlgorithm == models.HashAlgorithmMd5 {
return makeConfigGeneralResult(), errors.New("calculateMD5 must be true if using MD5")
}
if input.VideoFileNamingAlgorithm != config.GetVideoFileNamingAlgorithm() {
// validate changing VideoFileNamingAlgorithm
if err := manager.ValidateVideoFileNamingAlgorithm(r.txnManager, input.VideoFileNamingAlgorithm); err != nil {
return makeConfigGeneralResult(), err
}
config.Set(config.VideoFileNamingAlgorithm, input.VideoFileNamingAlgorithm)
}
config.Set(config.CalculateMD5, input.CalculateMd5)
if input.ParallelTasks != nil {
config.Set(config.ParallelTasks, *input.ParallelTasks)
}
if input.PreviewSegments != nil {
config.Set(config.PreviewSegments, *input.PreviewSegments)
}
if input.PreviewSegmentDuration != nil {
config.Set(config.PreviewSegmentDuration, *input.PreviewSegmentDuration)
}
if input.PreviewExcludeStart != nil {
config.Set(config.PreviewExcludeStart, *input.PreviewExcludeStart)
}
if input.PreviewExcludeEnd != nil {
config.Set(config.PreviewExcludeEnd, *input.PreviewExcludeEnd)
}
if input.PreviewPreset != nil {
config.Set(config.PreviewPreset, input.PreviewPreset.String())
}
if input.MaxTranscodeSize != nil {
config.Set(config.MaxTranscodeSize, input.MaxTranscodeSize.String())
}
@@ -60,6 +102,10 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
}
}
if input.MaxSessionAge != nil {
config.Set(config.MaxSessionAge, *input.MaxSessionAge)
}
if input.LogFile != nil {
config.Set(config.LogFile, input.LogFile)
}
@@ -76,16 +122,59 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
config.Set(config.Exclude, input.Excludes)
}
if input.ImageExcludes != nil {
config.Set(config.ImageExclude, input.ImageExcludes)
}
if input.VideoExtensions != nil {
config.Set(config.VideoExtensions, input.VideoExtensions)
}
if input.ImageExtensions != nil {
config.Set(config.ImageExtensions, input.ImageExtensions)
}
if input.GalleryExtensions != nil {
config.Set(config.GalleryExtensions, input.GalleryExtensions)
}
config.Set(config.CreateGalleriesFromFolders, input.CreateGalleriesFromFolders)
refreshScraperCache := false
if input.ScraperUserAgent != nil {
config.Set(config.ScraperUserAgent, input.ScraperUserAgent)
refreshScraperCache = true
}
if input.ScraperCDPPath != nil {
config.Set(config.ScraperCDPPath, input.ScraperCDPPath)
refreshScraperCache = true
}
if input.StashBoxes != nil {
if err := config.ValidateStashBoxes(input.StashBoxes); err != nil {
return nil, err
}
config.Set(config.StashBoxes, input.StashBoxes)
}
if err := config.Write(); err != nil {
return makeConfigGeneralResult(), err
}
manager.GetInstance().RefreshConfig()
if refreshScraperCache {
manager.GetInstance().RefreshScraperCache()
}
return makeConfigGeneralResult(), nil
}
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.ConfigInterfaceInput) (*models.ConfigInterfaceResult, error) {
if input.MenuItems != nil {
config.Set(config.MenuItems, input.MenuItems)
}
if input.SoundOnPreview != nil {
config.Set(config.SoundOnPreview, *input.SoundOnPreview)
}
@@ -94,6 +183,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
config.Set(config.WallShowTitle, *input.WallShowTitle)
}
if input.WallPlayback != nil {
config.Set(config.WallPlayback, *input.WallPlayback)
}
if input.MaximumLoopDuration != nil {
config.Set(config.MaximumLoopDuration, *input.MaximumLoopDuration)
}
@@ -106,6 +199,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
config.Set(config.ShowStudioAsText, *input.ShowStudioAsText)
}
if input.Language != nil {
config.Set(config.Language, *input.Language)
}
css := ""
if input.CSS != nil {

View File

@@ -0,0 +1,522 @@
package api
import (
"context"
"database/sql"
"errors"
"fmt"
"strconv"
"time"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *mutationResolver) GalleryCreate(ctx context.Context, input models.GalleryCreateInput) (*models.Gallery, error) {
// name must be provided
if input.Title == "" {
return nil, errors.New("title must not be empty")
}
// for manually created galleries, generate checksum from title
checksum := utils.MD5FromString(input.Title)
// Populate a new performer from the input
currentTime := time.Now()
newGallery := models.Gallery{
Title: sql.NullString{
String: input.Title,
Valid: true,
},
Checksum: checksum,
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
}
if input.URL != nil {
newGallery.URL = sql.NullString{String: *input.URL, Valid: true}
}
if input.Details != nil {
newGallery.Details = sql.NullString{String: *input.Details, Valid: true}
}
if input.URL != nil {
newGallery.URL = sql.NullString{String: *input.URL, Valid: true}
}
if input.Date != nil {
newGallery.Date = models.SQLiteDate{String: *input.Date, Valid: true}
}
if input.Rating != nil {
newGallery.Rating = sql.NullInt64{Int64: int64(*input.Rating), Valid: true}
} else {
// rating must be nullable
newGallery.Rating = sql.NullInt64{Valid: false}
}
if input.StudioID != nil {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
newGallery.StudioID = sql.NullInt64{Int64: studioID, Valid: true}
} else {
// studio must be nullable
newGallery.StudioID = sql.NullInt64{Valid: false}
}
// Start the transaction and save the gallery
var gallery *models.Gallery
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Gallery()
var err error
gallery, err = qb.Create(newGallery)
if err != nil {
return err
}
// Save the performers
if err := r.updateGalleryPerformers(qb, gallery.ID, input.PerformerIds); err != nil {
return err
}
// Save the tags
if err := r.updateGalleryTags(qb, gallery.ID, input.TagIds); err != nil {
return err
}
// Save the scenes
if err := r.updateGalleryScenes(qb, gallery.ID, input.SceneIds); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return gallery, nil
}
func (r *mutationResolver) updateGalleryPerformers(qb models.GalleryReaderWriter, galleryID int, performerIDs []string) error {
ids, err := utils.StringSliceToIntSlice(performerIDs)
if err != nil {
return err
}
return qb.UpdatePerformers(galleryID, ids)
}
func (r *mutationResolver) updateGalleryTags(qb models.GalleryReaderWriter, galleryID int, tagIDs []string) error {
ids, err := utils.StringSliceToIntSlice(tagIDs)
if err != nil {
return err
}
return qb.UpdateTags(galleryID, ids)
}
func (r *mutationResolver) updateGalleryScenes(qb models.GalleryReaderWriter, galleryID int, sceneIDs []string) error {
ids, err := utils.StringSliceToIntSlice(sceneIDs)
if err != nil {
return err
}
return qb.UpdateScenes(galleryID, ids)
}
func (r *mutationResolver) GalleryUpdate(ctx context.Context, input models.GalleryUpdateInput) (ret *models.Gallery, err error) {
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
// Start the transaction and save the gallery
if err := r.withTxn(ctx, func(repo models.Repository) error {
ret, err = r.galleryUpdate(input, translator, repo)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *mutationResolver) GalleriesUpdate(ctx context.Context, input []*models.GalleryUpdateInput) (ret []*models.Gallery, err error) {
inputMaps := getUpdateInputMaps(ctx)
// Start the transaction and save the gallery
if err := r.withTxn(ctx, func(repo models.Repository) error {
for i, gallery := range input {
translator := changesetTranslator{
inputMap: inputMaps[i],
}
thisGallery, err := r.galleryUpdate(*gallery, translator, repo)
if err != nil {
return err
}
ret = append(ret, thisGallery)
}
return nil
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, translator changesetTranslator, repo models.Repository) (*models.Gallery, error) {
qb := repo.Gallery()
// Populate gallery from the input
galleryID, err := strconv.Atoi(input.ID)
if err != nil {
return nil, err
}
originalGallery, err := qb.Find(galleryID)
if err != nil {
return nil, err
}
if originalGallery == nil {
return nil, errors.New("not found")
}
updatedTime := time.Now()
updatedGallery := models.GalleryPartial{
ID: galleryID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
if input.Title != nil {
// ensure title is not empty
if *input.Title == "" {
return nil, errors.New("title must not be empty")
}
// if gallery is not zip-based, then generate the checksum from the title
if !originalGallery.Path.Valid {
checksum := utils.MD5FromString(*input.Title)
updatedGallery.Checksum = &checksum
}
updatedGallery.Title = &sql.NullString{String: *input.Title, Valid: true}
}
updatedGallery.Details = translator.nullString(input.Details, "details")
updatedGallery.URL = translator.nullString(input.URL, "url")
updatedGallery.Date = translator.sqliteDate(input.Date, "date")
updatedGallery.Rating = translator.nullInt64(input.Rating, "rating")
updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
updatedGallery.Organized = input.Organized
// gallery scene is set from the scene only
gallery, err := qb.UpdatePartial(updatedGallery)
if err != nil {
return nil, err
}
// Save the performers
if translator.hasField("performer_ids") {
if err := r.updateGalleryPerformers(qb, galleryID, input.PerformerIds); err != nil {
return nil, err
}
}
// Save the tags
if translator.hasField("tag_ids") {
if err := r.updateGalleryTags(qb, galleryID, input.TagIds); err != nil {
return nil, err
}
}
// Save the scenes
if translator.hasField("scene_ids") {
if err := r.updateGalleryScenes(qb, galleryID, input.SceneIds); err != nil {
return nil, err
}
}
return gallery, nil
}
func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.BulkGalleryUpdateInput) ([]*models.Gallery, error) {
// Populate gallery from the input
updatedTime := time.Now()
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
updatedGallery := models.GalleryPartial{
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
updatedGallery.Details = translator.nullString(input.Details, "details")
updatedGallery.URL = translator.nullString(input.URL, "url")
updatedGallery.Date = translator.sqliteDate(input.Date, "date")
updatedGallery.Rating = translator.nullInt64(input.Rating, "rating")
updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
updatedGallery.Organized = input.Organized
ret := []*models.Gallery{}
// Start the transaction and save the galleries
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Gallery()
for _, galleryIDStr := range input.Ids {
galleryID, _ := strconv.Atoi(galleryIDStr)
updatedGallery.ID = galleryID
gallery, err := qb.UpdatePartial(updatedGallery)
if err != nil {
return err
}
ret = append(ret, gallery)
// Save the performers
if translator.hasField("performer_ids") {
performerIDs, err := adjustGalleryPerformerIDs(qb, galleryID, *input.PerformerIds)
if err != nil {
return err
}
if err := qb.UpdatePerformers(galleryID, performerIDs); err != nil {
return err
}
}
// Save the tags
if translator.hasField("tag_ids") {
tagIDs, err := adjustGalleryTagIDs(qb, galleryID, *input.TagIds)
if err != nil {
return err
}
if err := qb.UpdateTags(galleryID, tagIDs); err != nil {
return err
}
}
// Save the scenes
if translator.hasField("scene_ids") {
sceneIDs, err := adjustGallerySceneIDs(qb, galleryID, *input.SceneIds)
if err != nil {
return err
}
if err := qb.UpdateScenes(galleryID, sceneIDs); err != nil {
return err
}
}
}
return nil
}); err != nil {
return nil, err
}
return ret, nil
}
func adjustGalleryPerformerIDs(qb models.GalleryReader, galleryID int, ids models.BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetPerformerIDs(galleryID)
if err != nil {
return nil, err
}
return adjustIDs(ret, ids), nil
}
func adjustGalleryTagIDs(qb models.GalleryReader, galleryID int, ids models.BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetTagIDs(galleryID)
if err != nil {
return nil, err
}
return adjustIDs(ret, ids), nil
}
func adjustGallerySceneIDs(qb models.GalleryReader, galleryID int, ids models.BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetSceneIDs(galleryID)
if err != nil {
return nil, err
}
return adjustIDs(ret, ids), nil
}
func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.GalleryDestroyInput) (bool, error) {
galleryIDs, err := utils.StringSliceToIntSlice(input.Ids)
if err != nil {
return false, err
}
var galleries []*models.Gallery
var imgsToPostProcess []*models.Image
var imgsToDelete []*models.Image
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Gallery()
iqb := repo.Image()
for _, id := range galleryIDs {
gallery, err := qb.Find(id)
if err != nil {
return err
}
if gallery == nil {
return fmt.Errorf("gallery with id %d not found", id)
}
galleries = append(galleries, gallery)
// if this is a zip-based gallery, delete the images as well first
if gallery.Zip {
imgs, err := iqb.FindByGalleryID(id)
if err != nil {
return err
}
for _, img := range imgs {
if err := iqb.Destroy(img.ID); err != nil {
return err
}
imgsToPostProcess = append(imgsToPostProcess, img)
}
} else if input.DeleteFile != nil && *input.DeleteFile {
// Delete image if it is only attached to this gallery
imgs, err := iqb.FindByGalleryID(id)
if err != nil {
return err
}
for _, img := range imgs {
imgGalleries, err := qb.FindByImageID(img.ID)
if err != nil {
return err
}
if len(imgGalleries) == 0 {
if err := iqb.Destroy(img.ID); err != nil {
return err
}
imgsToDelete = append(imgsToDelete, img)
imgsToPostProcess = append(imgsToPostProcess, img)
}
}
}
if err := qb.Destroy(id); err != nil {
return err
}
}
return nil
}); err != nil {
return false, err
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
for _, gallery := range galleries {
manager.DeleteGalleryFile(gallery)
}
for _, img := range imgsToDelete {
manager.DeleteImageFile(img)
}
}
// if delete generated is true, then delete the generated files
// for the gallery
if input.DeleteGenerated != nil && *input.DeleteGenerated {
for _, img := range imgsToPostProcess {
manager.DeleteGeneratedImageFiles(img)
}
}
return true, nil
}
func (r *mutationResolver) AddGalleryImages(ctx context.Context, input models.GalleryAddInput) (bool, error) {
galleryID, err := strconv.Atoi(input.GalleryID)
if err != nil {
return false, err
}
imageIDs, err := utils.StringSliceToIntSlice(input.ImageIds)
if err != nil {
return false, err
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Gallery()
gallery, err := qb.Find(galleryID)
if err != nil {
return err
}
if gallery == nil {
return errors.New("gallery not found")
}
if gallery.Zip {
return errors.New("cannot modify zip gallery images")
}
newIDs, err := qb.GetImageIDs(galleryID)
if err != nil {
return err
}
newIDs = utils.IntAppendUniques(newIDs, imageIDs)
return qb.UpdateImages(galleryID, newIDs)
}); err != nil {
return false, err
}
return true, nil
}
func (r *mutationResolver) RemoveGalleryImages(ctx context.Context, input models.GalleryRemoveInput) (bool, error) {
galleryID, err := strconv.Atoi(input.GalleryID)
if err != nil {
return false, err
}
imageIDs, err := utils.StringSliceToIntSlice(input.ImageIds)
if err != nil {
return false, err
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Gallery()
gallery, err := qb.Find(galleryID)
if err != nil {
return err
}
if gallery == nil {
return errors.New("gallery not found")
}
if gallery.Zip {
return errors.New("cannot modify zip gallery images")
}
newIDs, err := qb.GetImageIDs(galleryID)
if err != nil {
return err
}
newIDs = utils.IntExclude(newIDs, imageIDs)
return qb.UpdateImages(galleryID, newIDs)
}); err != nil {
return false, err
}
return true, nil
}

View File

@@ -0,0 +1,375 @@
package api
import (
"context"
"fmt"
"strconv"
"time"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *mutationResolver) ImageUpdate(ctx context.Context, input models.ImageUpdateInput) (ret *models.Image, err error) {
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
// Start the transaction and save the image
if err := r.withTxn(ctx, func(repo models.Repository) error {
ret, err = r.imageUpdate(input, translator, repo)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*models.ImageUpdateInput) (ret []*models.Image, err error) {
inputMaps := getUpdateInputMaps(ctx)
// Start the transaction and save the image
if err := r.withTxn(ctx, func(repo models.Repository) error {
for i, image := range input {
translator := changesetTranslator{
inputMap: inputMaps[i],
}
thisImage, err := r.imageUpdate(*image, translator, repo)
if err != nil {
return err
}
ret = append(ret, thisImage)
}
return nil
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, translator changesetTranslator, repo models.Repository) (*models.Image, error) {
// Populate image from the input
imageID, err := strconv.Atoi(input.ID)
if err != nil {
return nil, err
}
updatedTime := time.Now()
updatedImage := models.ImagePartial{
ID: imageID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
updatedImage.Title = translator.nullString(input.Title, "title")
updatedImage.Rating = translator.nullInt64(input.Rating, "rating")
updatedImage.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
updatedImage.Organized = input.Organized
qb := repo.Image()
image, err := qb.Update(updatedImage)
if err != nil {
return nil, err
}
if translator.hasField("gallery_ids") {
if err := r.updateImageGalleries(qb, imageID, input.GalleryIds); err != nil {
return nil, err
}
}
// Save the performers
if translator.hasField("performer_ids") {
if err := r.updateImagePerformers(qb, imageID, input.PerformerIds); err != nil {
return nil, err
}
}
// Save the tags
if translator.hasField("tag_ids") {
if err := r.updateImageTags(qb, imageID, input.TagIds); err != nil {
return nil, err
}
}
return image, nil
}
func (r *mutationResolver) updateImageGalleries(qb models.ImageReaderWriter, imageID int, galleryIDs []string) error {
ids, err := utils.StringSliceToIntSlice(galleryIDs)
if err != nil {
return err
}
return qb.UpdateGalleries(imageID, ids)
}
func (r *mutationResolver) updateImagePerformers(qb models.ImageReaderWriter, imageID int, performerIDs []string) error {
ids, err := utils.StringSliceToIntSlice(performerIDs)
if err != nil {
return err
}
return qb.UpdatePerformers(imageID, ids)
}
func (r *mutationResolver) updateImageTags(qb models.ImageReaderWriter, imageID int, tagsIDs []string) error {
ids, err := utils.StringSliceToIntSlice(tagsIDs)
if err != nil {
return err
}
return qb.UpdateTags(imageID, ids)
}
func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.BulkImageUpdateInput) (ret []*models.Image, err error) {
imageIDs, err := utils.StringSliceToIntSlice(input.Ids)
if err != nil {
return nil, err
}
// Populate image from the input
updatedTime := time.Now()
updatedImage := models.ImagePartial{
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
updatedImage.Title = translator.nullString(input.Title, "title")
updatedImage.Rating = translator.nullInt64(input.Rating, "rating")
updatedImage.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
updatedImage.Organized = input.Organized
// Start the transaction and save the image marker
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
for _, imageID := range imageIDs {
updatedImage.ID = imageID
image, err := qb.Update(updatedImage)
if err != nil {
return err
}
ret = append(ret, image)
// Save the galleries
if translator.hasField("gallery_ids") {
galleryIDs, err := adjustImageGalleryIDs(qb, imageID, *input.GalleryIds)
if err != nil {
return err
}
if err := qb.UpdateGalleries(imageID, galleryIDs); err != nil {
return err
}
}
// Save the performers
if translator.hasField("performer_ids") {
performerIDs, err := adjustImagePerformerIDs(qb, imageID, *input.PerformerIds)
if err != nil {
return err
}
if err := qb.UpdatePerformers(imageID, performerIDs); err != nil {
return err
}
}
// Save the tags
if translator.hasField("tag_ids") {
tagIDs, err := adjustImageTagIDs(qb, imageID, *input.TagIds)
if err != nil {
return err
}
if err := qb.UpdateTags(imageID, tagIDs); err != nil {
return err
}
}
}
return nil
}); err != nil {
return nil, err
}
return ret, nil
}
func adjustImageGalleryIDs(qb models.ImageReader, imageID int, ids models.BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetGalleryIDs(imageID)
if err != nil {
return nil, err
}
return adjustIDs(ret, ids), nil
}
func adjustImagePerformerIDs(qb models.ImageReader, imageID int, ids models.BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetPerformerIDs(imageID)
if err != nil {
return nil, err
}
return adjustIDs(ret, ids), nil
}
func adjustImageTagIDs(qb models.ImageReader, imageID int, ids models.BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetTagIDs(imageID)
if err != nil {
return nil, err
}
return adjustIDs(ret, ids), nil
}
func (r *mutationResolver) ImageDestroy(ctx context.Context, input models.ImageDestroyInput) (ret bool, err error) {
imageID, err := strconv.Atoi(input.ID)
if err != nil {
return false, err
}
var image *models.Image
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
image, err = qb.Find(imageID)
if err != nil {
return err
}
if image == nil {
return fmt.Errorf("image with id %d not found", imageID)
}
return qb.Destroy(imageID)
}); err != nil {
return false, err
}
// if delete generated is true, then delete the generated files
// for the image
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedImageFiles(image)
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteImageFile(image)
}
return true, nil
}
func (r *mutationResolver) ImagesDestroy(ctx context.Context, input models.ImagesDestroyInput) (ret bool, err error) {
imageIDs, err := utils.StringSliceToIntSlice(input.Ids)
if err != nil {
return false, err
}
var images []*models.Image
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
for _, imageID := range imageIDs {
image, err := qb.Find(imageID)
if err != nil {
return err
}
if image == nil {
return fmt.Errorf("image with id %d not found", imageID)
}
images = append(images, image)
if err := qb.Destroy(imageID); err != nil {
return err
}
}
return nil
}); err != nil {
return false, err
}
for _, image := range images {
// if delete generated is true, then delete the generated files
// for the image
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedImageFiles(image)
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteImageFile(image)
}
}
return true, nil
}
func (r *mutationResolver) ImageIncrementO(ctx context.Context, id string) (ret int, err error) {
imageID, err := strconv.Atoi(id)
if err != nil {
return 0, err
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
ret, err = qb.IncrementOCounter(imageID)
return err
}); err != nil {
return 0, err
}
return ret, nil
}
func (r *mutationResolver) ImageDecrementO(ctx context.Context, id string) (ret int, err error) {
imageID, err := strconv.Atoi(id)
if err != nil {
return 0, err
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
ret, err = qb.DecrementOCounter(imageID)
return err
}); err != nil {
return 0, err
}
return ret, nil
}
func (r *mutationResolver) ImageResetO(ctx context.Context, id string) (ret int, err error) {
imageID, err := strconv.Atoi(id)
if err != nil {
return 0, err
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
ret, err = qb.ResetOCounter(imageID)
return err
}); err != nil {
return 0, err
}
return ret, nil
}

View File

@@ -0,0 +1,139 @@
package api
import (
"context"
"io/ioutil"
"path/filepath"
"time"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *mutationResolver) MetadataScan(ctx context.Context, input models.ScanMetadataInput) (string, error) {
manager.GetInstance().Scan(input)
return "todo", nil
}
func (r *mutationResolver) MetadataImport(ctx context.Context) (string, error) {
manager.GetInstance().Import()
return "todo", nil
}
func (r *mutationResolver) ImportObjects(ctx context.Context, input models.ImportObjectsInput) (string, error) {
t, err := manager.CreateImportTask(config.GetVideoFileNamingAlgorithm(), input)
if err != nil {
return "", err
}
_, err = manager.GetInstance().RunSingleTask(t)
if err != nil {
return "", err
}
return "todo", nil
}
func (r *mutationResolver) MetadataExport(ctx context.Context) (string, error) {
manager.GetInstance().Export()
return "todo", nil
}
func (r *mutationResolver) ExportObjects(ctx context.Context, input models.ExportObjectsInput) (*string, error) {
t := manager.CreateExportTask(config.GetVideoFileNamingAlgorithm(), input)
wg, err := manager.GetInstance().RunSingleTask(t)
if err != nil {
return nil, err
}
wg.Wait()
if t.DownloadHash != "" {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
// generate timestamp
suffix := time.Now().Format("20060102-150405")
ret := baseURL + "/downloads/" + t.DownloadHash + "/export" + suffix + ".zip"
return &ret, nil
}
return nil, nil
}
func (r *mutationResolver) MetadataGenerate(ctx context.Context, input models.GenerateMetadataInput) (string, error) {
manager.GetInstance().Generate(input)
return "todo", nil
}
func (r *mutationResolver) MetadataAutoTag(ctx context.Context, input models.AutoTagMetadataInput) (string, error) {
manager.GetInstance().AutoTag(input)
return "todo", nil
}
func (r *mutationResolver) MetadataClean(ctx context.Context, input models.CleanMetadataInput) (string, error) {
manager.GetInstance().Clean(input)
return "todo", nil
}
func (r *mutationResolver) MigrateHashNaming(ctx context.Context) (string, error) {
manager.GetInstance().MigrateHash()
return "todo", nil
}
func (r *mutationResolver) JobStatus(ctx context.Context) (*models.MetadataUpdateStatus, error) {
status := manager.GetInstance().Status
ret := models.MetadataUpdateStatus{
Progress: status.Progress,
Status: status.Status.String(),
Message: "",
}
return &ret, nil
}
func (r *mutationResolver) StopJob(ctx context.Context) (bool, error) {
return manager.GetInstance().Status.Stop(), nil
}
func (r *mutationResolver) BackupDatabase(ctx context.Context, input models.BackupDatabaseInput) (*string, error) {
// if download is true, then backup to temporary file and return a link
download := input.Download != nil && *input.Download
mgr := manager.GetInstance()
var backupPath string
if download {
utils.EnsureDir(mgr.Paths.Generated.Downloads)
f, err := ioutil.TempFile(mgr.Paths.Generated.Downloads, "backup*.sqlite")
if err != nil {
return nil, err
}
backupPath = f.Name()
f.Close()
} else {
backupPath = database.DatabaseBackupPath()
}
err := database.Backup(database.DB, backupPath)
if err != nil {
return nil, err
}
if download {
downloadHash := mgr.DownloadStore.RegisterFile(backupPath, "", false)
logger.Debugf("Generated backup file %s with hash %s", backupPath, downloadHash)
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
fn := filepath.Base(database.DatabaseBackupPath())
ret := baseURL + "/downloads/" + downloadHash + "/" + fn
return &ret, nil
} else {
logger.Infof("Successfully backed up database to: %s", backupPath)
}
return nil, nil
}

View File

@@ -0,0 +1,242 @@
package api
import (
"context"
"database/sql"
"strconv"
"time"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCreateInput) (*models.Movie, error) {
// generate checksum from movie name rather than image
checksum := utils.MD5FromString(input.Name)
var frontimageData []byte
var backimageData []byte
var err error
// 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 input.FrontImage == nil && input.BackImage != nil {
input.FrontImage = &models.DefaultMovieImage
}
// Process the base 64 encoded image string
if input.FrontImage != nil {
_, frontimageData, err = utils.ProcessBase64Image(*input.FrontImage)
if err != nil {
return nil, err
}
}
// Process the base 64 encoded image string
if input.BackImage != nil {
_, backimageData, err = utils.ProcessBase64Image(*input.BackImage)
if err != nil {
return nil, err
}
}
// Populate a new movie from the input
currentTime := time.Now()
newMovie := models.Movie{
Checksum: checksum,
Name: sql.NullString{String: input.Name, Valid: true},
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
}
if input.Aliases != nil {
newMovie.Aliases = sql.NullString{String: *input.Aliases, Valid: true}
}
if input.Duration != nil {
duration := int64(*input.Duration)
newMovie.Duration = sql.NullInt64{Int64: duration, Valid: true}
}
if input.Date != nil {
newMovie.Date = models.SQLiteDate{String: *input.Date, Valid: true}
}
if input.Rating != nil {
rating := int64(*input.Rating)
newMovie.Rating = sql.NullInt64{Int64: rating, Valid: true}
}
if input.StudioID != nil {
studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64)
newMovie.StudioID = sql.NullInt64{Int64: studioID, Valid: true}
}
if input.Director != nil {
newMovie.Director = sql.NullString{String: *input.Director, Valid: true}
}
if input.Synopsis != nil {
newMovie.Synopsis = sql.NullString{String: *input.Synopsis, Valid: true}
}
if input.URL != nil {
newMovie.URL = sql.NullString{String: *input.URL, Valid: true}
}
// Start the transaction and save the movie
var movie *models.Movie
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Movie()
movie, err = qb.Create(newMovie)
if err != nil {
return err
}
// update image table
if len(frontimageData) > 0 {
if err := qb.UpdateImages(movie.ID, frontimageData, backimageData); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
return movie, nil
}
func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUpdateInput) (*models.Movie, error) {
// Populate movie from the input
movieID, err := strconv.Atoi(input.ID)
if err != nil {
return nil, err
}
updatedMovie := models.MoviePartial{
ID: movieID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
}
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
var frontimageData []byte
frontImageIncluded := translator.hasField("front_image")
if input.FrontImage != nil {
_, frontimageData, err = utils.ProcessBase64Image(*input.FrontImage)
if err != nil {
return nil, err
}
}
backImageIncluded := translator.hasField("back_image")
var backimageData []byte
if input.BackImage != nil {
_, backimageData, err = utils.ProcessBase64Image(*input.BackImage)
if err != nil {
return nil, err
}
}
if input.Name != nil {
// generate checksum from movie name rather than image
checksum := utils.MD5FromString(*input.Name)
updatedMovie.Name = &sql.NullString{String: *input.Name, Valid: true}
updatedMovie.Checksum = &checksum
}
updatedMovie.Aliases = translator.nullString(input.Aliases, "aliases")
updatedMovie.Duration = translator.nullInt64(input.Duration, "duration")
updatedMovie.Date = translator.sqliteDate(input.Date, "date")
updatedMovie.Rating = translator.nullInt64(input.Rating, "rating")
updatedMovie.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
updatedMovie.Director = translator.nullString(input.Director, "director")
updatedMovie.Synopsis = translator.nullString(input.Synopsis, "synopsis")
updatedMovie.URL = translator.nullString(input.URL, "url")
// Start the transaction and save the movie
var movie *models.Movie
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Movie()
movie, err = qb.Update(updatedMovie)
if err != nil {
return err
}
// update image table
if frontImageIncluded || backImageIncluded {
if !frontImageIncluded {
frontimageData, err = qb.GetFrontImage(updatedMovie.ID)
if err != nil {
return err
}
}
if !backImageIncluded {
backimageData, err = qb.GetBackImage(updatedMovie.ID)
if err != nil {
return err
}
}
if len(frontimageData) == 0 && len(backimageData) == 0 {
// both images are being nulled. Destroy them.
if err := qb.DestroyImages(movie.ID); err != nil {
return err
}
} else {
// HACK - if front image is null and back image is not null, then set the front image
// to the default image since we can't have a null front image and a non-null back image
if frontimageData == nil && backimageData != nil {
_, frontimageData, _ = utils.ProcessBase64Image(models.DefaultMovieImage)
}
if err := qb.UpdateImages(movie.ID, frontimageData, backimageData); err != nil {
return err
}
}
}
return nil
}); err != nil {
return nil, err
}
return movie, nil
}
func (r *mutationResolver) MovieDestroy(ctx context.Context, input models.MovieDestroyInput) (bool, error) {
id, err := strconv.Atoi(input.ID)
if err != nil {
return false, err
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
return repo.Movie().Destroy(id)
}); err != nil {
return false, err
}
return true, nil
}
func (r *mutationResolver) MoviesDestroy(ctx context.Context, movieIDs []string) (bool, error) {
ids, err := utils.StringSliceToIntSlice(movieIDs)
if err != nil {
return false, err
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Movie()
for _, id := range ids {
if err := qb.Destroy(id); err != nil {
return err
}
}
return nil
}); err != nil {
return false, err
}
return true, nil
}

View File

@@ -6,21 +6,18 @@ import (
"strconv"
"time"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.PerformerCreateInput) (*models.Performer, error) {
// generate checksum from performer name rather than image
checksum := utils.MD5FromString(*input.Name)
checksum := utils.MD5FromString(input.Name)
var imageData []byte
var err error
if input.Image == nil {
imageData, err = getRandomPerformerImage()
} else {
if input.Image != nil {
_, imageData, err = utils.ProcessBase64Image(*input.Image)
}
@@ -31,17 +28,17 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
// Populate a new performer from the input
currentTime := time.Now()
newPerformer := models.Performer{
Image: imageData,
Checksum: checksum,
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
}
if input.Name != nil {
newPerformer.Name = sql.NullString{String: *input.Name, Valid: true}
}
newPerformer.Name = sql.NullString{String: input.Name, Valid: true}
if input.URL != nil {
newPerformer.URL = sql.NullString{String: *input.URL, Valid: true}
}
if input.Gender != nil {
newPerformer.Gender = sql.NullString{String: input.Gender.String(), Valid: true}
}
if input.Birthdate != nil {
newPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
}
@@ -88,16 +85,32 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
}
// Start the transaction and save the performer
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewPerformerQueryBuilder()
performer, err := qb.Create(newPerformer, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
var performer *models.Performer
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Performer()
// Commit
if err := tx.Commit(); err != nil {
performer, err = qb.Create(newPerformer)
if err != nil {
return err
}
// update image table
if len(imageData) > 0 {
if err := qb.UpdateImage(performer.ID, imageData); err != nil {
return err
}
}
// Save the stash_ids
if input.StashIds != nil {
stashIDJoins := models.StashIDsFromInput(input.StashIds)
if err := qb.UpdateStashIDs(performer.ID, stashIDJoins); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
@@ -107,83 +120,91 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.PerformerUpdateInput) (*models.Performer, error) {
// Populate performer from the input
performerID, _ := strconv.Atoi(input.ID)
updatedPerformer := models.Performer{
updatedPerformer := models.PerformerPartial{
ID: performerID,
UpdatedAt: models.SQLiteTimestamp{Timestamp: time.Now()},
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
}
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
var imageData []byte
var err error
imageIncluded := translator.hasField("image")
if input.Image != nil {
_, imageData, err := utils.ProcessBase64Image(*input.Image)
_, imageData, err = utils.ProcessBase64Image(*input.Image)
if err != nil {
return nil, err
}
updatedPerformer.Image = imageData
}
if input.Name != nil {
// generate checksum from performer name rather than image
checksum := utils.MD5FromString(*input.Name)
updatedPerformer.Name = sql.NullString{String: *input.Name, Valid: true}
updatedPerformer.Checksum = checksum
updatedPerformer.Name = &sql.NullString{String: *input.Name, Valid: true}
updatedPerformer.Checksum = &checksum
}
if input.URL != nil {
updatedPerformer.URL = sql.NullString{String: *input.URL, Valid: true}
}
if input.Birthdate != nil {
updatedPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
}
if input.Ethnicity != nil {
updatedPerformer.Ethnicity = sql.NullString{String: *input.Ethnicity, Valid: true}
}
if input.Country != nil {
updatedPerformer.Country = sql.NullString{String: *input.Country, Valid: true}
}
if input.EyeColor != nil {
updatedPerformer.EyeColor = sql.NullString{String: *input.EyeColor, Valid: true}
}
if input.Height != nil {
updatedPerformer.Height = sql.NullString{String: *input.Height, Valid: true}
}
if input.Measurements != nil {
updatedPerformer.Measurements = sql.NullString{String: *input.Measurements, Valid: true}
}
if input.FakeTits != nil {
updatedPerformer.FakeTits = sql.NullString{String: *input.FakeTits, Valid: true}
}
if input.CareerLength != nil {
updatedPerformer.CareerLength = sql.NullString{String: *input.CareerLength, Valid: true}
}
if input.Tattoos != nil {
updatedPerformer.Tattoos = sql.NullString{String: *input.Tattoos, Valid: true}
}
if input.Piercings != nil {
updatedPerformer.Piercings = sql.NullString{String: *input.Piercings, Valid: true}
}
if input.Aliases != nil {
updatedPerformer.Aliases = sql.NullString{String: *input.Aliases, Valid: true}
}
if input.Twitter != nil {
updatedPerformer.Twitter = sql.NullString{String: *input.Twitter, Valid: true}
}
if input.Instagram != nil {
updatedPerformer.Instagram = sql.NullString{String: *input.Instagram, Valid: true}
}
if input.Favorite != nil {
updatedPerformer.Favorite = sql.NullBool{Bool: *input.Favorite, Valid: true}
} else {
updatedPerformer.Favorite = sql.NullBool{Bool: false, Valid: true}
updatedPerformer.URL = translator.nullString(input.URL, "url")
if translator.hasField("gender") {
if input.Gender != nil {
updatedPerformer.Gender = &sql.NullString{String: input.Gender.String(), Valid: true}
} else {
updatedPerformer.Gender = &sql.NullString{String: "", Valid: false}
}
}
updatedPerformer.Birthdate = translator.sqliteDate(input.Birthdate, "birthdate")
updatedPerformer.Country = translator.nullString(input.Country, "country")
updatedPerformer.EyeColor = translator.nullString(input.EyeColor, "eye_color")
updatedPerformer.Measurements = translator.nullString(input.Measurements, "measurements")
updatedPerformer.Height = translator.nullString(input.Height, "height")
updatedPerformer.Ethnicity = translator.nullString(input.Ethnicity, "ethnicity")
updatedPerformer.FakeTits = translator.nullString(input.FakeTits, "fake_tits")
updatedPerformer.CareerLength = translator.nullString(input.CareerLength, "career_length")
updatedPerformer.Tattoos = translator.nullString(input.Tattoos, "tattoos")
updatedPerformer.Piercings = translator.nullString(input.Piercings, "piercings")
updatedPerformer.Aliases = translator.nullString(input.Aliases, "aliases")
updatedPerformer.Twitter = translator.nullString(input.Twitter, "twitter")
updatedPerformer.Instagram = translator.nullString(input.Instagram, "instagram")
updatedPerformer.Favorite = translator.nullBool(input.Favorite, "favorite")
// Start the transaction and save the performer
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewPerformerQueryBuilder()
performer, err := qb.Update(updatedPerformer, tx)
if err != nil {
_ = tx.Rollback()
return nil, err
}
var performer *models.Performer
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Performer()
// Commit
if err := tx.Commit(); err != nil {
var err error
performer, err = qb.Update(updatedPerformer)
if err != nil {
return err
}
// update image table
if len(imageData) > 0 {
if err := qb.UpdateImage(performer.ID, imageData); err != nil {
return err
}
} else if imageIncluded {
// must be unsetting
if err := qb.DestroyImage(performer.ID); err != nil {
return err
}
}
// Save the stash_ids
if translator.hasField("stash_ids") {
stashIDJoins := models.StashIDsFromInput(input.StashIds)
if err := qb.UpdateStashIDs(performerID, stashIDJoins); err != nil {
return err
}
}
return nil
}); err != nil {
return nil, err
}
@@ -191,13 +212,35 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
}
func (r *mutationResolver) PerformerDestroy(ctx context.Context, input models.PerformerDestroyInput) (bool, error) {
qb := models.NewPerformerQueryBuilder()
tx := database.DB.MustBeginTx(ctx, nil)
if err := qb.Destroy(input.ID, tx); err != nil {
_ = tx.Rollback()
id, err := strconv.Atoi(input.ID)
if err != nil {
return false, err
}
if err := tx.Commit(); err != nil {
if err := r.withTxn(ctx, func(repo models.Repository) error {
return repo.Performer().Destroy(id)
}); err != nil {
return false, err
}
return true, nil
}
func (r *mutationResolver) PerformersDestroy(ctx context.Context, performerIDs []string) (bool, error) {
ids, err := utils.StringSliceToIntSlice(performerIDs)
if err != nil {
return false, err
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Performer()
for _, id := range ids {
if err := qb.Destroy(id); err != nil {
return err
}
}
return nil
}); err != nil {
return false, err
}
return true, nil

View File

@@ -0,0 +1,48 @@
package api
import (
"context"
"net/http"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin/common"
)
func (r *mutationResolver) RunPluginTask(ctx context.Context, pluginID string, taskName string, args []*models.PluginArgInput) (string, error) {
currentUser := getCurrentUserID(ctx)
var cookie *http.Cookie
var err error
if currentUser != nil {
cookie, err = createSessionCookie(*currentUser)
if err != nil {
return "", err
}
}
serverConnection := common.StashServerConnection{
Scheme: "http",
Port: config.GetPort(),
SessionCookie: cookie,
Dir: config.GetConfigPath(),
}
if HasTLSConfig() {
serverConnection.Scheme = "https"
}
manager.GetInstance().RunPluginTask(pluginID, taskName, args, serverConnection)
return "todo", nil
}
func (r *mutationResolver) ReloadPlugins(ctx context.Context) (bool, error) {
err := manager.GetInstance().PluginCache.ReloadPlugins()
if err != nil {
logger.Errorf("Error reading plugin configs: %s", err.Error())
}
return true, nil
}

Some files were not shown because too many files have changed in this diff Show More