Compare commits

...

15 Commits

Author SHA1 Message Date
0523c0f909 stashed 2021-04-05 20:40:23 -04:00
5e83740f57 Do Grid View Better 2021-03-04 01:06:09 -05:00
eba444459b Migrate to bimg & remove exif dependency 2021-03-01 11:49:28 -05:00
745d843af7 Lazy Loading & Pagination, Alpha Channel -> White Conversion, Add Height & Width to DB & API 2021-02-28 19:23:41 -05:00
6827a2994a Add Logo to App 2021-02-22 01:37:41 -05:00
a3e3544ee8 Add Icon 2021-02-22 01:05:25 -05:00
ae1b9ca6e8 Center Readme 2021-02-22 01:04:29 -05:00
b1904a0a7d Center Image 2021-02-22 01:02:52 -05:00
123d4436d5 Resize 2021-02-22 00:33:17 -05:00
35ebf90f0a Add Logo 2021-02-22 00:31:07 -05:00
01f6509c99 Clean up dependencies 2021-02-21 23:18:34 -05:00
f4273ebfd6 Update README 2021-02-21 22:52:18 -05:00
f46db58ed1 Woooo! Login & Gallery Loading Working! 2021-02-21 22:36:25 -05:00
901a69bb91 On the fly image conversion 2021-02-21 12:31:03 -05:00
5212d7bf70 Documentation, Basic Login Workflow 2021-02-20 14:10:25 -05:00
78 changed files with 2866 additions and 1156 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
imagini.db
media/
notes
web/node_modules/
web/dist/
build/

View File

@@ -1,22 +1,44 @@
#uImagini
<p align="center">
<img src="https://gitea.va.reichard.io/evan/imagini/raw/branch/master/assets/imagini_full.png" width="300">
</p>
## Running Server
# cd ./cmd/
CONFIG_PATH=$(pwd) DATA_PATH=$(pwd) go run main.go serve
---
## Generate GraphQL Models
# cd ./cmd/
A self hosted photo library with user management & authentication. Cross platform Client supporting Android, iOS, and Web.
## Client
See `web_native` [subfolder](./web_native/README.md)
## Server
### Features / Roadmap
- [DONE] DB w/ user management - bcrypt salt & hash
- [DONE] JWT Access & Refresh Tokens
- [DONE] GraphQL API
- [DONE] GraphQL multipart upload
- [DONE] GraphQL basic filtering, ordering, pagination
- [DONE] Uploading images - exif extraction (load db with lat, long, etc)
- [DONE] Dynamic image conversion (heif support, width params)
- [TODO] ALL the tests
- [TODO] GraphQL & DB deletes & update
- [TODO] Dockerfile
- [TODO] Resolving GraphQL nested queries (e.g. albums, tags)
- [TODO] GraphQL nested filters
- [TODO] Lots more... TBD
### Dependencies
- libvips 8.8+
### Running
CONFIG_PATH=$(pwd) DATA_PATH=$(pwd) go run cmd/main.go serve
## Building
# Generate GraphQL Models
go run github.com/99designs/gqlgen generate
go run main.go generate
go run cmd/main.go generate
## Generate GraphQL Documentation
# From app root
# Generate GraphQL Documentation
graphdoc -e http://localhost:8484/query -o ./docs/schema
## Server Build
## Flutter Build
flutter run
## Generate GraphQL Flutter Models
flutter pub run build_runner build

BIN
assets/imagini_full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

174
assets/imagini_full.svg Normal file
View File

@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="2048"
height="2201.7515"
id="svg2"
version="1.1"
inkscape:version="1.0.2 (e86c8708, 2021-01-15)"
sodipodi:docname="temp.svg"
inkscape:export-filename="./Fotografia Originuum - Símbolo Logotipo - 1.0.0 - 2048x2048.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.23174669"
inkscape:cx="2237.7535"
inkscape:cy="935.0222"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-global="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1680"
inkscape:window-height="997"
inkscape:window-x="91"
inkscape:window-y="28"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:document-rotation="0">
<sodipodi:guide
id="guide3862"
position="0,423.28277"
orientation="2048.0,0" />
<sodipodi:guide
id="guide3864"
position="1024,423.28277"
orientation="2048.0,0" />
<sodipodi:guide
id="guide3866"
position="2048,423.28277"
orientation="2048.0,0" />
<sodipodi:guide
id="guide3868"
position="1367.8728,-145.63908"
orientation="0,2048.0" />
<sodipodi:guide
id="guide3870"
position="0,1447.2828"
orientation="0,2048.0" />
<sodipodi:guide
id="guide3872"
position="0,2471.2828"
orientation="0,2048.0" />
</sodipodi:namedview>
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-256.4375,1297.5312)"
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Camada 1">
<g
id="g3897"
transform="translate(0,69.535705)">
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#1a1a1a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:22.6;marker:none;enable-background:accumulate"
d="m 1423.7287,-1367.0669 c -223.8882,0 -218.1856,238.1299 -415.169,243.0944 H 860.5756 833.6694 760.77236 461.36265 410.05314 406.61165 c -83.19648,0 -150.17415,66.9777 -150.17415,150.17418 V -8.30366 c 0,83.19648 66.97767,150.17415 150.17415,150.17415 H 2154.2633 c 83.1965,0 150.1742,-66.97767 150.1742,-150.17415 v -965.49466 c 0,-83.19648 -66.9777,-150.17418 -150.1742,-150.17418 h -170.5102 c -196.9835,-4.9645 -191.2807,-243.0944 -415.1689,-243.0944 z m -891.3462,154.5543 c -39.29917,0 -71.01985,30.2859 -71.01985,68.204 h 299.40971 c 0,-37.9181 -31.40792,-68.204 -70.707,-68.204 z"
id="path3829"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sccccccsssssssscsssccss" />
<ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:50.0581;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
id="path3772"
cx="1496.1152"
cy="-483.31482"
rx="460.15295"
ry="460.15305" />
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
id="rect3797"
width="258.54865"
height="144.11386"
x="1366.8821"
y="-1317.5764"
ry="72.056931" />
</g>
<g
inkscape:label="Layer 1"
id="layer1-1"
transform="matrix(1.7826087,0,0,1.7826087,613.92339,-1368.5121)">
<path
style="fill:#ef00ef;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 495.06889,306.5449 c -78.2212,0 -147.3233,39.05231 -188.875,98.71875 l 68.8437,119.28125 125.8438,-217.9375 c -1.9315,-0.0479 -3.8695,-0.0625 -5.8125,-0.0625 z"
id="path3841"
inkscape:connector-curvature="0" />
<path
style="fill:#ff5151;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 514.31889,307.3574 -68.875,119.28125 h 251.6875 C 661.03789,360.41854 593.35419,313.91127 514.31889,307.3574 Z"
id="path3839"
inkscape:connector-curvature="0" />
<path
style="fill:#5d5dff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 298.81889,416.51365 c -21.4218,34.94341 -33.75,76.04095 -33.75,120.03125 0,35.02939 7.837,68.22126 21.8437,97.9375 h 137.75 z"
id="path3837"
inkscape:connector-curvature="0" />
<path
style="fill:#ff8c41;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 565.44389,438.63865 125.8437,217.9375 c 21.4218,-34.94341 33.7813,-76.04095 33.7813,-120.03125 0,-35.01463 -7.8482,-68.1996 -21.8438,-97.90625 z"
id="path3835"
inkscape:connector-curvature="0" />
<path
style="fill:#ffff5d;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="M 615.03759,548.57615 489.22509,766.4824 c 1.9418,0.0484 3.8903,0.0625 5.8438,0.0625 78.2094,0 147.2899,-39.03629 188.8437,-98.6875 z"
id="path3833"
inkscape:connector-curvature="0" />
<path
style="fill:#5fff5f;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 293.00639,646.4824 c 36.098,66.2005 103.761,112.69668 182.7812,119.25 l 68.8438,-119.25 z"
id="path3828"
inkscape:connector-curvature="0" />
</g>
<text
xml:space="preserve"
style="font-size:614.817px;line-height:1.25;font-family:sans-serif;stroke-width:3.20218"
x="214.89394"
y="780.23138"
id="text455"
transform="scale(1.0039787,0.99603708)"><tspan
sodipodi:role="line"
id="tspan453"
x="214.89394"
y="780.23138"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'.Keyboard';-inkscape-font-specification:'.Keyboard';stroke-width:3.20218">imagini</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
assets/imagini_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

196
assets/imagini_logo.svg Normal file
View File

@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="2400"
height="2400"
id="svg2"
version="1.1"
inkscape:version="1.0.2 (e86c8708, 2021-01-15)"
sodipodi:docname="imagini_logo.svg"
inkscape:export-filename="./Fotografia Originuum - Símbolo Logotipo - 1.0.0 - 2048x2048.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.23174669"
inkscape:cx="2512.9998"
inkscape:cy="1771.1564"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-global="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1680"
inkscape:window-height="997"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:document-rotation="0">
<sodipodi:guide
id="guide3862"
position="176,176"
orientation="2048.0,0" />
<sodipodi:guide
id="guide3864"
position="1200,176"
orientation="2048.0,0" />
<sodipodi:guide
id="guide3866"
position="2224,176"
orientation="2048.0,0" />
<sodipodi:guide
id="guide3868"
position="1543.8728,-392.92185"
orientation="0,2048.0" />
<sodipodi:guide
id="guide3870"
position="176,1200"
orientation="0,2048.0" />
<sodipodi:guide
id="guide3872"
position="176,2224"
orientation="0,2048.0" />
</sodipodi:namedview>
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient78">
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="0"
id="stop74" />
<stop
style="stop-color:#e6e6e6;stop-opacity:1"
offset="1"
id="stop76" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient78"
id="radialGradient72"
cx="1280.4375"
cy="-543.0625"
fx="1280.4375"
fy="-543.0625"
r="1200"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-1.1067567e-8,1.8164746,-1.9529083,-2.1450912e-6,219.88626,-2868.9458)" />
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-80.4375,1743.0625)"
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Camada 1">
<rect
id="rect32"
width="2400"
height="2400"
x="80.4375"
y="-1743.0625"
style="fill:url(#radialGradient72);stroke-width:0.9016;fill-opacity:1"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<g
id="g3897"
transform="translate(0,69.535705)">
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#1a1a1a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:22.6;marker:none;enable-background:accumulate"
d="m 1423.7287,-1367.0669 c -223.8882,0 -218.1856,238.1299 -415.169,243.0944 H 860.5756 833.6694 760.77236 461.36265 410.05314 406.61165 c -83.19648,0 -150.17415,66.9777 -150.17415,150.17418 V -8.30366 c 0,83.19648 66.97767,150.17415 150.17415,150.17415 H 2154.2633 c 83.1965,0 150.1742,-66.97767 150.1742,-150.17415 v -965.49466 c 0,-83.19648 -66.9777,-150.17418 -150.1742,-150.17418 h -170.5102 c -196.9835,-4.9645 -191.2807,-243.0944 -415.1689,-243.0944 z m -891.3462,154.5543 c -39.29917,0 -71.01985,30.2859 -71.01985,68.204 h 299.40971 c 0,-37.9181 -31.40792,-68.204 -70.707,-68.204 z"
id="path3829"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sccccccsssssssscsssccss" />
<ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#ffffff;stroke-width:50.0581;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate"
id="path3772"
cx="1496.1152"
cy="-483.31482"
rx="460.15295"
ry="460.15305" />
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;marker:none;enable-background:accumulate"
id="rect3797"
width="258.54865"
height="144.11386"
x="1366.8821"
y="-1317.5764"
ry="72.056931" />
</g>
<g
inkscape:label="Layer 1"
id="layer1-1"
transform="matrix(1.7826087,0,0,1.7826087,613.92339,-1368.5121)">
<path
style="fill:#ef00ef;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 495.06889,306.5449 c -78.2212,0 -147.3233,39.05231 -188.875,98.71875 l 68.8437,119.28125 125.8438,-217.9375 c -1.9315,-0.0479 -3.8695,-0.0625 -5.8125,-0.0625 z"
id="path3841"
inkscape:connector-curvature="0" />
<path
style="fill:#ff5151;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 514.31889,307.3574 -68.875,119.28125 h 251.6875 C 661.03789,360.41854 593.35419,313.91127 514.31889,307.3574 Z"
id="path3839"
inkscape:connector-curvature="0" />
<path
style="fill:#5d5dff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 298.81889,416.51365 c -21.4218,34.94341 -33.75,76.04095 -33.75,120.03125 0,35.02939 7.837,68.22126 21.8437,97.9375 h 137.75 z"
id="path3837"
inkscape:connector-curvature="0" />
<path
style="fill:#ff8c41;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 565.44389,438.63865 125.8437,217.9375 c 21.4218,-34.94341 33.7813,-76.04095 33.7813,-120.03125 0,-35.01463 -7.8482,-68.1996 -21.8438,-97.90625 z"
id="path3835"
inkscape:connector-curvature="0" />
<path
style="fill:#ffff5d;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="M 615.03759,548.57615 489.22509,766.4824 c 1.9418,0.0484 3.8903,0.0625 5.8438,0.0625 78.2094,0 147.2899,-39.03629 188.8437,-98.6875 z"
id="path3833"
inkscape:connector-curvature="0" />
<path
style="fill:#5fff5f;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 293.00639,646.4824 c 36.098,66.2005 103.761,112.69668 182.7812,119.25 l 68.8438,-119.25 z"
id="path3828"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -5,6 +5,7 @@ import (
"net/http"
"time"
"github.com/h2non/bimg"
log "github.com/sirupsen/logrus"
"reichard.io/imagini/internal/api"
@@ -44,6 +45,9 @@ func (s *Server) StartServer() {
}
go func() {
bimg.VipsCacheSetMax(0)
bimg.VipsCacheSetMaxMem(0)
err := s.httpServer.ListenAndServe()
if err != nil {
log.Error("Error starting server ", err)

8
go.mod
View File

@@ -4,21 +4,15 @@ go 1.15
require (
github.com/99designs/gqlgen v0.13.0
github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/disintegration/imaging v1.6.2 // indirect
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483
github.com/gabriel-vasile/mimetype v1.1.2
github.com/google/uuid v1.1.5
github.com/h2non/bimg v1.1.5
github.com/iancoleman/strcase v0.1.3
github.com/lestrrat-go/jwx v1.0.8
github.com/mattn/go-sqlite3 v1.14.6
github.com/sirupsen/logrus v1.7.0
github.com/tus/tusd v1.4.0
github.com/urfave/cli/v2 v2.3.0
github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.20.9
)

228
go.sum
View File

@@ -1,122 +1,44 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro=
github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA=
github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/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/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/aws/aws-sdk-go v1.20.1/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/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha5FFErxK+sr6sWxQovRMzwMhejo=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 h1:/wmSAm0UZlQ/NmiwUlThjUUHe2cD36rjM61px1X6ccM=
github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33/go.mod h1:kXwwWC7hMGnPrV6bw8gFi2k0ZLTiYTNMM+G3D9cUBt0=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dsoprea/go-exif v0.0.0-20201216222538-db167117f483 h1:zJb7OUzMMSul61UUhYXWNOXc9nO1lexj3jsAgoDtCqg=
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483 h1:rz9dPf+Unge2D5RNIRNFvCc2OrGfhAPuxx4L6412jdI=
github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d h1:F/7L5wr/fP/SKeO5HuMlNEX9Ipyx2MbH2rV9G4zJRpk=
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU=
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e h1:IxIbA7VbCNrwumIYjDoMOdf4KOSkMC6NJE4s8oRbE7E=
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU=
github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
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-macaron/gzip v0.0.0-20200329073552-98214d7a897e h1:PlmAvovRGUTW15weOGR3gny33PCUL2Ko65rN1w1XBog=
github.com/go-macaron/gzip v0.0.0-20200329073552-98214d7a897e/go.mod h1:1if9hBU2ZPlrmuwN27VIn11Ur9OXBiZDLDPmCKbb7N4=
github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI=
github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc=
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
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.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/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/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/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/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
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/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/h2non/bimg v1.1.5 h1:o3xsUBxM8s7+e7PmpiWIkEYdeYayJ94eh4cJLx67m1k=
github.com/h2non/bimg v1.1.5/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
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/iancoleman/strcase v0.1.3 h1:dJBk1m2/qjL1twPLf68JND55vvivMupZ4wIzE8CTdBw=
github.com/iancoleman/strcase v0.1.3/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
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/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/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lestrrat-go/backoff/v2 v2.0.3 h1:2ABaTa5ifB1L90aoRMjaPa97p0WzzVe93Vggv8oZftw=
github.com/lestrrat-go/backoff/v2 v2.0.3/go.mod h1:mU93bMXuG27/Y5erI5E9weqavpTX5qiVFZI4uXAX0xk=
@@ -128,6 +50,7 @@ github.com/lestrrat-go/jwx v1.0.8 h1:Mj/2Ey9rkGx4w5IMQ2Q+9KLZn4cZoMgKrnMxi9eXE3k
github.com/lestrrat-go/jwx v1.0.8/go.mod h1:6XJ5sxHF5U116AxYxeHfTnfsZRMgmeKY214zwZDdvho=
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35 h1:lea8Wt+1ePkVrI2/WD+NgQT5r/XsLAzxeqtyFLcEs10=
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/pdebug/v3 v3.0.0-20210111091911-ec4f5c88c087 h1:T5Wh8C/p5nWoGuEUBQj+daEXkj1CScB9GshvvsBJhpg=
github.com/lestrrat-go/pdebug/v3 v3.0.0-20210111091911-ec4f5c88c087/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
@@ -135,65 +58,35 @@ github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIG
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
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.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
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/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/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
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/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tus/tusd v1.4.0 h1:8+TmY/Ip2mxO3UlYzrqUkQWMd/FmSSdcIrlJHrr+3HQ=
github.com/tus/tusd v1.4.0/go.mod h1:ygrT4B9ZSb27dx3uTnobX5nOFDnutBL6iWKLH4+KpA0=
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
@@ -201,88 +94,33 @@ github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWp
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/vimeo/go-util v1.2.0/go.mod h1:s13SMDTSO7AjH1nbgp707mfN5JFIWUFDU5MDDuRRtKs=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
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=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/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-20181116152217-5ac8a444bdc5/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-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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/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-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290 h1:NXNmtp0ToD36cui5IqWy95LC4Y6vT/4y3RnPxlQPinU=
@@ -291,49 +129,19 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
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=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/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-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
gopkg.in/Acconut/lockfile.v1 v1.1.0/go.mod h1:6UCz3wJ8tSFUsPR6uP/j8uegEtDuEEqFxlpi0JI4Umw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/macaron.v1 v1.3.5/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
gopkg.in/macaron.v1 v1.4.0 h1:RJHC09fAnQ8tuGUiZNjG0uyL1BWSdSWd9SpufIcEArQ=
gopkg.in/macaron.v1 v1.4.0/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/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.7/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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.9 h1:M3aIZKXAC1PtPVu9t3WGwkBTE1le5c2telz3I/qjRNg=
gorm.io/gorm v1.20.9/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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=

View File

@@ -87,6 +87,7 @@ type ComplexityRoot struct {
CreatedAt func(childComplexity int) int
ExifDate func(childComplexity int) int
FileName func(childComplexity int) int
Height func(childComplexity int) int
ID func(childComplexity int) int
IsVideo func(childComplexity int) int
Latitude func(childComplexity int) int
@@ -95,6 +96,7 @@ type ComplexityRoot struct {
Tags func(childComplexity int) int
UpdatedAt func(childComplexity int) int
UserID func(childComplexity int) int
Width func(childComplexity int) int
}
MediaItemResponse struct {
@@ -363,6 +365,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.MediaItem.FileName(childComplexity), true
case "MediaItem.height":
if e.complexity.MediaItem.Height == nil {
break
}
return e.complexity.MediaItem.Height(childComplexity), true
case "MediaItem.id":
if e.complexity.MediaItem.ID == nil {
break
@@ -419,6 +428,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.MediaItem.UserID(childComplexity), true
case "MediaItem.width":
if e.complexity.MediaItem.Width == nil {
break
}
return e.complexity.MediaItem.Width(childComplexity), true
case "MediaItemResponse.data":
if e.complexity.MediaItemResponse.Data == nil {
break
@@ -1019,6 +1035,8 @@ type MediaItem {
isVideo: Boolean! @meta(gorm: "default:false;not null")
fileName: String! @meta(gorm: "not null")
origName: String! @meta(gorm: "not null")
width: Int! @meta(gorm: "not null")
height: Int! @meta(gorm: "not null")
tags: [Tag] @meta(gorm: "many2many:media_tags;foreignKey:ID,UserID;References:ID")
albums: [Album] @meta(gorm: "many2many:media_albums;foreignKey:ID,UserID;Refrences:ID")
userID: ID! @meta(gorm: "not null")
@@ -2937,6 +2955,124 @@ func (ec *executionContext) _MediaItem_origName(ctx context.Context, field graph
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _MediaItem_width(ctx context.Context, field graphql.CollectedField, obj *model.MediaItem) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MediaItem",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Width, nil
}
directive1 := func(ctx context.Context) (interface{}, error) {
gorm, err := ec.unmarshalOString2ᚖstring(ctx, "not null")
if err != nil {
return nil, err
}
if ec.directives.Meta == nil {
return nil, errors.New("directive meta is not implemented")
}
return ec.directives.Meta(ctx, obj, directive0, gorm)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(int); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
fc.Result = res
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _MediaItem_height(ctx context.Context, field graphql.CollectedField, obj *model.MediaItem) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "MediaItem",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Height, nil
}
directive1 := func(ctx context.Context) (interface{}, error) {
gorm, err := ec.unmarshalOString2ᚖstring(ctx, "not null")
if err != nil {
return nil, err
}
if ec.directives.Meta == nil {
return nil, errors.New("directive meta is not implemented")
}
return ec.directives.Meta(ctx, obj, directive0, gorm)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(int); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
fc.Result = res
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _MediaItem_tags(ctx context.Context, field graphql.CollectedField, obj *model.MediaItem) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -7615,6 +7751,16 @@ func (ec *executionContext) _MediaItem(ctx context.Context, sel ast.SelectionSet
if out.Values[i] == graphql.Null {
invalids++
}
case "width":
out.Values[i] = ec._MediaItem_width(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "height":
out.Values[i] = ec._MediaItem_height(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "tags":
out.Values[i] = ec._MediaItem_tags(ctx, field, obj)
case "albums":

View File

@@ -4,12 +4,13 @@ import (
"context"
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/dsoprea/go-exif/v3"
exifcommon "github.com/dsoprea/go-exif/v3/common"
"github.com/google/uuid"
"github.com/h2non/bimg"
log "github.com/sirupsen/logrus"
"reichard.io/imagini/graph/model"
)
@@ -76,45 +77,50 @@ func deriveDeviceType(r *http.Request) model.DeviceType {
return model.DeviceTypeUnknown
}
func mediaItemFromEXIFData(filePath string) (*model.MediaItem, error) {
rawExif, err := exif.SearchFileAndExtractExif(filePath)
entries, _, err := exif.GetFlatExifData(rawExif, nil)
decLong := float64(1)
decLat := float64(1)
func mediaItemFromEXIF(meta bimg.EXIF) *model.MediaItem {
mediaItem := &model.MediaItem{}
for _, v := range entries {
if v.TagName == "DateTimeOriginal" {
formattedTime, _ := time.Parse("2006:01:02 15:04:05", v.Formatted)
// DateTimeOriginal
formattedTime, _ := time.Parse("2006:01:02 15:04:05", meta.DateTimeOriginal)
mediaItem.ExifDate = &formattedTime
} else if v.TagName == "GPSLatitude" {
latStruct := v.Value.([]exifcommon.Rational)
decLat *= deriveDecimalCoordinate(
latStruct[0].Numerator/latStruct[0].Denominator,
latStruct[1].Numerator/latStruct[1].Denominator,
float64(latStruct[2].Numerator)/float64(latStruct[2].Denominator),
)
} else if v.TagName == "GPSLongitude" {
longStruct := v.Value.([]exifcommon.Rational)
decLong *= deriveDecimalCoordinate(
longStruct[0].Numerator/longStruct[0].Denominator,
longStruct[1].Numerator/longStruct[1].Denominator,
float64(longStruct[2].Numerator)/float64(longStruct[2].Denominator),
)
} else if v.TagName == "GPSLatitudeRef" && v.Formatted == "S" {
decLat *= -1
} else if v.TagName == "GPSLongitudeRef" && v.Formatted == "W" {
decLong *= -1
}
// GPSLatitude / Ref
mediaItem.Latitude = deriveDecimalCoordinate(meta.GPSLatitude, meta.GPSLatitudeRef)
// GPSLongitude / Ref
mediaItem.Longitude = deriveDecimalCoordinate(meta.GPSLongitude, meta.GPSLongitudeRef)
return mediaItem
}
mediaItem.Latitude = &decLat
mediaItem.Longitude = &decLong
func deriveDecimalCoordinate(coords, direction string) *float64 {
divideBy := [3]float64{1, 60, 3600}
var calculatedResult float64
return mediaItem, err
splitCoords := strings.Split(coords, " ")
if len(splitCoords) != 3 {
return nil
}
func deriveDecimalCoordinate(degrees, minutes uint32, seconds float64) float64 {
return float64(degrees) + (float64(minutes) / 60) + (seconds / 3600)
for i := 0; i < len(splitCoords); i++ {
splitSection := strings.Split(splitCoords[i], "/")
numerator, err := strconv.ParseFloat(splitSection[0], 64)
if err != nil {
return nil
}
denominator, err := strconv.ParseFloat(splitSection[1], 64)
if err != nil {
return nil
}
calculatedResult += numerator / denominator / divideBy[i]
}
if direction == "S" {
calculatedResult *= -1
} else if direction == "W" {
calculatedResult *= -1
}
log.Info(calculatedResult)
return &calculatedResult
}

View File

@@ -108,6 +108,8 @@ type MediaItem struct {
IsVideo bool `json:"isVideo" gorm:"default:false;not null"`
FileName string `json:"fileName" gorm:"not null"`
OrigName string `json:"origName" gorm:"not null"`
Width int `json:"width" gorm:"not null"`
Height int `json:"height" gorm:"not null"`
Tags []*Tag `json:"tags" gorm:"many2many:media_tags;foreignKey:ID,UserID;References:ID"`
Albums []*Album `json:"albums" gorm:"many2many:media_albums;foreignKey:ID,UserID;Refrences:ID"`
UserID string `json:"userID" gorm:"not null"`

View File

@@ -158,6 +158,8 @@ type MediaItem {
isVideo: Boolean! @meta(gorm: "default:false;not null")
fileName: String! @meta(gorm: "not null")
origName: String! @meta(gorm: "not null")
width: Int! @meta(gorm: "not null")
height: Int! @meta(gorm: "not null")
tags: [Tag] @meta(gorm: "many2many:media_tags;foreignKey:ID,UserID;References:ID")
albums: [Album] @meta(gorm: "many2many:media_albums;foreignKey:ID,UserID;Refrences:ID")
userID: ID! @meta(gorm: "not null")

View File

@@ -8,6 +8,7 @@ import (
"context"
"errors"
"io"
"io/ioutil"
"net/http"
"os"
"path"
@@ -16,6 +17,8 @@ import (
"github.com/gabriel-vasile/mimetype"
"github.com/google/uuid"
"github.com/h2non/bimg"
log "github.com/sirupsen/logrus"
"reichard.io/imagini/graph/generated"
"reichard.io/imagini/graph/model"
)
@@ -32,6 +35,7 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
// Copy headers into the buffer
if _, err := input.File.File.Read(fileHeader); err != nil {
log.Error("[upload] Failed to read file header:", err)
return nil, errors.New("Upload Failed")
}
@@ -42,8 +46,12 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
if strings.HasPrefix(contentType, "image/") {
isVideo = false
} else if strings.HasPrefix(contentType, "video/") {
isVideo = true
// TODO
log.Error("[upload] Video unsupported at this time")
return nil, errors.New("Upload Failed")
// isVideo = true
} else {
log.Error("[upload] File is neither an image or video")
return nil, errors.New("Upload Failed")
}
@@ -55,28 +63,49 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
filePath := path.Join(folderPath + "/" + fileName)
// Create File
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0600)
f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
log.Error("[upload] Unable to open file handle:", err)
return nil, errors.New("Upload Failed")
}
defer f.Close()
// Copy header to file
_, err = io.Copy(f, bytes.NewReader(fileHeader))
// Concat File
concatFile := io.MultiReader(bytes.NewReader(fileHeader), input.File.File)
// Copy file
_, err = io.Copy(f, concatFile)
if err != nil {
log.Error("[upload] Unable to copy file:", err)
return nil, errors.New("Upload Failed")
}
// Copy remaining file
_, err = io.Copy(f, input.File.File)
// Load Image
f.Seek(0, io.SeekStart)
buffer, err := ioutil.ReadAll(f)
newImage := bimg.NewImage(buffer)
// Create MediaItem from EXIF
meta, err := newImage.Metadata()
if err != nil {
log.Error("[upload] Unable to extract metadata:", err)
return nil, errors.New("Upload Failed")
}
mediaItem := mediaItemFromEXIF(meta.EXIF)
// Determine Image Size
imageSize, err := newImage.Size()
if err != nil {
log.Error("[upload] Unable to extract dimension data:", err)
return nil, errors.New("Upload Failed")
}
// Create MediaItem From EXIF Data
mediaItem, err := mediaItemFromEXIFData(filePath)
if err != nil {
return nil, errors.New("Upload Failed")
if meta.Orientation > 4 {
mediaItem.Width = imageSize.Height
mediaItem.Height = imageSize.Width
} else {
mediaItem.Width = imageSize.Width
mediaItem.Height = imageSize.Height
}
// Add Additional MediaItem Fields
@@ -89,6 +118,7 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
// Create MediaItem in DB
err = r.DB.CreateMediaItem(mediaItem)
if err != nil {
log.Error("[upload] Unable to populate create file in DB:", err)
return nil, errors.New("Upload Failed")
}

5
init_pkg_config_path Executable file
View File

@@ -0,0 +1,5 @@
# nix-env -i vips
PKG1=$(find /nix/store/*/lib/pkgconfig/* -name 'vips.pc' | sed 's/vips\.pc$//')
PKG2=$(find /nix/store/*/lib/pkgconfig/* -name 'gobject-2.0.pc' | sed 's/gobject-2.0\.pc$//')
export PKG_CONFIG_PATH=$PKG1:$PKG2

View File

@@ -41,35 +41,46 @@ func (api *API) refreshTokens(refreshToken jwt.Token) (string, string, error) {
}
// Update Access Token
accessTokenCookie, err := api.Auth.CreateJWTAccessToken(user, device)
accessToken, err := api.Auth.CreateJWTAccessToken(user, device)
if err != nil {
return "", "", err
}
return accessTokenCookie, "", err
return accessToken, "", err
}
func (api *API) validateTokens(w *http.ResponseWriter, r *http.Request) (jwt.Token, error) {
// TODO: Check from X-Imagini-AccessToken
// TODO: Check from X-Imagini-RefreshToken
// Validate Access Token
accessCookie, _ := r.Cookie("AccessToken")
if accessCookie != nil {
accessToken, err := api.Auth.ValidateJWTAccessToken(accessCookie.Value)
accessTokenHeader := r.Header.Get("X-Imagini-AccessToken")
if accessTokenHeader != "" {
accessToken, err := api.Auth.ValidateJWTAccessToken(accessTokenHeader)
if err == nil {
return accessToken, nil
}
}
// Validate Refresh Cookie Exists
refreshCookie, _ := r.Cookie("RefreshToken")
if refreshCookie == nil {
refreshTokenHeader := r.Header.Get("X-Imagini-RefreshToken")
if refreshTokenHeader == "" {
return nil, errors.New("Tokens Invalid")
}
// Validate Access Token
// accessCookie, _ := r.Cookie("AccessToken")
// if accessCookie != nil {
// accessToken, err := api.Auth.ValidateJWTAccessToken(accessCookie.Value)
// if err == nil {
// return accessToken, nil
// }
// }
// Validate Refresh Cookie Exists
// refreshCookie, _ := r.Cookie("RefreshToken")
// if refreshCookie == nil {
// return nil, errors.New("Tokens Invalid")
// }
// Validate Refresh Token
refreshToken, err := api.Auth.ValidateJWTRefreshToken(refreshCookie.Value)
// refreshToken, err := api.Auth.ValidateJWTRefreshToken(refreshCookie.Value)
refreshToken, err := api.Auth.ValidateJWTRefreshToken(refreshTokenHeader)
if err != nil {
return nil, errors.New("Tokens Invalid")
}
@@ -81,21 +92,21 @@ func (api *API) validateTokens(w *http.ResponseWriter, r *http.Request) (jwt.Tok
}
// TODO: Actually Refresh Refresh Token
newRefreshToken = refreshCookie.Value
// newRefreshToken = refreshCookie.Value
newRefreshToken = refreshTokenHeader
// Set appropriate cookies (TODO: Only for web!)
// Update Access & Refresh Cookies
http.SetCookie(*w, &http.Cookie{
Name: "AccessToken",
Value: newAccessToken,
})
http.SetCookie(*w, &http.Cookie{
Name: "RefreshToken",
Value: newRefreshToken,
})
// http.SetCookie(*w, &http.Cookie{
// Name: "AccessToken",
// Value: newAccessToken,
// })
// http.SetCookie(*w, &http.Cookie{
// Name: "RefreshToken",
// Value: newRefreshToken,
// })
// Only for iOS & Android (TODO: Remove for web! Only cause affected by CORS during development)
(*w).Header().Set("X-Imagini-AccessToken", newAccessToken)
(*w).Header().Set("X-Imagini-RefreshToken", newRefreshToken)

View File

@@ -1,10 +1,16 @@
package api
import (
"bytes"
"errors"
"net/http"
"os"
"path"
"strconv"
"time"
"github.com/h2non/bimg"
log "github.com/sirupsen/logrus"
"reichard.io/imagini/graph/model"
)
@@ -23,15 +29,7 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) {
}
// Acquire Width & Height Parameters
query := r.URL.Query()
width := query["width"]
height := query["height"]
_ = width
_ = height
// TODO: Caching & Resizing
// - If both, force resize with new scale
// - If one, scale resize proportionally
width, _ := strconv.Atoi(r.URL.Query().Get("width"))
// Pull out userID
authContext := r.Context().Value("auth").(*model.AuthContext)
@@ -46,10 +44,55 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) {
// Check if File Exists
_, err := os.Stat(mediaPath)
if os.IsNotExist(err) {
// TODO: Different HTTP Response Code?
w.WriteHeader(http.StatusMethodNotAllowed)
log.Warn("[media] Image does not exist on disk")
w.WriteHeader(http.StatusInternalServerError)
return
}
http.ServeFile(w, r, mediaPath)
mediaFile, err := resizeAndConvertImageBIMG(mediaPath, width)
//mediaFile, err := resizeAndConvertImage(mediaPath, width)
if err != nil {
log.Warn("[media] Image conversion failed:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
// TODO: Cache mediaFile
http.ServeContent(w, r, fileName, time.Time{}, bytes.NewReader(mediaFile))
}
func resizeAndConvertImageBIMG(path string, desiredWidth int) ([]byte, error) {
buffer, err := bimg.Read(path)
if err != nil {
return nil, errors.New("[media] Unable to read image")
}
inputImage := bimg.NewImage(buffer)
// Return RAW
if inputImage.Type() == "jpeg" && desiredWidth == 0 {
return buffer, nil
}
imageSize, err := inputImage.Size()
options := bimg.Options{
Quality: 100,
// NoAutoRotate: true,
Background: bimg.Color{R: 255, G: 255, B: 255},
}
// Set dimensions
if desiredWidth != 0 && imageSize.Width > desiredWidth {
options.Width = desiredWidth
options.Quality = 50
}
// Convert to JPEG
if inputImage.Type() != "jpeg" {
options.Type = bimg.JPEG
}
// Process image
return inputImage.Process(options)
}

View File

@@ -1 +0,0 @@
package api

View File

@@ -52,6 +52,20 @@ func (api *API) queryMiddleware(next http.Handler) http.Handler {
**/
func (api *API) authMiddleware(next http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TODO: REMOVE (SOME OF) THIS!! Only for developement due to CORS
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Origin", "*")
//w.Header().Set("Access-Control-Expose-Headers", "*")
w.Header().Set("Access-Control-Allow-Headers", "x-imagini-accesstoken,x-imagini-refreshtoken")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
// CORS Preflight
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
// Validate Tokens
accessToken, err := api.validateTokens(&w, r)
if err != nil {

View File

@@ -1,16 +1,30 @@
# imagini
# Imagini Client
A cross platform (iOS, Android, & Web) client used with the Imagini server.
A new Flutter project.
## Features / Roadmap
- [DONE] Login w/ errors
- [DONE] GraphQL client
- [DONE] Load & tile images for user
- [DONE] Access & Refresh Token secure storage (`localStorage` web -_-)
## Getting Started
- [TODO] ALL the tests
- [TODO] Pagination and lazy scroll load
- [TODO] File picker upload
- [TODO] Sync upload (Android & iOS)
- [TODO] Image caching (Android & iOS)
- [TODO] Lots more... TBD
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
## Running
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
# Chrome
flutter run -d chrome
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
# Simulator
open -a Simulator
flutter run
## Building
# Generate GraphQL Flutter Models
flutter pub run build_runner build

View File

@@ -2,7 +2,7 @@
package="com.reichard.imagini">
<application
android:label="Imagini"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/launcher_icon">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -0,0 +1,20 @@
query mediaItems($order: Order, $page: Page, $filter: MediaItemFilter) {
mediaItems(filter: $filter, page: $page, order: $order) {
data {
id
fileName
latitude
longitude
isVideo
width
height
origName
createdAt
}
page {
size
page
total
}
}
}

View File

@@ -1,92 +1,64 @@
PODS:
- DKImagePickerController/Core (4.3.2):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.2)
- DKImagePickerController/PhotoGallery (4.3.2):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.2)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.17):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- connectivity (0.0.1):
- Flutter
- Reachability
- Flutter (1.0.0)
- flutter_secure_storage (3.3.1):
- Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- integration_test (0.0.1):
- Flutter
- SDWebImage (5.10.2):
- SDWebImage/Core (= 5.10.2)
- SDWebImage/Core (5.10.2)
- shared_preferences (0.0.1):
- path_provider (0.0.1):
- Flutter
- SwiftyGif (5.3.0)
- url_launcher (0.0.1):
- photo_manager (0.0.1):
- Flutter
- Reachability (3.2)
- sqflite (0.0.2):
- Flutter
- FMDB (>= 2.7.5)
DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- connectivity (from `.symlinks/plugins/connectivity/ios`)
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- SDWebImage
- SwiftyGif
- FMDB
- Reachability
EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
connectivity:
:path: ".symlinks/plugins/connectivity/ios"
Flutter:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
url_launcher:
:path: ".symlinks/plugins/url_launcher/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
SPEC CHECKSUMS:
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
integration_test: 5ed24a436eb7ec17b6a13046e9bf7ca4a404e59e
SDWebImage: b969dcfc02c40a5da71eac0b03b8f1a0c794a86f
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
integration_test: 6eb66a19f7104200dcfdd62bc0077e1b09686e4f
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
photo_manager: f7c619c2cc8c2adb8d85c63363babac477de9c67
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c

View File

@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 998 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPhotoLibraryUsageDescription</key>
<string>App needs access to Gallery</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPhotoLibraryUsageDescription</key>
<string>App needs access to Gallery</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>

View File

@@ -1,61 +1,65 @@
import 'dart:async';
import "package:imagini/api/auth_client/base_auth_client.dart";
import 'package:flutter/material.dart' show BoxFit, Center, SizedBox;
// ignore: uri_does_not_exist
import 'cookie_client_stub.dart'
// ignore: uri_does_not_exist
if (dart.library.html) 'browser_cookie_client.dart'
// ignore: uri_does_not_exist
if (dart.library.io) 'io_cookie_client.dart';
import 'package:imagini/api/auth_client/auth_client.dart'
if (dart.library.html) 'package:imagini/api/auth_client/browser_auth_client.dart'
if (dart.library.io) 'package:imagini/api/auth_client/io_auth_client.dart';
import 'package:meta/meta.dart';
import 'package:imagini/core/storage_client/base_storage_client.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
class APIProvider{
String _server, _accessToken, _refreshToken;
GraphQLClient _client;
HttpLink httpLink;
// CookieLink cookieLink;
static const String _GRAPHQL_ENDPOINT = "/query";
APIProvider({
@required String server,
String accessToken,
String refreshToken
}) {
_server = server;
_accessToken = accessToken;
_refreshToken = refreshToken;
BaseStorageClient _storage;
BaseAuthClient _authClient;
GraphQLClient _client;
HttpLink httpLink;
APIProvider(BaseStorageClient storage) {
_storage = storage;
}
Future<void> init() async {
// Initialize Server
String _server = await _storage.get("server");
if (_server == null) {
_server = "http://localhost";
await _storage.set("server", _server);
}
// Initialize Auth Client
_authClient = getAuthClient(_storage);
// Initialize HTTP Link
httpLink = HttpLink(_server + _GRAPHQL_ENDPOINT,
httpClient: getCookieClient(),
httpClient: _authClient,
);
// cookieLink = CookieLink(_updateAccessToken, _updateRefreshToken);
// Initialize GraphQL Client
_client = GraphQLClient(
cache: GraphQLCache(),
link: httpLink,
);
}
// void _updateAccessToken(_accessToken) {
// print("Updating Access Token: $_accessToken");
// this._accessToken = _accessToken;
// }
// void _updateRefreshToken(_refreshToken) {
// print("Updating Refresh Token: $_accessToken");
// this._refreshToken = _refreshToken;
// }
Future<Login$Query$AuthResponse> login([
Future<QueryResult> login(
String username,
String password,
]) async {
String server,
) async {
assert(
(username != null && password != null)
(username != null && password != null && server != null)
);
// Initialize New Connection
await _storage.set("server", server);
await init();
QueryResult response = await _client.query(
QueryOptions(
document: LoginQuery().document,
@@ -65,79 +69,72 @@ class APIProvider{
},
)
);
final loginResponse = Login$Query.fromJson(response.data);
return loginResponse.login;
return response;
}
Future<Me$Query$User> me() async {
CachedNetworkImage getImage(String fileName, int width, int height) {
String server = _authClient.getServer();
String fullURL = "$server/media/$fileName?width=$width";
print(fullURL);
return CachedNetworkImage(
imageUrl: fullURL,
placeholder: (context, url) => Center(
child: SizedBox(
width: width.toDouble() / 3.5,
height: height.toDouble() / 3.5,
child: new PlatformCircularProgressIndicator(),
),
),
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
httpHeaders: _authClient.getHeaders(),
fit: BoxFit.contain,
);
}
// Future<CachedNetworkImage> getImage(String fileName, int width, int height) async {
// String server = await _storage.get("server");
// String accessToken = await _storage.get("accessToken");
// String refreshToken = await _storage.get("refreshToken");
// String fullURL = "$server/media/$fileName?width=$width";
// print(fullURL);
// return CachedNetworkImage(
// imageUrl: fullURL,
// placeholder: (context, url) => Center(
// child: SizedBox(
// width: width.toDouble() / 3.5,
// height: height.toDouble() / 3.5,
// child: new PlatformCircularProgressIndicator(),
// ),
// ),
// imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
// httpHeaders: {
// "X-Imagini-AccessToken": accessToken,
// "X-Imagini-RefreshToken": refreshToken,
// },
// fit: BoxFit.contain,
// );
// }
Future<QueryResult> me() async {
QueryResult response = await _client.query(
QueryOptions(
document: MeQuery().document,
)
);
final meResponse = Me$Query.fromJson(response.data);
return meResponse.me;
return response;
}
Future<String> mediaItems([
String startDate,
String endDate,
String albumID,
List<String> tagID,
String type, // TODO: Make enum
int page,
]) async {
// Query:
// /api/v1/MediaItems
// Derive Params:
// startDate:
// &createdAt=>2020-10-10T10:10:10
// endDate:
// &createdAt=<2020-10-10T10:10:10
// albumID:
// &albumID=9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
// tagID:
// &tagID=9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d,9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
// type:
// &type=Photos
// &type=Videos
// page:
// &page=4
// Returns:
// {
//
// }
return null;
}
Future<String> tags([
int page
]) async {
// Query:
// /api/v1/Tags
// Derive Params:
// page:
// &page=4
return null;
}
Future<String> albums([
int page
]) async {
// Query:
// /api/v1/Albums
// Derive Params:
// page:
// &page=4
return null;
}
Future<String> me() async {
return null;
Future<QueryResult> mediaItems(Page page) async {
QueryResult response = await _client.query(
QueryOptions(
document: MediaItemsQuery().document,
variables: {
"page": page,
},
)
);
return response;
}
void dispose() {}

View File

@@ -0,0 +1,4 @@
import "package:imagini/api/auth_client/base_auth_client.dart";
BaseAuthClient getAuthClient(storage) => throw UnsupportedError(
'Cannot create a client without dart:html or dart:io.');

View File

@@ -0,0 +1,6 @@
import 'package:http/http.dart';
abstract class BaseAuthClient implements BaseClient {
String getServer();
Map<String, String> getHeaders();
}

View File

@@ -0,0 +1,56 @@
import "package:http/http.dart";
import 'package:http/browser_client.dart';
import "package:imagini/api/auth_client/base_auth_client.dart";
import "package:imagini/core/storage_client/base_storage_client.dart";
BaseClient getAuthClient(storage) => BrowserAuthClient(storage);
class BrowserAuthClient extends BrowserClient implements BaseAuthClient {
BaseStorageClient _storage;
String _cachedServer;
String _cachedAccessToken;
String _cachedRefreshToken;
BrowserAuthClient(BaseStorageClient storage) {
_storage = storage;
}
@override
String getServer() {
return _cachedServer;
}
@override
Map<String, String> getHeaders() {
return {
'X-Imagini-AccessToken': _cachedAccessToken,
'X-Imagini-RefreshToken': _cachedRefreshToken,
};
}
@override
Future<StreamedResponse> send(BaseRequest request) async {
String _accessToken = await _storage.get("accessToken");
String _refreshToken = await _storage.get("refreshToken");
_cachedAccessToken = _accessToken;
_cachedRefreshToken = _refreshToken;
request.headers.addAll({
'X-Imagini-AccessToken': _accessToken,
'X-Imagini-RefreshToken': _refreshToken,
});
return super.send(request).then((response) async {
if (response.headers.containsKey("x-imagini-accesstoken")) {
_cachedAccessToken = response.headers["x-imagini-accesstoken"];
await _storage.set("accessToken", response.headers["x-imagini-accesstoken"]);
}
if (response.headers.containsKey("x-imagini-refreshtoken")) {
_cachedRefreshToken = response.headers["x-imagini-refreshtoken"];
await _storage.set("refreshToken", response.headers["x-imagini-refreshtoken"]);
}
return response;
});
}
}

View File

@@ -0,0 +1,36 @@
import 'package:http/browser_client.dart';
import "package:http/http.dart";
import "package:imagini/core/storage_client/base_storage_client.dart";
BaseClient getCookieClient(storage) => ClientWithCookies(storage);
class ClientWithCookies extends BrowserClient {
BaseStorageClient _storage;
ClientWithCookies(BaseStorageClient storage) {
_storage = storage;
}
@override
Future<StreamedResponse> send(BaseRequest request) async {
String _accessToken = await _storage.get("accessToken");
String _refreshToken = await _storage.get("refreshToken");
request.headers.addAll({
'X-Imagini-AccessToken': _accessToken,
'X-Imagini-RefreshToken': _refreshToken,
});
return super.send(request).then((response) async {
// We've been told to update our access token
if (response.headers.containsKey("x-imagini-accesstoken")) {
await _storage.set("accessToken", response.headers["x-imagini-accesstoken"]);
}
// We've been told to update our refresh token
if (response.headers.containsKey("x-imagini-refreshtoken")) {
await _storage.set("refreshToken", response.headers["x-imagini-refreshtoken"]);
}
return response;
});
}
}

View File

@@ -1,4 +1,4 @@
import 'package:http/http.dart';
BaseClient getCookieClient() => throw UnsupportedError(
BaseClient getCookieClient(storage) => throw UnsupportedError(
'Cannot create a client without dart:html or dart:io.');

View File

@@ -0,0 +1,56 @@
import "package:http/http.dart";
import 'package:http/io_client.dart';
import "package:imagini/api/auth_client/base_auth_client.dart";
import "package:imagini/core/storage_client/base_storage_client.dart";
BaseClient getAuthClient(storage) => IOAuthClient(storage);
class IOAuthClient extends IOClient implements BaseAuthClient {
BaseStorageClient _storage;
String _cachedServer;
String _cachedAccessToken;
String _cachedRefreshToken;
IOAuthClient(BaseStorageClient storage) {
_storage = storage;
}
@override
String getServer() {
return _cachedServer;
}
@override
Map<String, String> getHeaders() {
return {
'X-Imagini-AccessToken': _cachedAccessToken,
'X-Imagini-RefreshToken': _cachedRefreshToken,
};
}
@override
Future<IOStreamedResponse> send(BaseRequest request) async {
String _accessToken = await _storage.get("accessToken");
String _refreshToken = await _storage.get("refreshToken");
_cachedAccessToken = _accessToken;
_cachedRefreshToken = _refreshToken;
request.headers.addAll({
'X-Imagini-AccessToken': _accessToken,
'X-Imagini-RefreshToken': _refreshToken,
});
return super.send(request).then((response) async {
if (response.headers.containsKey("x-imagini-accesstoken")) {
_cachedAccessToken = response.headers["x-imagini-accesstoken"];
await _storage.set("accessToken", response.headers["x-imagini-accesstoken"]);
}
if (response.headers.containsKey("x-imagini-refreshtoken")) {
_cachedRefreshToken = response.headers["x-imagini-refreshtoken"];
await _storage.set("refreshToken", response.headers["x-imagini-refreshtoken"]);
}
return response;
});
}
}

View File

@@ -0,0 +1,36 @@
import 'package:http/io_client.dart';
import "package:http/http.dart";
import "package:imagini/core/storage_client/base_storage_client.dart";
BaseClient getCookieClient(storage) => IOClientWithCookies(storage);
class IOClientWithCookies extends IOClient {
BaseStorageClient _storage;
IOClientWithCookies(BaseStorageClient storage) {
_storage = storage;
}
@override
Future<IOStreamedResponse> send(BaseRequest request) async {
String _accessToken = await _storage.get("accessToken");
String _refreshToken = await _storage.get("refreshToken");
request.headers.addAll({
'X-Imagini-AccessToken': _accessToken,
'X-Imagini-RefreshToken': _refreshToken,
});
return super.send(request).then((response) async {
// We've been told to update our access token
if (response.headers.containsKey("x-imagini-accesstoken")) {
await _storage.set("accessToken", response.headers["x-imagini-accesstoken"]);
}
// We've been told to update our refresh token
if (response.headers.containsKey("x-imagini-refreshtoken")) {
await _storage.set("refreshToken", response.headers["x-imagini-refreshtoken"]);
}
return response;
});
}
}

View File

@@ -1,30 +0,0 @@
import 'package:http/browser_client.dart';
import "package:http/http.dart";
BaseClient getCookieClient() => ClientWithCookies();
class ClientWithCookies extends BrowserClient {
String _accessToken = "asdasdasd";
String _refreshToken;
@override
Future<StreamedResponse> send(BaseRequest request) async {
request.headers.addAll({
'X-Imagini-AccessToken': _accessToken,
'X-Imagini-RefreshToken': _refreshToken,
});
return super.send(request).then((response) {
if (response.headers.containsKey("x-imagini-accesstoken")) {
this._accessToken = response.headers["x-imagini-accesstoken"];
}
if (response.headers.containsKey("x-imagini-refreshtoken")) {
this._refreshToken = response.headers["x-imagini-refreshtoken"];
}
print("Access Token: $_accessToken");
print("Refresh Token: $_refreshToken");
return response;
});
}
}

View File

@@ -1,12 +1,45 @@
import 'dart:async';
import 'package:imagini/api/api_provider.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:cached_network_image/cached_network_image.dart';
class ImaginiAPIRepository {
APIProvider _apiProvider;
ImaginiAPIRepository(this._apiProvider);
Stream<Login$Query$AuthResponse> login(String user, password) {
return Stream.fromFuture(_apiProvider.login(user, password));
Stream<bool> login(String user, password, server) {
return Stream.fromFuture(_apiProvider.login(user, password, server).then((QueryResult resp) {
if (resp.exception != null)
return false;
final loginResponse = Login$Query.fromJson(resp.data);
if (loginResponse.login.result == AuthResult.failure)
return false;
return true;
}));
}
Stream<QueryResult> me() {
return Stream.fromFuture(_apiProvider.me());
}
Future<MediaItems$Query> mediaItems(Page page) async {
QueryResult allItems = await _apiProvider.mediaItems(page);
return MediaItems$Query.fromJson(allItems.data);
}
Stream<bool> isAuthenticated() {
return Stream.fromFuture(_apiProvider.me().then((QueryResult resp) {
if (resp.exception != null)
return false;
return true;
}));
}
CachedNetworkImage getImage(String fileName, int width, int height) {
return _apiProvider.getImage(fileName, width, height);
}
}

View File

@@ -1,28 +0,0 @@
import 'package:http/io_client.dart';
import "package:http/http.dart";
BaseClient getCookieClient() => IOClientWithCookies();
class IOClientWithCookies extends IOClient {
String _accessToken;
String _refreshToken;
@override
Future<IOStreamedResponse> send(BaseRequest request) async {
// String cookie = await getCookie();
// String getCookieString(String _) => cookie;
// request.headers.update('cookie', getCookieString);
return super.send(request).then((response) {
if (response.headers.containsKey("x-imagini-accesstoken")) {
this._accessToken = response.headers["x-imagini-accesstoken"];
}
if (response.headers.containsKey("x-imagini-refreshtoken")) {
this._refreshToken = response.headers["x-imagini-refreshtoken"];
}
print("Access Token: $_accessToken");
print("Refresh Token: $_refreshToken");
return response;
});
}
}

View File

@@ -0,0 +1,96 @@
import 'dart:io';
import 'dart:async';
import 'package:imagini/core/imagini_application.dart';
import 'package:imagini/api/imagini_api_repository.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:photo_manager/photo_manager.dart';
class HomeBloc{
final ImaginiApplication _application;
ImaginiAPIRepository _imaginiAPI;
final _loginController = StreamController<bool>.broadcast();
Stream<bool> get loginResult => _loginController.stream;
final _authenticatedController = StreamController<bool>.broadcast();
Stream<bool> get authenticatedResult => _authenticatedController.stream;
Map<int, List<MediaItems$Query$MediaItemResponse$MediaItem>> _pagedMediaItemListCache =
Map<int, List<MediaItems$Query$MediaItemResponse$MediaItem>>();
int totalMediaItems;
int _pageSize = 50;
HomeBloc(this._application){
_init();
}
void _init(){
_imaginiAPI = _application.imaginiAPI;
_init_photo_manager();
}
void _init_photo_manager() async {
var result = await PhotoManager.requestPermission();
if (!result) {
PhotoManager.openSetting();
return;
}
// Gets list of "Albums"
List<AssetPathEntity> list = await PhotoManager.getAssetPathList();
// iOS - Select Gallery (predefined settings?)
// TODO: Filters: https://pub.dev/packages/photo_manager#filteroption
final assetList = await list[0].getAssetListPaged(0, 50);
// Example entity. This should be done through a loop
AssetEntity currEntity = assetList[0];
// File, Create, Modified, Title
File file = await currEntity.file;
DateTime createDt = currEntity.createDateTime;
DateTime modifiedDt = currEntity.modifiedDateTime;
String title = await currEntity.titleAsync;
}
void dispose(){
_loginController.close();
_authenticatedController.close();
}
CachedNetworkImage getMedia(int index, derivedContentWidth) {
MediaItems$Query$MediaItemResponse$MediaItem mediaDetails = await getMediaDetails(index);
if (mediaDetails == null)
return null;
int derivedContentHeight = (mediaDetails.height / mediaDetails.width * derivedContentWidth).ceil();
return _imaginiAPI.getImage(mediaDetails.fileName, derivedContentWidth, derivedContentHeight);
}
Future<MediaItems$Query$MediaItemResponse$MediaItem> getMediaDetails(int index) async {
int itemPage = (index / _pageSize).ceil();
int indexOnPage = index % _pageSize;
if (!_pagedMediaItemListCache.containsKey(itemPage))
await _cachePage(itemPage);
return _pagedMediaItemListCache[itemPage][indexOnPage];
}
_cachePage(int itemPage) async {
MediaItems$Query newItems = await _imaginiAPI.mediaItems(Page(page: itemPage));
totalMediaItems = newItems.mediaItems.page.total;
_pagedMediaItemListCache[itemPage] = newItems.mediaItems.data;
}
checkAuthentication(){
_authenticatedController.addStream(_imaginiAPI.isAuthenticated());
}
attemptHome(String username, password, server){
_loginController.addStream(_imaginiAPI.login(username, password, server));
}
}

View File

@@ -0,0 +1,40 @@
import 'dart:async';
import 'package:imagini/core/imagini_application.dart';
import 'package:imagini/api/imagini_api_repository.dart';
class LoginBloc{
final ImaginiApplication _application;
ImaginiAPIRepository _imaginiAPI;
final _loginController = StreamController<bool>.broadcast();
Stream<bool> get loginResult => _loginController.stream;
final _authenticatedController = StreamController<bool>.broadcast();
Stream<bool> get authenticatedResult => _authenticatedController.stream;
LoginBloc(this._application){
_init();
}
void _init(){
_imaginiAPI = _application.imaginiAPI;
checkAuthentication();
// attemptLogin("admin", "admin", "http://localhost:8484");
}
void dispose(){
_loginController.close();
_authenticatedController.close();
}
checkAuthentication(){
_authenticatedController.addStream(_imaginiAPI.isAuthenticated());
}
attemptLogin(String username, password, server){
_loginController.addStream(_imaginiAPI.login(username, password, server));
}
}

View File

@@ -1,36 +0,0 @@
import 'dart:async';
import 'package:imagini/core/imagini_application.dart';
import 'package:imagini/api/imagini_api_repository.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
class SplashBloc{
final ImaginiApplication _application;
final _loginController = StreamController<Login$Query$AuthResponse>();
Stream<Login$Query$AuthResponse> get loginResult => _loginController.stream;
SplashBloc(this._application){
_init();
}
void _init(){
// Do Initial Load
initializeLogin();
}
void dispose(){
_loginController.close();
}
initializeLogin(){
ImaginiAPIRepository imaginiAPI = _application.imaginiAPI;
// TODO: This should actually attempt to load the existing Tokens, not login
_loginController.addStream(imaginiAPI.login("admin", "admin"));
// imaginiAPI.login("admin", "admin1").listen((LoginResponse lr) {
// });
}
}

View File

@@ -24,7 +24,6 @@ class AppComponentState extends State<AppComponent> {
@override
void dispose() async {
// Log.info('dispose');
super.dispose();
await _application.onTerminate();
}

View File

@@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
import 'package:imagini/screens/home_screen.dart';
import 'package:imagini/screens/login_screen.dart';
import 'package:imagini/screens/splash_screen.dart';
// import 'package:imagini/screens/splash_screen.dart';
var splashHandler = new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return SplashScreen();
}
);
// var splashHandler = new Handler(
// handlerFunc: (BuildContext context, Map<String, List<String>> params) {
// return SplashScreen();
// }
// );
var loginHandler = new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) {
@@ -42,7 +42,7 @@ class AppRoutes {
return;
}
);
router.define(SplashScreen.PATH, handler: splashHandler);
// router.define(SplashScreen.PATH, handler: splashHandler);
router.define(LoginScreen.PATH, handler: loginHandler);
router.define(HomeScreen.PATH, handler: homeHandler);
// router.define(AppDetailPage.PATH, handler: appDetailRouteHandler);

View File

@@ -3,14 +3,21 @@ import 'package:fluro/fluro.dart';
import 'package:imagini/core/app_routes.dart';
import 'package:imagini/api/api_provider.dart';
import 'package:imagini/api/imagini_api_repository.dart';
import 'package:imagini/core/storage_client/base_storage_client.dart';
import 'package:imagini/core/storage_client/storage_client.dart'
if (dart.library.html) 'package:imagini/core/storage_client/browser_storage_client.dart'
if (dart.library.io) 'package:imagini/core/storage_client/mobile_storage_client.dart';
class ImaginiApplication {
FluroRouter router;
ImaginiAPIRepository imaginiAPI;
BaseStorageClient storageClient;
Future<void> onCreate() async {
_initRouter();
_initAPIRepository();
_initStorageClient();
await _initAPIRepository();
}
Future<void> onTerminate() async {}
@@ -20,8 +27,14 @@ class ImaginiApplication {
AppRoutes.configureRoutes(router);
}
_initAPIRepository() {
APIProvider apiProvider = new APIProvider(server: "http://localhost:8484");
_initStorageClient() {
storageClient = getStorageClient();
}
_initAPIRepository() async {
// TODO: Get from config
APIProvider apiProvider = new APIProvider(storageClient);
await apiProvider.init();
imaginiAPI = ImaginiAPIRepository(apiProvider);
}
}

View File

@@ -0,0 +1,4 @@
abstract class BaseStorageClient {
Future<String> get(String key);
Future<void> set(String key, String value);
}

View File

@@ -0,0 +1,18 @@
import 'dart:html';
import './base_storage_client.dart';
BaseStorageClient getStorageClient() => BrowserStorageClient();
class BrowserStorageClient extends BaseStorageClient {
@override
Future<String> get(String key) async {
var requestedValue = window.localStorage.containsKey(key) ? window.localStorage[key] : "";
return requestedValue;
}
@override
Future<void> set(String key, String value) async {
window.localStorage[key] = value;
}
}

View File

@@ -0,0 +1,19 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import './base_storage_client.dart';
BaseStorageClient getStorageClient() => MobileStorageClient();
class MobileStorageClient extends BaseStorageClient {
final storage = new FlutterSecureStorage();
@override
Future<String> get(String key) async {
return storage.read(key: key);
}
@override
Future<void> set(String key, String value) async {
return storage.write(key: key, value: value);
}
}

View File

@@ -0,0 +1,4 @@
import './base_storage_client.dart';
BaseStorageClient getStorageClient() => throw UnsupportedError(
'Cannot create a storage client.');

View File

@@ -9,6 +9,62 @@ import 'package:http/http.dart';
import 'package:imagini/helpers/upload_serializer.dart';
part 'imagini_graphql.graphql.g.dart';
@JsonSerializable(explicitToJson: true)
class Me$Query$User with EquatableMixin {
Me$Query$User();
factory Me$Query$User.fromJson(Map<String, dynamic> json) =>
_$Me$Query$UserFromJson(json);
String id;
DateTime createdAt;
DateTime updatedAt;
String email;
String username;
String firstName;
String lastName;
@JsonKey(unknownEnumValue: Role.artemisUnknown)
Role role;
@JsonKey(unknownEnumValue: AuthType.artemisUnknown)
AuthType authType;
@override
List<Object> get props => [
id,
createdAt,
updatedAt,
email,
username,
firstName,
lastName,
role,
authType
];
Map<String, dynamic> toJson() => _$Me$Query$UserToJson(this);
}
@JsonSerializable(explicitToJson: true)
class Me$Query with EquatableMixin {
Me$Query();
factory Me$Query.fromJson(Map<String, dynamic> json) =>
_$Me$QueryFromJson(json);
Me$Query$User me;
@override
List<Object> get props => [me];
Map<String, dynamic> toJson() => _$Me$QueryToJson(this);
}
@JsonSerializable(explicitToJson: true)
class Login$Query$AuthResponse$Device with EquatableMixin {
Login$Query$AuthResponse$Device();
@@ -115,69 +171,374 @@ class CreateMediaItem$Mutation with EquatableMixin {
}
@JsonSerializable(explicitToJson: true)
class Me$Query$User with EquatableMixin {
Me$Query$User();
class MediaItems$Query$MediaItemResponse$MediaItem with EquatableMixin {
MediaItems$Query$MediaItemResponse$MediaItem();
factory Me$Query$User.fromJson(Map<String, dynamic> json) =>
_$Me$Query$UserFromJson(json);
factory MediaItems$Query$MediaItemResponse$MediaItem.fromJson(
Map<String, dynamic> json) =>
_$MediaItems$Query$MediaItemResponse$MediaItemFromJson(json);
String id;
String fileName;
double latitude;
double longitude;
bool isVideo;
int width;
int height;
String origName;
DateTime createdAt;
DateTime updatedAt;
@override
List<Object> get props => [
id,
fileName,
latitude,
longitude,
isVideo,
width,
height,
origName,
createdAt
];
Map<String, dynamic> toJson() =>
_$MediaItems$Query$MediaItemResponse$MediaItemToJson(this);
}
String email;
@JsonSerializable(explicitToJson: true)
class MediaItems$Query$MediaItemResponse$PageResponse with EquatableMixin {
MediaItems$Query$MediaItemResponse$PageResponse();
String username;
factory MediaItems$Query$MediaItemResponse$PageResponse.fromJson(
Map<String, dynamic> json) =>
_$MediaItems$Query$MediaItemResponse$PageResponseFromJson(json);
String firstName;
int size;
String lastName;
int page;
@JsonKey(unknownEnumValue: Role.artemisUnknown)
Role role;
int total;
@JsonKey(unknownEnumValue: AuthType.artemisUnknown)
AuthType authType;
@override
List<Object> get props => [size, page, total];
Map<String, dynamic> toJson() =>
_$MediaItems$Query$MediaItemResponse$PageResponseToJson(this);
}
@JsonSerializable(explicitToJson: true)
class MediaItems$Query$MediaItemResponse with EquatableMixin {
MediaItems$Query$MediaItemResponse();
factory MediaItems$Query$MediaItemResponse.fromJson(
Map<String, dynamic> json) =>
_$MediaItems$Query$MediaItemResponseFromJson(json);
List<MediaItems$Query$MediaItemResponse$MediaItem> data;
MediaItems$Query$MediaItemResponse$PageResponse page;
@override
List<Object> get props => [data, page];
Map<String, dynamic> toJson() =>
_$MediaItems$Query$MediaItemResponseToJson(this);
}
@JsonSerializable(explicitToJson: true)
class MediaItems$Query with EquatableMixin {
MediaItems$Query();
factory MediaItems$Query.fromJson(Map<String, dynamic> json) =>
_$MediaItems$QueryFromJson(json);
MediaItems$Query$MediaItemResponse mediaItems;
@override
List<Object> get props => [mediaItems];
Map<String, dynamic> toJson() => _$MediaItems$QueryToJson(this);
}
@JsonSerializable(explicitToJson: true)
class TimeFilter with EquatableMixin {
TimeFilter(
{this.equalTo,
this.notEqualTo,
this.lessThan,
this.lessThanOrEqualTo,
this.greaterThan,
this.greaterThanOrEqualTo});
factory TimeFilter.fromJson(Map<String, dynamic> json) =>
_$TimeFilterFromJson(json);
DateTime equalTo;
DateTime notEqualTo;
DateTime lessThan;
DateTime lessThanOrEqualTo;
DateTime greaterThan;
DateTime greaterThanOrEqualTo;
@override
List<Object> get props => [
equalTo,
notEqualTo,
lessThan,
lessThanOrEqualTo,
greaterThan,
greaterThanOrEqualTo
];
Map<String, dynamic> toJson() => _$TimeFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class FloatFilter with EquatableMixin {
FloatFilter(
{this.equalTo,
this.notEqualTo,
this.lessThan,
this.lessThanOrEqualTo,
this.greaterThan,
this.greaterThanOrEqualTo});
factory FloatFilter.fromJson(Map<String, dynamic> json) =>
_$FloatFilterFromJson(json);
double equalTo;
double notEqualTo;
double lessThan;
double lessThanOrEqualTo;
double greaterThan;
double greaterThanOrEqualTo;
@override
List<Object> get props => [
equalTo,
notEqualTo,
lessThan,
lessThanOrEqualTo,
greaterThan,
greaterThanOrEqualTo
];
Map<String, dynamic> toJson() => _$FloatFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class BooleanFilter with EquatableMixin {
BooleanFilter({this.equalTo, this.notEqualTo});
factory BooleanFilter.fromJson(Map<String, dynamic> json) =>
_$BooleanFilterFromJson(json);
bool equalTo;
bool notEqualTo;
@override
List<Object> get props => [equalTo, notEqualTo];
Map<String, dynamic> toJson() => _$BooleanFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class IDFilter with EquatableMixin {
IDFilter({this.equalTo, this.notEqualTo});
factory IDFilter.fromJson(Map<String, dynamic> json) =>
_$IDFilterFromJson(json);
String equalTo;
String notEqualTo;
@override
List<Object> get props => [equalTo, notEqualTo];
Map<String, dynamic> toJson() => _$IDFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class StringFilter with EquatableMixin {
StringFilter(
{this.equalTo,
this.notEqualTo,
this.startsWith,
this.notStartsWith,
this.endsWith,
this.notEndsWith,
this.contains,
this.notContains});
factory StringFilter.fromJson(Map<String, dynamic> json) =>
_$StringFilterFromJson(json);
String equalTo;
String notEqualTo;
String startsWith;
String notStartsWith;
String endsWith;
String notEndsWith;
String contains;
String notContains;
@override
List<Object> get props => [
equalTo,
notEqualTo,
startsWith,
notStartsWith,
endsWith,
notEndsWith,
contains,
notContains
];
Map<String, dynamic> toJson() => _$StringFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class MediaItemFilter with EquatableMixin {
MediaItemFilter(
{this.id,
this.createdAt,
this.updatedAt,
this.exifDate,
this.latitude,
this.longitude,
this.isVideo,
this.origName,
this.tags,
this.albums});
factory MediaItemFilter.fromJson(Map<String, dynamic> json) =>
_$MediaItemFilterFromJson(json);
IDFilter id;
TimeFilter createdAt;
TimeFilter updatedAt;
TimeFilter exifDate;
FloatFilter latitude;
FloatFilter longitude;
BooleanFilter isVideo;
StringFilter origName;
TagFilter tags;
AlbumFilter albums;
@override
List<Object> get props => [
id,
createdAt,
updatedAt,
email,
username,
firstName,
lastName,
role,
authType
exifDate,
latitude,
longitude,
isVideo,
origName,
tags,
albums
];
Map<String, dynamic> toJson() => _$Me$Query$UserToJson(this);
Map<String, dynamic> toJson() => _$MediaItemFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class Me$Query with EquatableMixin {
Me$Query();
class TagFilter with EquatableMixin {
TagFilter({this.id, this.createdAt, this.updatedAt, this.name});
factory Me$Query.fromJson(Map<String, dynamic> json) =>
_$Me$QueryFromJson(json);
factory TagFilter.fromJson(Map<String, dynamic> json) =>
_$TagFilterFromJson(json);
Me$Query$User me;
IDFilter id;
TimeFilter createdAt;
TimeFilter updatedAt;
StringFilter name;
@override
List<Object> get props => [me];
Map<String, dynamic> toJson() => _$Me$QueryToJson(this);
List<Object> get props => [id, createdAt, updatedAt, name];
Map<String, dynamic> toJson() => _$TagFilterToJson(this);
}
enum AuthResult {
@JsonValue('Success')
success,
@JsonValue('Failure')
failure,
@JsonValue('ARTEMIS_UNKNOWN')
artemisUnknown,
@JsonSerializable(explicitToJson: true)
class AlbumFilter with EquatableMixin {
AlbumFilter({this.id, this.createdAt, this.updatedAt, this.name});
factory AlbumFilter.fromJson(Map<String, dynamic> json) =>
_$AlbumFilterFromJson(json);
IDFilter id;
TimeFilter createdAt;
TimeFilter updatedAt;
StringFilter name;
@override
List<Object> get props => [id, createdAt, updatedAt, name];
Map<String, dynamic> toJson() => _$AlbumFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class Page with EquatableMixin {
Page({this.size, this.page});
factory Page.fromJson(Map<String, dynamic> json) => _$PageFromJson(json);
int size;
int page;
@override
List<Object> get props => [size, page];
Map<String, dynamic> toJson() => _$PageToJson(this);
}
@JsonSerializable(explicitToJson: true)
class Order with EquatableMixin {
Order({this.by, this.direction});
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
String by;
@JsonKey(unknownEnumValue: OrderDirection.artemisUnknown)
OrderDirection direction;
@override
List<Object> get props => [by, direction];
Map<String, dynamic> toJson() => _$OrderToJson(this);
}
enum Role {
@JsonValue('Admin')
admin,
@@ -194,6 +555,106 @@ enum AuthType {
@JsonValue('ARTEMIS_UNKNOWN')
artemisUnknown,
}
enum AuthResult {
@JsonValue('Success')
success,
@JsonValue('Failure')
failure,
@JsonValue('ARTEMIS_UNKNOWN')
artemisUnknown,
}
enum OrderDirection {
@JsonValue('ASC')
asc,
@JsonValue('DESC')
desc,
@JsonValue('ARTEMIS_UNKNOWN')
artemisUnknown,
}
class MeQuery extends GraphQLQuery<Me$Query, JsonSerializable> {
MeQuery();
@override
final DocumentNode document = DocumentNode(definitions: [
OperationDefinitionNode(
type: OperationType.query,
name: NameNode(value: 'me'),
variableDefinitions: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'me'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'id'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'createdAt'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'updatedAt'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'email'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'username'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'firstName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'lastName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'role'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'authType'),
alias: null,
arguments: [],
directives: [],
selectionSet: null)
]))
]))
]);
@override
final String operationName = 'me';
@override
List<Object> get props => [document, operationName];
@override
Me$Query parse(Map<String, dynamic> json) => Me$Query.fromJson(json);
}
@JsonSerializable(explicitToJson: true)
class LoginArguments extends JsonSerializable with EquatableMixin {
@@ -425,19 +886,75 @@ class CreateMediaItemMutation
CreateMediaItem$Mutation.fromJson(json);
}
class MeQuery extends GraphQLQuery<Me$Query, JsonSerializable> {
MeQuery();
@JsonSerializable(explicitToJson: true)
class MediaItemsArguments extends JsonSerializable with EquatableMixin {
MediaItemsArguments({this.order, this.page, this.filter});
@override
factory MediaItemsArguments.fromJson(Map<String, dynamic> json) =>
_$MediaItemsArgumentsFromJson(json);
final Order order;
final Page page;
final MediaItemFilter filter;
@override
List<Object> get props => [order, page, filter];
@override
Map<String, dynamic> toJson() => _$MediaItemsArgumentsToJson(this);
}
class MediaItemsQuery
extends GraphQLQuery<MediaItems$Query, MediaItemsArguments> {
MediaItemsQuery({this.variables});
@override
final DocumentNode document = DocumentNode(definitions: [
OperationDefinitionNode(
type: OperationType.query,
name: NameNode(value: 'me'),
variableDefinitions: [],
name: NameNode(value: 'mediaItems'),
variableDefinitions: [
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'order')),
type: NamedTypeNode(
name: NameNode(value: 'Order'), isNonNull: false),
defaultValue: DefaultValueNode(value: null),
directives: []),
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'page')),
type: NamedTypeNode(
name: NameNode(value: 'Page'), isNonNull: false),
defaultValue: DefaultValueNode(value: null),
directives: []),
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'filter')),
type: NamedTypeNode(
name: NameNode(value: 'MediaItemFilter'), isNonNull: false),
defaultValue: DefaultValueNode(value: null),
directives: [])
],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'me'),
name: NameNode(value: 'mediaItems'),
alias: null,
arguments: [
ArgumentNode(
name: NameNode(value: 'filter'),
value: VariableNode(name: NameNode(value: 'filter'))),
ArgumentNode(
name: NameNode(value: 'page'),
value: VariableNode(name: NameNode(value: 'page'))),
ArgumentNode(
name: NameNode(value: 'order'),
value: VariableNode(name: NameNode(value: 'order')))
],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'data'),
alias: null,
arguments: [],
directives: [],
@@ -448,63 +965,93 @@ class MeQuery extends GraphQLQuery<Me$Query, JsonSerializable> {
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'fileName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'latitude'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'longitude'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'isVideo'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'width'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'height'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'origName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'createdAt'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
selectionSet: null)
])),
FieldNode(
name: NameNode(value: 'updatedAt'),
name: NameNode(value: 'page'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'size'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'email'),
name: NameNode(value: 'page'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'username'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'firstName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'lastName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'role'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'authType'),
name: NameNode(value: 'total'),
alias: null,
arguments: [],
directives: [],
selectionSet: null)
]))
]))
]))
]);
@override
final String operationName = 'me';
final String operationName = 'mediaItems';
@override
List<Object> get props => [document, operationName];
final MediaItemsArguments variables;
@override
Me$Query parse(Map<String, dynamic> json) => Me$Query.fromJson(json);
List<Object> get props => [document, operationName, variables];
@override
MediaItems$Query parse(Map<String, dynamic> json) =>
MediaItems$Query.fromJson(json);
}

View File

@@ -6,33 +6,36 @@ part of 'imagini_graphql.graphql.dart';
// JsonSerializableGenerator
// **************************************************************************
Login$Query$AuthResponse$Device _$Login$Query$AuthResponse$DeviceFromJson(
Map<String, dynamic> json) {
return Login$Query$AuthResponse$Device()..id = json['id'] as String;
Me$Query$User _$Me$Query$UserFromJson(Map<String, dynamic> json) {
return Me$Query$User()
..id = json['id'] as String
..createdAt = json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String)
..updatedAt = json['updatedAt'] == null
? null
: DateTime.parse(json['updatedAt'] as String)
..email = json['email'] as String
..username = json['username'] as String
..firstName = json['firstName'] as String
..lastName = json['lastName'] as String
..role = _$enumDecodeNullable(_$RoleEnumMap, json['role'],
unknownValue: Role.artemisUnknown)
..authType = _$enumDecodeNullable(_$AuthTypeEnumMap, json['authType'],
unknownValue: AuthType.artemisUnknown);
}
Map<String, dynamic> _$Login$Query$AuthResponse$DeviceToJson(
Login$Query$AuthResponse$Device instance) =>
Map<String, dynamic> _$Me$Query$UserToJson(Me$Query$User instance) =>
<String, dynamic>{
'id': instance.id,
};
Login$Query$AuthResponse _$Login$Query$AuthResponseFromJson(
Map<String, dynamic> json) {
return Login$Query$AuthResponse()
..result = _$enumDecodeNullable(_$AuthResultEnumMap, json['result'],
unknownValue: AuthResult.artemisUnknown)
..device = json['device'] == null
? null
: Login$Query$AuthResponse$Device.fromJson(
json['device'] as Map<String, dynamic>);
}
Map<String, dynamic> _$Login$Query$AuthResponseToJson(
Login$Query$AuthResponse instance) =>
<String, dynamic>{
'result': _$AuthResultEnumMap[instance.result],
'device': instance.device?.toJson(),
'createdAt': instance.createdAt?.toIso8601String(),
'updatedAt': instance.updatedAt?.toIso8601String(),
'email': instance.email,
'username': instance.username,
'firstName': instance.firstName,
'lastName': instance.lastName,
'role': _$RoleEnumMap[instance.role],
'authType': _$AuthTypeEnumMap[instance.authType],
};
T _$enumDecode<T>(
@@ -67,6 +70,58 @@ T _$enumDecodeNullable<T>(
return _$enumDecode<T>(enumValues, source, unknownValue: unknownValue);
}
const _$RoleEnumMap = {
Role.admin: 'Admin',
Role.user: 'User',
Role.artemisUnknown: 'ARTEMIS_UNKNOWN',
};
const _$AuthTypeEnumMap = {
AuthType.local: 'Local',
AuthType.ldap: 'LDAP',
AuthType.artemisUnknown: 'ARTEMIS_UNKNOWN',
};
Me$Query _$Me$QueryFromJson(Map<String, dynamic> json) {
return Me$Query()
..me = json['me'] == null
? null
: Me$Query$User.fromJson(json['me'] as Map<String, dynamic>);
}
Map<String, dynamic> _$Me$QueryToJson(Me$Query instance) => <String, dynamic>{
'me': instance.me?.toJson(),
};
Login$Query$AuthResponse$Device _$Login$Query$AuthResponse$DeviceFromJson(
Map<String, dynamic> json) {
return Login$Query$AuthResponse$Device()..id = json['id'] as String;
}
Map<String, dynamic> _$Login$Query$AuthResponse$DeviceToJson(
Login$Query$AuthResponse$Device instance) =>
<String, dynamic>{
'id': instance.id,
};
Login$Query$AuthResponse _$Login$Query$AuthResponseFromJson(
Map<String, dynamic> json) {
return Login$Query$AuthResponse()
..result = _$enumDecodeNullable(_$AuthResultEnumMap, json['result'],
unknownValue: AuthResult.artemisUnknown)
..device = json['device'] == null
? null
: Login$Query$AuthResponse$Device.fromJson(
json['device'] as Map<String, dynamic>);
}
Map<String, dynamic> _$Login$Query$AuthResponseToJson(
Login$Query$AuthResponse instance) =>
<String, dynamic>{
'result': _$AuthResultEnumMap[instance.result],
'device': instance.device?.toJson(),
};
const _$AuthResultEnumMap = {
AuthResult.success: 'Success',
AuthResult.failure: 'Failure',
@@ -137,59 +192,320 @@ Map<String, dynamic> _$CreateMediaItem$MutationToJson(
'createMediaItem': instance.createMediaItem?.toJson(),
};
Me$Query$User _$Me$Query$UserFromJson(Map<String, dynamic> json) {
return Me$Query$User()
MediaItems$Query$MediaItemResponse$MediaItem
_$MediaItems$Query$MediaItemResponse$MediaItemFromJson(
Map<String, dynamic> json) {
return MediaItems$Query$MediaItemResponse$MediaItem()
..id = json['id'] as String
..fileName = json['fileName'] as String
..latitude = (json['latitude'] as num)?.toDouble()
..longitude = (json['longitude'] as num)?.toDouble()
..isVideo = json['isVideo'] as bool
..width = json['width'] as int
..height = json['height'] as int
..origName = json['origName'] as String
..createdAt = json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String)
..updatedAt = json['updatedAt'] == null
? null
: DateTime.parse(json['updatedAt'] as String)
..email = json['email'] as String
..username = json['username'] as String
..firstName = json['firstName'] as String
..lastName = json['lastName'] as String
..role = _$enumDecodeNullable(_$RoleEnumMap, json['role'],
unknownValue: Role.artemisUnknown)
..authType = _$enumDecodeNullable(_$AuthTypeEnumMap, json['authType'],
unknownValue: AuthType.artemisUnknown);
: DateTime.parse(json['createdAt'] as String);
}
Map<String, dynamic> _$Me$Query$UserToJson(Me$Query$User instance) =>
Map<String, dynamic> _$MediaItems$Query$MediaItemResponse$MediaItemToJson(
MediaItems$Query$MediaItemResponse$MediaItem instance) =>
<String, dynamic>{
'id': instance.id,
'fileName': instance.fileName,
'latitude': instance.latitude,
'longitude': instance.longitude,
'isVideo': instance.isVideo,
'width': instance.width,
'height': instance.height,
'origName': instance.origName,
'createdAt': instance.createdAt?.toIso8601String(),
'updatedAt': instance.updatedAt?.toIso8601String(),
'email': instance.email,
'username': instance.username,
'firstName': instance.firstName,
'lastName': instance.lastName,
'role': _$RoleEnumMap[instance.role],
'authType': _$AuthTypeEnumMap[instance.authType],
};
const _$RoleEnumMap = {
Role.admin: 'Admin',
Role.user: 'User',
Role.artemisUnknown: 'ARTEMIS_UNKNOWN',
};
const _$AuthTypeEnumMap = {
AuthType.local: 'Local',
AuthType.ldap: 'LDAP',
AuthType.artemisUnknown: 'ARTEMIS_UNKNOWN',
};
Me$Query _$Me$QueryFromJson(Map<String, dynamic> json) {
return Me$Query()
..me = json['me'] == null
? null
: Me$Query$User.fromJson(json['me'] as Map<String, dynamic>);
MediaItems$Query$MediaItemResponse$PageResponse
_$MediaItems$Query$MediaItemResponse$PageResponseFromJson(
Map<String, dynamic> json) {
return MediaItems$Query$MediaItemResponse$PageResponse()
..size = json['size'] as int
..page = json['page'] as int
..total = json['total'] as int;
}
Map<String, dynamic> _$Me$QueryToJson(Me$Query instance) => <String, dynamic>{
'me': instance.me?.toJson(),
Map<String, dynamic> _$MediaItems$Query$MediaItemResponse$PageResponseToJson(
MediaItems$Query$MediaItemResponse$PageResponse instance) =>
<String, dynamic>{
'size': instance.size,
'page': instance.page,
'total': instance.total,
};
MediaItems$Query$MediaItemResponse _$MediaItems$Query$MediaItemResponseFromJson(
Map<String, dynamic> json) {
return MediaItems$Query$MediaItemResponse()
..data = (json['data'] as List)
?.map((e) => e == null
? null
: MediaItems$Query$MediaItemResponse$MediaItem.fromJson(
e as Map<String, dynamic>))
?.toList()
..page = json['page'] == null
? null
: MediaItems$Query$MediaItemResponse$PageResponse.fromJson(
json['page'] as Map<String, dynamic>);
}
Map<String, dynamic> _$MediaItems$Query$MediaItemResponseToJson(
MediaItems$Query$MediaItemResponse instance) =>
<String, dynamic>{
'data': instance.data?.map((e) => e?.toJson())?.toList(),
'page': instance.page?.toJson(),
};
MediaItems$Query _$MediaItems$QueryFromJson(Map<String, dynamic> json) {
return MediaItems$Query()
..mediaItems = json['mediaItems'] == null
? null
: MediaItems$Query$MediaItemResponse.fromJson(
json['mediaItems'] as Map<String, dynamic>);
}
Map<String, dynamic> _$MediaItems$QueryToJson(MediaItems$Query instance) =>
<String, dynamic>{
'mediaItems': instance.mediaItems?.toJson(),
};
TimeFilter _$TimeFilterFromJson(Map<String, dynamic> json) {
return TimeFilter(
equalTo: json['equalTo'] == null
? null
: DateTime.parse(json['equalTo'] as String),
notEqualTo: json['notEqualTo'] == null
? null
: DateTime.parse(json['notEqualTo'] as String),
lessThan: json['lessThan'] == null
? null
: DateTime.parse(json['lessThan'] as String),
lessThanOrEqualTo: json['lessThanOrEqualTo'] == null
? null
: DateTime.parse(json['lessThanOrEqualTo'] as String),
greaterThan: json['greaterThan'] == null
? null
: DateTime.parse(json['greaterThan'] as String),
greaterThanOrEqualTo: json['greaterThanOrEqualTo'] == null
? null
: DateTime.parse(json['greaterThanOrEqualTo'] as String),
);
}
Map<String, dynamic> _$TimeFilterToJson(TimeFilter instance) =>
<String, dynamic>{
'equalTo': instance.equalTo?.toIso8601String(),
'notEqualTo': instance.notEqualTo?.toIso8601String(),
'lessThan': instance.lessThan?.toIso8601String(),
'lessThanOrEqualTo': instance.lessThanOrEqualTo?.toIso8601String(),
'greaterThan': instance.greaterThan?.toIso8601String(),
'greaterThanOrEqualTo': instance.greaterThanOrEqualTo?.toIso8601String(),
};
FloatFilter _$FloatFilterFromJson(Map<String, dynamic> json) {
return FloatFilter(
equalTo: (json['equalTo'] as num)?.toDouble(),
notEqualTo: (json['notEqualTo'] as num)?.toDouble(),
lessThan: (json['lessThan'] as num)?.toDouble(),
lessThanOrEqualTo: (json['lessThanOrEqualTo'] as num)?.toDouble(),
greaterThan: (json['greaterThan'] as num)?.toDouble(),
greaterThanOrEqualTo: (json['greaterThanOrEqualTo'] as num)?.toDouble(),
);
}
Map<String, dynamic> _$FloatFilterToJson(FloatFilter instance) =>
<String, dynamic>{
'equalTo': instance.equalTo,
'notEqualTo': instance.notEqualTo,
'lessThan': instance.lessThan,
'lessThanOrEqualTo': instance.lessThanOrEqualTo,
'greaterThan': instance.greaterThan,
'greaterThanOrEqualTo': instance.greaterThanOrEqualTo,
};
BooleanFilter _$BooleanFilterFromJson(Map<String, dynamic> json) {
return BooleanFilter(
equalTo: json['equalTo'] as bool,
notEqualTo: json['notEqualTo'] as bool,
);
}
Map<String, dynamic> _$BooleanFilterToJson(BooleanFilter instance) =>
<String, dynamic>{
'equalTo': instance.equalTo,
'notEqualTo': instance.notEqualTo,
};
IDFilter _$IDFilterFromJson(Map<String, dynamic> json) {
return IDFilter(
equalTo: json['equalTo'] as String,
notEqualTo: json['notEqualTo'] as String,
);
}
Map<String, dynamic> _$IDFilterToJson(IDFilter instance) => <String, dynamic>{
'equalTo': instance.equalTo,
'notEqualTo': instance.notEqualTo,
};
StringFilter _$StringFilterFromJson(Map<String, dynamic> json) {
return StringFilter(
equalTo: json['equalTo'] as String,
notEqualTo: json['notEqualTo'] as String,
startsWith: json['startsWith'] as String,
notStartsWith: json['notStartsWith'] as String,
endsWith: json['endsWith'] as String,
notEndsWith: json['notEndsWith'] as String,
contains: json['contains'] as String,
notContains: json['notContains'] as String,
);
}
Map<String, dynamic> _$StringFilterToJson(StringFilter instance) =>
<String, dynamic>{
'equalTo': instance.equalTo,
'notEqualTo': instance.notEqualTo,
'startsWith': instance.startsWith,
'notStartsWith': instance.notStartsWith,
'endsWith': instance.endsWith,
'notEndsWith': instance.notEndsWith,
'contains': instance.contains,
'notContains': instance.notContains,
};
MediaItemFilter _$MediaItemFilterFromJson(Map<String, dynamic> json) {
return MediaItemFilter(
id: json['id'] == null
? null
: IDFilter.fromJson(json['id'] as Map<String, dynamic>),
createdAt: json['createdAt'] == null
? null
: TimeFilter.fromJson(json['createdAt'] as Map<String, dynamic>),
updatedAt: json['updatedAt'] == null
? null
: TimeFilter.fromJson(json['updatedAt'] as Map<String, dynamic>),
exifDate: json['exifDate'] == null
? null
: TimeFilter.fromJson(json['exifDate'] as Map<String, dynamic>),
latitude: json['latitude'] == null
? null
: FloatFilter.fromJson(json['latitude'] as Map<String, dynamic>),
longitude: json['longitude'] == null
? null
: FloatFilter.fromJson(json['longitude'] as Map<String, dynamic>),
isVideo: json['isVideo'] == null
? null
: BooleanFilter.fromJson(json['isVideo'] as Map<String, dynamic>),
origName: json['origName'] == null
? null
: StringFilter.fromJson(json['origName'] as Map<String, dynamic>),
tags: json['tags'] == null
? null
: TagFilter.fromJson(json['tags'] as Map<String, dynamic>),
albums: json['albums'] == null
? null
: AlbumFilter.fromJson(json['albums'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$MediaItemFilterToJson(MediaItemFilter instance) =>
<String, dynamic>{
'id': instance.id?.toJson(),
'createdAt': instance.createdAt?.toJson(),
'updatedAt': instance.updatedAt?.toJson(),
'exifDate': instance.exifDate?.toJson(),
'latitude': instance.latitude?.toJson(),
'longitude': instance.longitude?.toJson(),
'isVideo': instance.isVideo?.toJson(),
'origName': instance.origName?.toJson(),
'tags': instance.tags?.toJson(),
'albums': instance.albums?.toJson(),
};
TagFilter _$TagFilterFromJson(Map<String, dynamic> json) {
return TagFilter(
id: json['id'] == null
? null
: IDFilter.fromJson(json['id'] as Map<String, dynamic>),
createdAt: json['createdAt'] == null
? null
: TimeFilter.fromJson(json['createdAt'] as Map<String, dynamic>),
updatedAt: json['updatedAt'] == null
? null
: TimeFilter.fromJson(json['updatedAt'] as Map<String, dynamic>),
name: json['name'] == null
? null
: StringFilter.fromJson(json['name'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$TagFilterToJson(TagFilter instance) => <String, dynamic>{
'id': instance.id?.toJson(),
'createdAt': instance.createdAt?.toJson(),
'updatedAt': instance.updatedAt?.toJson(),
'name': instance.name?.toJson(),
};
AlbumFilter _$AlbumFilterFromJson(Map<String, dynamic> json) {
return AlbumFilter(
id: json['id'] == null
? null
: IDFilter.fromJson(json['id'] as Map<String, dynamic>),
createdAt: json['createdAt'] == null
? null
: TimeFilter.fromJson(json['createdAt'] as Map<String, dynamic>),
updatedAt: json['updatedAt'] == null
? null
: TimeFilter.fromJson(json['updatedAt'] as Map<String, dynamic>),
name: json['name'] == null
? null
: StringFilter.fromJson(json['name'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$AlbumFilterToJson(AlbumFilter instance) =>
<String, dynamic>{
'id': instance.id?.toJson(),
'createdAt': instance.createdAt?.toJson(),
'updatedAt': instance.updatedAt?.toJson(),
'name': instance.name?.toJson(),
};
Page _$PageFromJson(Map<String, dynamic> json) {
return Page(
size: json['size'] as int,
page: json['page'] as int,
);
}
Map<String, dynamic> _$PageToJson(Page instance) => <String, dynamic>{
'size': instance.size,
'page': instance.page,
};
Order _$OrderFromJson(Map<String, dynamic> json) {
return Order(
by: json['by'] as String,
direction: _$enumDecodeNullable(_$OrderDirectionEnumMap, json['direction'],
unknownValue: OrderDirection.artemisUnknown),
);
}
Map<String, dynamic> _$OrderToJson(Order instance) => <String, dynamic>{
'by': instance.by,
'direction': _$OrderDirectionEnumMap[instance.direction],
};
const _$OrderDirectionEnumMap = {
OrderDirection.asc: 'ASC',
OrderDirection.desc: 'DESC',
OrderDirection.artemisUnknown: 'ARTEMIS_UNKNOWN',
};
LoginArguments _$LoginArgumentsFromJson(Map<String, dynamic> json) {
@@ -219,3 +535,25 @@ Map<String, dynamic> _$CreateMediaItemArgumentsToJson(
<String, dynamic>{
'file': fromDartMultipartFileToGraphQLUpload(instance.file),
};
MediaItemsArguments _$MediaItemsArgumentsFromJson(Map<String, dynamic> json) {
return MediaItemsArguments(
order: json['order'] == null
? null
: Order.fromJson(json['order'] as Map<String, dynamic>),
page: json['page'] == null
? null
: Page.fromJson(json['page'] as Map<String, dynamic>),
filter: json['filter'] == null
? null
: MediaItemFilter.fromJson(json['filter'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$MediaItemsArgumentsToJson(
MediaItemsArguments instance) =>
<String, dynamic>{
'order': instance.order?.toJson(),
'page': instance.page?.toJson(),
'filter': instance.filter?.toJson(),
};

View File

@@ -1,7 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:photo_view/photo_view.dart';
import 'package:imagini/blocs/home_bloc.dart';
import 'package:imagini/core/app_provider.dart';
class HomeScreen extends StatefulWidget {
static const String PATH = '/Home';
@@ -14,12 +21,26 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> {
// HomeBloc bloc;
HomeBloc bloc;
int _currentIndex = 0;
int _totalLength = 1;
@override
Widget build(BuildContext context) {
_init();
return PlatformScaffold(
body: _buildBody(),
bottomNavBar: _buildNavBar()
);
}
void _init(){
// if(null == bloc){
// bloc = HomeBloc(AppProvider.getApplication(context));
// }
if(bloc != null)
return;
bloc = HomeBloc(AppProvider.getApplication(context));
}
@override
@@ -28,33 +49,61 @@ class _HomeScreenState extends State<HomeScreen> {
// bloc.dispose();
}
@override
Widget build(BuildContext context) {
_init();
Widget _buildBody() {
var widgetMap = [
<Widget>[
_buildAppBar("Gallery"),
_buildGridView()
],
<Widget>[
_buildAppBar("Albums"),
SliverToBoxAdapter()
],
<Widget>[
_buildAppBar("Settings"),
SliverToBoxAdapter()
],
];
return CustomScrollView(
shrinkWrap: true,
slivers: widgetMap[_currentIndex],
);
}
return PlatformScaffold(
body: _buildGridView(),
bottomNavBar: _buildNavBar()
Widget _buildAppBar(String title) {
return SliverAppBar(
title: new Text(title),
pinned: false,
snap: false,
floating: true,
leading: PlatformIconButton(
icon: Icon(PlatformIcons(context).person),
),
actions: <Widget>[
PlatformIconButton(
icon: Icon(PlatformIcons(context).search),
),
PlatformIconButton(
icon: Icon(PlatformIcons(context).add),
),
],
);
}
Widget _buildNavBar() {
return PlatformNavBar(
currentIndex: 0,
itemChanged: (index) => setState(
() {
// _selectedTabIndex = index;
print(index);
},
),
currentIndex: _currentIndex,
itemChanged: (index) => setState(() {
_currentIndex = index;
}),
items: [
BottomNavigationBarItem(
label: "Gallery",
icon: Icon(PlatformIcons(context).collections),
label: "Photos",
icon: Icon(isMaterial(context) ? Icons.insert_photo : CupertinoIcons.photo),
),
BottomNavigationBarItem(
label: "Me",
icon: Icon(PlatformIcons(context).person),
label: "Albums",
icon: Icon(isMaterial(context) ? Icons.collections : CupertinoIcons.photo_on_rectangle),
),
BottomNavigationBarItem(
label: "Settings",
@@ -64,42 +113,90 @@ class _HomeScreenState extends State<HomeScreen> {
);
}
Widget _buildGridView() {
// return GridView.builder(
// itemCount: 5,
// gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
// childAspectRatio: (1 / 1),
// crossAxisCount: 2),
// itemBuilder: (BuildContext context, int index) {
// return _buildCard("https://i.imgur.com/CgSGqUz.jpeg");
// }
// Widget _buildPhotoPreview(){
// return Container(
// child: PhotoViewGallery.builder(
// scrollPhysics: const BouncingScrollPhysics(),
// builder: (BuildContext context, int index) {
// return PhotoViewGalleryPageOptions(
// imageProvider: AssetImage(widget.galleryItems[index].image),
// initialScale: PhotoViewComputedScale.contained * 0.8,
// heroAttributes: PhotoViewHeroAttributes(tag: galleryItems[index].id),
// );
return new StaggeredGridView.countBuilder(
crossAxisCount: 4,
itemCount: 80,
itemBuilder: (BuildContext context, int index) => _buildCard("https://i.imgur.com/CgSGqUz.jpeg"),
// itemBuilder: (BuildContext context, int index) => new Container(
// color: Colors.green,
// child: new Center(
// child: new CircleAvatar(
// backgroundColor: Colors.white,
// child: new Text('$index'),
// },
// itemCount: _totalLength,
// loadingBuilder: (context, event) => Center(
// child: Container(
// width: 20.0,
// height: 20.0,
// child: CircularProgressIndicator(
// value: event == null
// ? 0
// : event.cumulativeBytesLoaded / event.expectedTotalBytes,
// ),
// )),
staggeredTileBuilder: (int index) =>
// new StaggeredTile.count(2, index.isEven ? 2 : 1),
new StaggeredTile.fit(2),
// ),
// ),
// // backgroundDecoration: widget.backgroundDecoration,
// // pageController: widget.pageController,
// // onPageChanged: onPageChanged,
// )
// );
// }
Widget _appLoading(){
return Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 370,
child: Column(
children: <Widget>[
PlatformCircularProgressIndicator()
],
),
),
),
);
}
Widget _buildGridView() {
MediaQueryData queryData = MediaQuery.of(context);
// Can change this to change desired image size
final int desiredContentWidth = 500;
return SliverStaggeredGrid.extentBuilder(
itemCount: _totalLength,
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
maxCrossAxisExtent: 500 / queryData.devicePixelRatio,
staggeredTileBuilder: (int index) => new StaggeredTile.fit(1),
itemBuilder: (BuildContext context, int index) {
return bloc.getMedia(index, desiredContentWidth);
// return _buildCard(index, desiredContentWidth);
},
);
}
Widget _buildCard(charImageUrl) {
return new Image.network(
charImageUrl,
fit: BoxFit.contain,
Widget _buildCard(index, derivedContentWidth) {
return FutureBuilder<CachedNetworkImage>(
future: bloc.getMedia(index, derivedContentWidth),
builder: (context, snapshot) {
if (!snapshot.hasData)
return SizedBox(width: 500, height: 500);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_totalLength == bloc.totalMediaItems)
return;
setState(() {
_totalLength = bloc.totalMediaItems;
});
});
return snapshot.data;
}
);
}
}

View File

@@ -1,12 +1,13 @@
import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:imagini/blocs/login_bloc.dart';
import 'package:imagini/core/app_provider.dart';
class LoginScreen extends StatefulWidget {
static const String PATH = '/Login';
static const String PATH = '/';
LoginScreen({Key key}) : super(key: key);
@@ -16,7 +17,41 @@ class LoginScreen extends StatefulWidget {
class _LoginScreenState extends State<LoginScreen> {
// LoginBloc bloc;
LoginBloc bloc;
@override
Widget build(BuildContext context) {
_init();
return Scaffold(
body: StreamBuilder<bool>(
stream: bloc.authenticatedResult,
builder: (context, snapshot) {
if (snapshot.data == null || snapshot.data == true)
return _appLoading();
return _appLogin();
}
)
);
}
void _init(){
if(bloc != null)
return;
bloc = LoginBloc(AppProvider.getApplication(context));
bloc.authenticatedResult.listen((bool status) {
if (status)
AppProvider.getRouter(context).navigateTo(context, "/Home", transition: TransitionType.fadeIn);
});
bloc.loginResult.listen((bool status) {
if (status == null || status == false)
return;
AppProvider.getRouter(context).navigateTo(context, "/Home", transition: TransitionType.fadeIn);
});
}
@override
void dispose() {
@@ -24,46 +59,90 @@ class _LoginScreenState extends State<LoginScreen> {
// bloc.dispose();
}
@override
Widget build(BuildContext context) {
_init();
Widget _appLoading(){
return Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 275,
child: Column(
children: <Widget>[
Container(
child: Image(image: AssetImage('assets/imagini_full.png')),
width: 175,
margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
),
PlatformCircularProgressIndicator()
],
),
),
),
);
}
return Scaffold(
body: Center(
Widget _appLogin(){
TextEditingController serverController = new TextEditingController();
TextEditingController userController = new TextEditingController();
TextEditingController passwordController = new TextEditingController();
final _formKey = GlobalKey<FormState>();
return Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 500,
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Container(
child: FittedBox(
fit: BoxFit.contain,
child: const FlutterLogo(),
),
child: Image(image: AssetImage('assets/imagini_full.png')),
width: 175,
margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
),
Expanded(
child: TextField(
child: TextFormField(
controller: serverController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter server address';
}
return null;
},
decoration: InputDecoration(
labelText: 'Server Address'
),
),
),
Expanded(
child: TextField(
child: TextFormField(
controller: userController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter username or email';
}
return null;
},
decoration: InputDecoration(
labelText: 'Username / Email'
),
),
),
Expanded(
child: TextField(
child: TextFormField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
controller: passwordController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter password';
}
return null;
},
decoration: InputDecoration(
labelText: 'Password'
),
@@ -73,11 +152,31 @@ class _LoginScreenState extends State<LoginScreen> {
width: double.infinity,
child: PlatformButton(
onPressed: () {
AppProvider.getRouter(context).navigateTo(context, "/Home", transition: TransitionType.fadeIn);
if (!_formKey.currentState.validate())
return;
bloc.attemptLogin(userController.text, passwordController.text, serverController.text);
},
child: Text('Login')
),
),
StreamBuilder<bool>(
stream: bloc.loginResult,
builder: (context, snapshot) {
if (snapshot.data == null || snapshot.data == true)
return Container();
return Expanded(
child: Padding(
padding: EdgeInsets.fromLTRB(20, 20, 20, 20),
child: Text(
"Login Failed",
style: TextStyle(color: Colors.red),
)
),
);
}
)
],
),
),
@@ -85,10 +184,4 @@ class _LoginScreenState extends State<LoginScreen> {
),
);
}
void _init(){
// if(null == bloc){
// bloc = LoginBloc(AppProvider.getApplication(context));
// }
}
}

View File

@@ -1,70 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:fluro/fluro.dart';
import 'package:imagini/core/app_provider.dart';
import 'package:imagini/blocs/splash_bloc.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
class SplashScreen extends StatefulWidget {
static const String PATH = '/';
SplashScreen({Key key}) : super(key: key);
@override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
SplashBloc bloc;
@override
void dispose() {
super.dispose();
bloc.dispose();
}
@override
Widget build(BuildContext context) {
_init();
return Scaffold(
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 270,
child: Column(
children: <Widget>[
Container(
child: FittedBox(
fit: BoxFit.contain,
child: const FlutterLogo(),
),
width: 175,
margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
),
PlatformCircularProgressIndicator()
],
),
),
),
),
);
}
void _init(){
if(null == bloc){
bloc = SplashBloc(AppProvider.getApplication(context));
bloc.loginResult.listen((Login$Query$AuthResponse lr) {
if (lr.result == AuthResult.success) {
AppProvider.getRouter(context).navigateTo(context, "/Home", transition: TransitionType.fadeIn);
} else {
AppProvider.getRouter(context).navigateTo(context, "/Login", transition: TransitionType.fadeIn);
}
});
}
}
}

View File

@@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "14.0.0"
version: "12.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "0.41.2"
version: "0.40.6"
archive:
dependency: transitive
description:
@@ -30,33 +30,26 @@ packages:
source: hosted
version: "1.6.0"
artemis:
dependency: "direct main"
dependency: "direct dev"
description:
name: artemis
url: "https://pub.dartlang.org"
source: hosted
version: "6.18.3"
version: "6.18.4"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0-nullsafety.3"
bloc:
dependency: transitive
description:
name: bloc
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.1"
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety.3"
version: "2.1.0"
build:
dependency: transitive
description:
@@ -77,7 +70,7 @@ packages:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.7"
version: "2.1.8"
build_resolvers:
dependency: transitive
description:
@@ -112,21 +105,35 @@ packages:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.0-nullsafety.0"
version: "8.0.0"
built_value_generator:
dependency: "direct dev"
description:
name: built_value_generator
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.0"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.1"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-nullsafety.5"
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0-nullsafety.3"
version: "1.2.0"
checked_yaml:
dependency: transitive
description:
@@ -147,7 +154,7 @@ packages:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-nullsafety.3"
version: "1.1.0"
code_builder:
dependency: transitive
description:
@@ -161,7 +168,7 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0-nullsafety.5"
version: "1.15.0"
connectivity:
dependency: transitive
description:
@@ -217,7 +224,7 @@ packages:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.12"
version: "1.3.10"
equatable:
dependency: "direct main"
description:
@@ -231,28 +238,21 @@ packages:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0-nullsafety.3"
version: "1.2.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
version: "1.0.0"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.0-nullsafety.4"
file_picker:
dependency: "direct main"
description:
name: file_picker
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.6"
version: "6.0.0"
fixnum:
dependency: transitive
description:
@@ -272,32 +272,39 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: "direct main"
flutter_blurhash:
dependency: transitive
description:
name: flutter_bloc
name: flutter_blurhash
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
version: "0.5.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
flutter_driver:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_launcher_icons:
dependency: "direct main"
description:
name: flutter_launcher_icons
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.1"
flutter_platform_widgets:
dependency: "direct main"
description:
name: flutter_platform_widgets
url: "https://pub.dartlang.org"
source: hosted
version: "0.80.0-beta.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.11"
version: "0.80.0"
flutter_secure_storage:
dependency: "direct main"
description:
@@ -439,6 +446,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.4"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.19"
integration_test:
dependency: "direct dev"
description: flutter
@@ -450,14 +464,14 @@ packages:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.4"
version: "0.3.5"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3-nullsafety.3"
version: "0.6.3"
json_annotation:
dependency: "direct main"
description:
@@ -478,21 +492,21 @@ packages:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "0.11.4"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10-nullsafety.3"
version: "0.12.10"
meta:
dependency: "direct main"
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0-nullsafety.6"
version: "1.3.0"
mime:
dependency: transitive
description:
@@ -500,13 +514,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4"
node_interop:
dependency: transitive
description:
@@ -528,6 +535,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.7"
octo_image:
dependency: transitive
description:
name: octo_image
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
package_config:
dependency: transitive
description:
@@ -541,7 +555,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0-nullsafety.3"
version: "1.8.0"
path_provider:
dependency: transitive
description:
@@ -576,21 +590,42 @@ packages:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4+3"
version: "0.0.5"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
version: "1.11.0"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
photo_manager:
dependency: "direct main"
description:
name: photo_manager
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.0"
photo_view:
dependency: "direct main"
description:
name: photo_view
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.3"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0-nullsafety.4"
version: "3.0.0"
plugin_platform_interface:
dependency: transitive
description:
@@ -604,21 +639,14 @@ packages:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
version: "1.5.0"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0-nullsafety.4"
provider:
dependency: transitive
description:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.3"
version: "4.0.0"
pub_semver:
dependency: transitive
description:
@@ -633,6 +661,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.8"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
recase:
dependency: transitive
description:
@@ -647,48 +682,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.24.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.12+4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.2+4"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+11"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2+7"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.2+3"
shelf:
dependency: transitive
description:
@@ -702,7 +695,7 @@ packages:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.4"
version: "0.2.4+1"
sky_engine:
dependency: transitive
description: flutter
@@ -714,28 +707,42 @@ packages:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.10+1"
version: "0.9.10+2"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0-nullsafety.4"
version: "1.8.0"
sqflite:
dependency: transitive
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.2+4"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3+1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0-nullsafety.6"
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety.3"
version: "2.1.0"
stream_transform:
dependency: transitive
description:
@@ -749,7 +756,7 @@ packages:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0-nullsafety.3"
version: "1.1.0"
sync_http:
dependency: transitive
description:
@@ -757,20 +764,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0+2"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0-nullsafety.3"
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19-nullsafety.6"
version: "0.2.19"
timing:
dependency: transitive
description:
@@ -784,49 +798,14 @@ packages:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0-nullsafety.5"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "5.7.10"
url_launcher_linux:
version: "1.3.0"
uuid:
dependency: transitive
description:
name: url_launcher_linux
name: uuid
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+4"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+9"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.9"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5+3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+3"
version: "2.2.2"
uuid_enhanced:
dependency: transitive
description:
@@ -840,7 +819,7 @@ packages:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0-nullsafety.5"
version: "2.1.0"
vm_service:
dependency: transitive
description:
@@ -882,7 +861,7 @@ packages:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.4+1"
version: "2.0.0"
xdg_directories:
dependency: transitive
description:
@@ -890,6 +869,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "4.5.1"
yaml:
dependency: transitive
description:
@@ -898,5 +884,5 @@ packages:
source: hosted
version: "2.2.1"
sdks:
dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.22.0"
dart: ">=2.12.0-259.9.beta <3.0.0"
flutter: ">=1.22.2"

View File

@@ -1,21 +1,8 @@
name: imagini
description: A new Flutter project.
description: Imagini Client
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
version: 0.0.1
environment:
sdk: ">=2.7.0 <3.0.0"
@@ -23,22 +10,21 @@ environment:
dependencies:
flutter:
sdk: flutter
photo_view: ^0.10.3
photo_manager: ^0.6.0
graphql_flutter: ^4.0.1
flutter_staggered_grid_view: "^0.3.2"
fluro: ^1.7.8
equatable: ^1.2.5
http: ^0.12.2
shared_preferences: ^0.5.12+4
http: ^0.12.0+2
flutter_secure_storage: ^3.3.5
flutter_platform_widgets: ^0.80.0-beta.0
flutter_bloc: ^6.1.1
file_picker: ^2.1.5
url_launcher: ^5.7.10
cupertino_icons: ^1.0.1
artemis: '>=6.0.0 <7.0.0' # only if you're using ArtemisClient!
json_annotation: ^3.1.0
meta: '>=1.0.0 <2.0.0' # only if you have non nullable fields
meta: '>=1.0.0 <2.0.0'
gql: '>=0.12.3 <1.0.0'
cached_network_image: ^2.5.1
cupertino_icons: ^1.0.1
flutter_launcher_icons: ^0.8.1
dev_dependencies:
flutter_test:
@@ -47,46 +33,15 @@ dev_dependencies:
sdk: flutter
artemis: '>=6.0.0 <7.0.0'
build_runner: ^1.10.4
built_value_generator: any
json_serializable: ^3.5.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
assets:
- assets/imagini_full.png
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
flutter_icons:
android: "launcher_icon"
ios: true
image_path: "assets/icon/icon.png"

View File

@@ -158,6 +158,8 @@ type MediaItem {
isVideo: Boolean! @meta(gorm: "default:false;not null")
fileName: String! @meta(gorm: "not null")
origName: String! @meta(gorm: "not null")
width: Int! @meta(gorm: "not null")
height: Int! @meta(gorm: "not null")
tags: [Tag] @meta(gorm: "many2many:media_tags;foreignKey:ID,UserID;References:ID")
albums: [Album] @meta(gorm: "many2many:media_albums;foreignKey:ID,UserID;Refrences:ID")
userID: ID! @meta(gorm: "not null")

View File

@@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:imagini/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}