Compare commits

...

14 Commits

71 changed files with 2576 additions and 1087 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
imagini.db
media/
notes notes
web/node_modules/ web/node_modules/
web/dist/ 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 A self hosted photo library with user management & authentication. Cross platform Client supporting Android, iOS, and Web.
# cd ./cmd/
## 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 github.com/99designs/gqlgen generate
go run main.go generate go run cmd/main.go generate
## Generate GraphQL Documentation # Generate GraphQL Documentation
# From app root
graphdoc -e http://localhost:8484/query -o ./docs/schema 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" "net/http"
"time" "time"
"github.com/h2non/bimg"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"reichard.io/imagini/internal/api" "reichard.io/imagini/internal/api"
@@ -44,6 +45,9 @@ func (s *Server) StartServer() {
} }
go func() { go func() {
bimg.VipsCacheSetMax(0)
bimg.VipsCacheSetMaxMem(0)
err := s.httpServer.ListenAndServe() err := s.httpServer.ListenAndServe()
if err != nil { if err != nil {
log.Error("Error starting server ", err) log.Error("Error starting server ", err)

8
go.mod
View File

@@ -4,21 +4,15 @@ go 1.15
require ( require (
github.com/99designs/gqlgen v0.13.0 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/gabriel-vasile/mimetype v1.1.2
github.com/google/uuid v1.1.5 github.com/google/uuid v1.1.5
github.com/h2non/bimg v1.1.5
github.com/iancoleman/strcase v0.1.3 github.com/iancoleman/strcase v0.1.3
github.com/lestrrat-go/jwx v1.0.8 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/sirupsen/logrus v1.7.0
github.com/tus/tusd v1.4.0
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
github.com/vektah/gqlparser/v2 v2.1.0 github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad 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/driver/sqlite v1.1.4
gorm.io/gorm v1.20.9 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 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA=
github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk= 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/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.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0= github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs= github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/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/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 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/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.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/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/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
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/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= 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 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU=
github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= 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-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.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 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/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/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 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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/bimg v1.1.5 h1:o3xsUBxM8s7+e7PmpiWIkEYdeYayJ94eh4cJLx67m1k=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= 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.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 h1:dJBk1m2/qjL1twPLf68JND55vvivMupZ4wIzE8CTdBw=
github.com/iancoleman/strcase v0.1.3/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= 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 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 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 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/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/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 h1:2ABaTa5ifB1L90aoRMjaPa97p0WzzVe93Vggv8oZftw=
github.com/lestrrat-go/backoff/v2 v2.0.3/go.mod h1:mU93bMXuG27/Y5erI5E9weqavpTX5qiVFZI4uXAX0xk= 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/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 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/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/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/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= 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-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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-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.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 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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/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.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.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/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/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 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/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/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/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 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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/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 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 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.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.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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.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/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.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 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= 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/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 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= 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= 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 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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-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-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 h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 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.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 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-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-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-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-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-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/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.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-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-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-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-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290 h1:NXNmtp0ToD36cui5IqWy95LC4Y6vT/4y3RnPxlQPinU= 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-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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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 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/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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= 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.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.9 h1:M3aIZKXAC1PtPVu9t3WGwkBTE1le5c2telz3I/qjRNg= gorm.io/gorm v1.20.9 h1:M3aIZKXAC1PtPVu9t3WGwkBTE1le5c2telz3I/qjRNg=
gorm.io/gorm v1.20.9/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= 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 v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= sourcegraph.com/sourcegraph/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 CreatedAt func(childComplexity int) int
ExifDate func(childComplexity int) int ExifDate func(childComplexity int) int
FileName func(childComplexity int) int FileName func(childComplexity int) int
Height func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
IsVideo func(childComplexity int) int IsVideo func(childComplexity int) int
Latitude func(childComplexity int) int Latitude func(childComplexity int) int
@@ -95,6 +96,7 @@ type ComplexityRoot struct {
Tags func(childComplexity int) int Tags func(childComplexity int) int
UpdatedAt func(childComplexity int) int UpdatedAt func(childComplexity int) int
UserID func(childComplexity int) int UserID func(childComplexity int) int
Width func(childComplexity int) int
} }
MediaItemResponse struct { MediaItemResponse struct {
@@ -363,6 +365,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.MediaItem.FileName(childComplexity), true 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": case "MediaItem.id":
if e.complexity.MediaItem.ID == nil { if e.complexity.MediaItem.ID == nil {
break break
@@ -419,6 +428,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.MediaItem.UserID(childComplexity), true 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": case "MediaItemResponse.data":
if e.complexity.MediaItemResponse.Data == nil { if e.complexity.MediaItemResponse.Data == nil {
break break
@@ -1019,6 +1035,8 @@ type MediaItem {
isVideo: Boolean! @meta(gorm: "default:false;not null") isVideo: Boolean! @meta(gorm: "default:false;not null")
fileName: String! @meta(gorm: "not null") fileName: String! @meta(gorm: "not null")
origName: 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") tags: [Tag] @meta(gorm: "many2many:media_tags;foreignKey:ID,UserID;References:ID")
albums: [Album] @meta(gorm: "many2many:media_albums;foreignKey:ID,UserID;Refrences:ID") albums: [Album] @meta(gorm: "many2many:media_albums;foreignKey:ID,UserID;Refrences:ID")
userID: ID! @meta(gorm: "not null") 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) 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) { func (ec *executionContext) _MediaItem_tags(ctx context.Context, field graphql.CollectedField, obj *model.MediaItem) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { 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 { if out.Values[i] == graphql.Null {
invalids++ 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": case "tags":
out.Values[i] = ec._MediaItem_tags(ctx, field, obj) out.Values[i] = ec._MediaItem_tags(ctx, field, obj)
case "albums": case "albums":

View File

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

View File

@@ -108,6 +108,8 @@ type MediaItem struct {
IsVideo bool `json:"isVideo" gorm:"default:false;not null"` IsVideo bool `json:"isVideo" gorm:"default:false;not null"`
FileName string `json:"fileName" gorm:"not null"` FileName string `json:"fileName" gorm:"not null"`
OrigName string `json:"origName" 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"` 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"` Albums []*Album `json:"albums" gorm:"many2many:media_albums;foreignKey:ID,UserID;Refrences:ID"`
UserID string `json:"userID" gorm:"not null"` UserID string `json:"userID" gorm:"not null"`

View File

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

View File

@@ -8,6 +8,7 @@ import (
"context" "context"
"errors" "errors"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path" "path"
@@ -16,6 +17,8 @@ import (
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/h2non/bimg"
log "github.com/sirupsen/logrus"
"reichard.io/imagini/graph/generated" "reichard.io/imagini/graph/generated"
"reichard.io/imagini/graph/model" "reichard.io/imagini/graph/model"
) )
@@ -32,6 +35,7 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
// Copy headers into the buffer // Copy headers into the buffer
if _, err := input.File.File.Read(fileHeader); err != nil { if _, err := input.File.File.Read(fileHeader); err != nil {
log.Error("[upload] Failed to read file header:", err)
return nil, errors.New("Upload Failed") 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/") { if strings.HasPrefix(contentType, "image/") {
isVideo = false isVideo = false
} else if strings.HasPrefix(contentType, "video/") { } 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 { } else {
log.Error("[upload] File is neither an image or video")
return nil, errors.New("Upload Failed") 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) filePath := path.Join(folderPath + "/" + fileName)
// Create File // 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 { if err != nil {
log.Error("[upload] Unable to open file handle:", err)
return nil, errors.New("Upload Failed") return nil, errors.New("Upload Failed")
} }
defer f.Close() defer f.Close()
// Copy header to file // Concat File
_, err = io.Copy(f, bytes.NewReader(fileHeader)) concatFile := io.MultiReader(bytes.NewReader(fileHeader), input.File.File)
// Copy file
_, err = io.Copy(f, concatFile)
if err != nil { if err != nil {
log.Error("[upload] Unable to copy file:", err)
return nil, errors.New("Upload Failed") return nil, errors.New("Upload Failed")
} }
// Copy remaining file // Load Image
_, err = io.Copy(f, input.File.File) 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 { 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") return nil, errors.New("Upload Failed")
} }
// Create MediaItem From EXIF Data if meta.Orientation > 4 {
mediaItem, err := mediaItemFromEXIFData(filePath) mediaItem.Width = imageSize.Height
if err != nil { mediaItem.Height = imageSize.Width
return nil, errors.New("Upload Failed") } else {
mediaItem.Width = imageSize.Width
mediaItem.Height = imageSize.Height
} }
// Add Additional MediaItem Fields // Add Additional MediaItem Fields
@@ -89,6 +118,7 @@ func (r *mutationResolver) CreateMediaItem(ctx context.Context, input model.NewM
// Create MediaItem in DB // Create MediaItem in DB
err = r.DB.CreateMediaItem(mediaItem) err = r.DB.CreateMediaItem(mediaItem)
if err != nil { if err != nil {
log.Error("[upload] Unable to populate create file in DB:", err)
return nil, errors.New("Upload Failed") 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 // Update Access Token
accessTokenCookie, err := api.Auth.CreateJWTAccessToken(user, device) accessToken, err := api.Auth.CreateJWTAccessToken(user, device)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return accessTokenCookie, "", err return accessToken, "", err
} }
func (api *API) validateTokens(w *http.ResponseWriter, r *http.Request) (jwt.Token, error) { func (api *API) validateTokens(w *http.ResponseWriter, r *http.Request) (jwt.Token, error) {
// TODO: Check from X-Imagini-AccessToken accessTokenHeader := r.Header.Get("X-Imagini-AccessToken")
// TODO: Check from X-Imagini-RefreshToken if accessTokenHeader != "" {
accessToken, err := api.Auth.ValidateJWTAccessToken(accessTokenHeader)
// Validate Access Token
accessCookie, _ := r.Cookie("AccessToken")
if accessCookie != nil {
accessToken, err := api.Auth.ValidateJWTAccessToken(accessCookie.Value)
if err == nil { if err == nil {
return accessToken, nil return accessToken, nil
} }
} }
// Validate Refresh Cookie Exists refreshTokenHeader := r.Header.Get("X-Imagini-RefreshToken")
refreshCookie, _ := r.Cookie("RefreshToken") if refreshTokenHeader == "" {
if refreshCookie == nil {
return nil, errors.New("Tokens Invalid") 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 // 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 { if err != nil {
return nil, errors.New("Tokens Invalid") 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 // TODO: Actually Refresh Refresh Token
newRefreshToken = refreshCookie.Value // newRefreshToken = refreshCookie.Value
newRefreshToken = refreshTokenHeader
// Set appropriate cookies (TODO: Only for web!) // Set appropriate cookies (TODO: Only for web!)
// Update Access & Refresh Cookies // Update Access & Refresh Cookies
http.SetCookie(*w, &http.Cookie{ // http.SetCookie(*w, &http.Cookie{
Name: "AccessToken", // Name: "AccessToken",
Value: newAccessToken, // Value: newAccessToken,
}) // })
http.SetCookie(*w, &http.Cookie{ // http.SetCookie(*w, &http.Cookie{
Name: "RefreshToken", // Name: "RefreshToken",
Value: newRefreshToken, // 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-AccessToken", newAccessToken)
(*w).Header().Set("X-Imagini-RefreshToken", newRefreshToken) (*w).Header().Set("X-Imagini-RefreshToken", newRefreshToken)

View File

@@ -1,10 +1,16 @@
package api package api
import ( import (
"bytes"
"errors"
"net/http" "net/http"
"os" "os"
"path" "path"
"strconv"
"time"
"github.com/h2non/bimg"
log "github.com/sirupsen/logrus"
"reichard.io/imagini/graph/model" "reichard.io/imagini/graph/model"
) )
@@ -23,15 +29,7 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) {
} }
// Acquire Width & Height Parameters // Acquire Width & Height Parameters
query := r.URL.Query() width, _ := strconv.Atoi(r.URL.Query().Get("width"))
width := query["width"]
height := query["height"]
_ = width
_ = height
// TODO: Caching & Resizing
// - If both, force resize with new scale
// - If one, scale resize proportionally
// Pull out userID // Pull out userID
authContext := r.Context().Value("auth").(*model.AuthContext) 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 // Check if File Exists
_, err := os.Stat(mediaPath) _, err := os.Stat(mediaPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
// TODO: Different HTTP Response Code? log.Warn("[media] Image does not exist on disk")
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusInternalServerError)
return 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 { func (api *API) authMiddleware(next http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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 // Validate Tokens
accessToken, err := api.validateTokens(&w, r) accessToken, err := api.validateTokens(&w, r)
if err != nil { 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) # Chrome
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) flutter run -d chrome
For help getting started with Flutter, view our # Simulator
[online documentation](https://flutter.dev/docs), which offers tutorials, open -a Simulator
samples, guidance on mobile development, and a full API reference. flutter run
## Building
# Generate GraphQL Flutter Models
flutter pub run build_runner build

View File

@@ -2,7 +2,7 @@
package="com.reichard.imagini"> package="com.reichard.imagini">
<application <application
android:label="Imagini" android:label="Imagini"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/launcher_icon">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:launchMode="singleTop" 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,58 @@
PODS: PODS:
- DKImagePickerController/Core (4.3.2): - connectivity (0.0.1):
- 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
- Flutter - Flutter
- Reachability
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_secure_storage (3.3.1): - flutter_secure_storage (3.3.1):
- Flutter - Flutter
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- integration_test (0.0.1): - integration_test (0.0.1):
- Flutter - Flutter
- SDWebImage (5.10.2): - path_provider (0.0.1):
- SDWebImage/Core (= 5.10.2)
- SDWebImage/Core (5.10.2)
- shared_preferences (0.0.1):
- Flutter - Flutter
- SwiftyGif (5.3.0) - Reachability (3.2)
- url_launcher (0.0.1): - sqflite (0.0.2):
- Flutter - Flutter
- FMDB (>= 2.7.5)
DEPENDENCIES: DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`) - connectivity (from `.symlinks/plugins/connectivity/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- DKImagePickerController - FMDB
- DKPhotoGallery - Reachability
- SDWebImage
- SwiftyGif
EXTERNAL SOURCES: EXTERNAL SOURCES:
file_picker: connectivity:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/connectivity/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_secure_storage: flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios" :path: ".symlinks/plugins/flutter_secure_storage/ios"
integration_test: integration_test:
:path: ".symlinks/plugins/integration_test/ios" :path: ".symlinks/plugins/integration_test/ios"
shared_preferences: path_provider:
:path: ".symlinks/plugins/shared_preferences/ios" :path: ".symlinks/plugins/path_provider/ios"
url_launcher: sqflite:
:path: ".symlinks/plugins/url_launcher/ios" :path: ".symlinks/plugins/sqflite/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
integration_test: 5ed24a436eb7ec17b6a13046e9bf7ca4a404e59e FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
SDWebImage: b969dcfc02c40a5da71eac0b03b8f1a0c794a86f integration_test: 6eb66a19f7104200dcfdd62bc0077e1b09686e4f
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c

View File

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

@@ -1,61 +1,57 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart' show BoxFit, Center, SizedBox;
// ignore: uri_does_not_exist import 'package:imagini/api/cookie_client/cookie_client.dart'
import 'cookie_client_stub.dart' if (dart.library.html) 'package:imagini/api/cookie_client/browser_cookie_client.dart'
// ignore: uri_does_not_exist if (dart.library.io) 'package:imagini/api/cookie_client/io_cookie_client.dart';
if (dart.library.html) 'browser_cookie_client.dart'
// ignore: uri_does_not_exist
if (dart.library.io) 'io_cookie_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:graphql_flutter/graphql_flutter.dart';
import 'package:imagini/graphql/imagini_graphql.dart'; import 'package:imagini/graphql/imagini_graphql.dart';
class APIProvider{ class APIProvider{
String _server, _accessToken, _refreshToken;
GraphQLClient _client;
HttpLink httpLink;
// CookieLink cookieLink;
static const String _GRAPHQL_ENDPOINT = "/query"; static const String _GRAPHQL_ENDPOINT = "/query";
APIProvider({ BaseStorageClient _storage;
@required String server, GraphQLClient _client;
String accessToken, HttpLink httpLink;
String refreshToken
}) { APIProvider(BaseStorageClient storage) {
_server = server; _storage = storage;
_accessToken = accessToken; }
_refreshToken = refreshToken;
Future<void> init() async {
String _server = await _storage.get("server");
// Initialize
if (_server == null)
_server = "http://localhost";
httpLink = HttpLink(_server + _GRAPHQL_ENDPOINT, httpLink = HttpLink(_server + _GRAPHQL_ENDPOINT,
httpClient: getCookieClient(), httpClient: getCookieClient(_storage),
); );
// cookieLink = CookieLink(_updateAccessToken, _updateRefreshToken);
_client = GraphQLClient( _client = GraphQLClient(
cache: GraphQLCache(), cache: GraphQLCache(),
link: httpLink, link: httpLink,
); );
} }
// void _updateAccessToken(_accessToken) { Future<QueryResult> login(
// print("Updating Access Token: $_accessToken");
// this._accessToken = _accessToken;
// }
// void _updateRefreshToken(_refreshToken) {
// print("Updating Refresh Token: $_accessToken");
// this._refreshToken = _refreshToken;
// }
Future<Login$Query$AuthResponse> login([
String username, String username,
String password, String password,
]) async { String server,
) async {
assert( 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( QueryResult response = await _client.query(
QueryOptions( QueryOptions(
document: LoginQuery().document, document: LoginQuery().document,
@@ -65,79 +61,53 @@ class APIProvider{
}, },
) )
); );
return response;
final loginResponse = Login$Query.fromJson(response.data);
return loginResponse.login;
} }
Future<Me$Query$User> me() async { 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( QueryResult response = await _client.query(
QueryOptions( QueryOptions(
document: MeQuery().document, document: MeQuery().document,
) )
); );
return response;
final meResponse = Me$Query.fromJson(response.data);
return meResponse.me;
} }
Future<String> mediaItems([ Future<QueryResult> mediaItems(Page page) async {
String startDate, QueryResult response = await _client.query(
String endDate, QueryOptions(
String albumID, document: MediaItemsQuery().document,
List<String> tagID, variables: {
String type, // TODO: Make enum "page": page,
int page, },
]) async { )
// Query: );
// /api/v1/MediaItems return response;
// 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;
} }
void dispose() {} void dispose() {}

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

@@ -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'; import 'package:http/http.dart';
BaseClient getCookieClient() => throw UnsupportedError( BaseClient getCookieClient(storage) => throw UnsupportedError(
'Cannot create a client without dart:html or dart:io.'); 'Cannot create a client without dart:html or dart:io.');

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,12 +1,45 @@
import 'dart:async';
import 'package:imagini/api/api_provider.dart'; import 'package:imagini/api/api_provider.dart';
import 'package:imagini/graphql/imagini_graphql.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 { class ImaginiAPIRepository {
APIProvider _apiProvider; APIProvider _apiProvider;
ImaginiAPIRepository(this._apiProvider); ImaginiAPIRepository(this._apiProvider);
Stream<Login$Query$AuthResponse> login(String user, password) { Stream<bool> login(String user, password, server) {
return Stream.fromFuture(_apiProvider.login(user, password)); 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;
}));
}
Future<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,68 @@
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';
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;
}
void dispose(){
_loginController.close();
_authenticatedController.close();
}
Future<CachedNetworkImage> getMedia(int index, derivedContentWidth) async {
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 @override
void dispose() async { void dispose() async {
// Log.info('dispose');
super.dispose(); super.dispose();
await _application.onTerminate(); 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/home_screen.dart';
import 'package:imagini/screens/login_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( // var splashHandler = new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) { // handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return SplashScreen(); // return SplashScreen();
} // }
); // );
var loginHandler = new Handler( var loginHandler = new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) { handlerFunc: (BuildContext context, Map<String, List<String>> params) {
@@ -42,7 +42,7 @@ class AppRoutes {
return; return;
} }
); );
router.define(SplashScreen.PATH, handler: splashHandler); // router.define(SplashScreen.PATH, handler: splashHandler);
router.define(LoginScreen.PATH, handler: loginHandler); router.define(LoginScreen.PATH, handler: loginHandler);
router.define(HomeScreen.PATH, handler: homeHandler); router.define(HomeScreen.PATH, handler: homeHandler);
// router.define(AppDetailPage.PATH, handler: appDetailRouteHandler); // 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/core/app_routes.dart';
import 'package:imagini/api/api_provider.dart'; import 'package:imagini/api/api_provider.dart';
import 'package:imagini/api/imagini_api_repository.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 { class ImaginiApplication {
FluroRouter router; FluroRouter router;
ImaginiAPIRepository imaginiAPI; ImaginiAPIRepository imaginiAPI;
BaseStorageClient storageClient;
Future<void> onCreate() async { Future<void> onCreate() async {
_initRouter(); _initRouter();
_initAPIRepository(); _initStorageClient();
await _initAPIRepository();
} }
Future<void> onTerminate() async {} Future<void> onTerminate() async {}
@@ -20,8 +27,14 @@ class ImaginiApplication {
AppRoutes.configureRoutes(router); AppRoutes.configureRoutes(router);
} }
_initAPIRepository() { _initStorageClient() {
APIProvider apiProvider = new APIProvider(server: "http://localhost:8484"); storageClient = getStorageClient();
}
_initAPIRepository() async {
// TODO: Get from config
APIProvider apiProvider = new APIProvider(storageClient);
await apiProvider.init();
imaginiAPI = ImaginiAPIRepository(apiProvider); 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'; import 'package:imagini/helpers/upload_serializer.dart';
part 'imagini_graphql.graphql.g.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) @JsonSerializable(explicitToJson: true)
class Login$Query$AuthResponse$Device with EquatableMixin { class Login$Query$AuthResponse$Device with EquatableMixin {
Login$Query$AuthResponse$Device(); Login$Query$AuthResponse$Device();
@@ -115,69 +171,374 @@ class CreateMediaItem$Mutation with EquatableMixin {
} }
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class Me$Query$User with EquatableMixin { class MediaItems$Query$MediaItemResponse$MediaItem with EquatableMixin {
Me$Query$User(); MediaItems$Query$MediaItemResponse$MediaItem();
factory Me$Query$User.fromJson(Map<String, dynamic> json) => factory MediaItems$Query$MediaItemResponse$MediaItem.fromJson(
_$Me$Query$UserFromJson(json); Map<String, dynamic> json) =>
_$MediaItems$Query$MediaItemResponse$MediaItemFromJson(json);
String id; String id;
String fileName;
double latitude;
double longitude;
bool isVideo;
int width;
int height;
String origName;
DateTime createdAt; 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) int total;
Role role;
@JsonKey(unknownEnumValue: AuthType.artemisUnknown) @override
AuthType authType; 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 @override
List<Object> get props => [ List<Object> get props => [
id, id,
createdAt, createdAt,
updatedAt, updatedAt,
email, exifDate,
username, latitude,
firstName, longitude,
lastName, isVideo,
role, origName,
authType tags,
albums
]; ];
Map<String, dynamic> toJson() => _$Me$Query$UserToJson(this); Map<String, dynamic> toJson() => _$MediaItemFilterToJson(this);
} }
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class Me$Query with EquatableMixin { class TagFilter with EquatableMixin {
Me$Query(); TagFilter({this.id, this.createdAt, this.updatedAt, this.name});
factory Me$Query.fromJson(Map<String, dynamic> json) => factory TagFilter.fromJson(Map<String, dynamic> json) =>
_$Me$QueryFromJson(json); _$TagFilterFromJson(json);
Me$Query$User me; IDFilter id;
TimeFilter createdAt;
TimeFilter updatedAt;
StringFilter name;
@override @override
List<Object> get props => [me]; List<Object> get props => [id, createdAt, updatedAt, name];
Map<String, dynamic> toJson() => _$Me$QueryToJson(this); Map<String, dynamic> toJson() => _$TagFilterToJson(this);
} }
enum AuthResult { @JsonSerializable(explicitToJson: true)
@JsonValue('Success') class AlbumFilter with EquatableMixin {
success, AlbumFilter({this.id, this.createdAt, this.updatedAt, this.name});
@JsonValue('Failure')
failure, factory AlbumFilter.fromJson(Map<String, dynamic> json) =>
@JsonValue('ARTEMIS_UNKNOWN') _$AlbumFilterFromJson(json);
artemisUnknown,
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 { enum Role {
@JsonValue('Admin') @JsonValue('Admin')
admin, admin,
@@ -194,6 +555,106 @@ enum AuthType {
@JsonValue('ARTEMIS_UNKNOWN') @JsonValue('ARTEMIS_UNKNOWN')
artemisUnknown, 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) @JsonSerializable(explicitToJson: true)
class LoginArguments extends JsonSerializable with EquatableMixin { class LoginArguments extends JsonSerializable with EquatableMixin {
@@ -425,86 +886,172 @@ class CreateMediaItemMutation
CreateMediaItem$Mutation.fromJson(json); CreateMediaItem$Mutation.fromJson(json);
} }
class MeQuery extends GraphQLQuery<Me$Query, JsonSerializable> { @JsonSerializable(explicitToJson: true)
MeQuery(); 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 @override
final DocumentNode document = DocumentNode(definitions: [ final DocumentNode document = DocumentNode(definitions: [
OperationDefinitionNode( OperationDefinitionNode(
type: OperationType.query, type: OperationType.query,
name: NameNode(value: 'me'), name: NameNode(value: 'mediaItems'),
variableDefinitions: [], 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: [], directives: [],
selectionSet: SelectionSetNode(selections: [ selectionSet: SelectionSetNode(selections: [
FieldNode( FieldNode(
name: NameNode(value: 'me'), name: NameNode(value: 'mediaItems'),
alias: null, alias: null,
arguments: [], 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: [], directives: [],
selectionSet: SelectionSetNode(selections: [ selectionSet: SelectionSetNode(selections: [
FieldNode( FieldNode(
name: NameNode(value: 'id'), name: NameNode(value: 'data'),
alias: null, alias: null,
arguments: [], arguments: [],
directives: [], directives: [],
selectionSet: null), selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'id'),
alias: null,
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)
])),
FieldNode( FieldNode(
name: NameNode(value: 'createdAt'), name: NameNode(value: 'page'),
alias: null, alias: null,
arguments: [], arguments: [],
directives: [], directives: [],
selectionSet: null), selectionSet: SelectionSetNode(selections: [
FieldNode( FieldNode(
name: NameNode(value: 'updatedAt'), name: NameNode(value: 'size'),
alias: null, alias: null,
arguments: [], arguments: [],
directives: [], directives: [],
selectionSet: null), selectionSet: null),
FieldNode( FieldNode(
name: NameNode(value: 'email'), name: NameNode(value: 'page'),
alias: null, alias: null,
arguments: [], arguments: [],
directives: [], directives: [],
selectionSet: null), selectionSet: null),
FieldNode( FieldNode(
name: NameNode(value: 'username'), name: NameNode(value: 'total'),
alias: null, alias: null,
arguments: [], arguments: [],
directives: [], directives: [],
selectionSet: null), 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 @override
final String operationName = 'me'; final String operationName = 'mediaItems';
@override @override
List<Object> get props => [document, operationName]; final MediaItemsArguments variables;
@override @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 // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
Login$Query$AuthResponse$Device _$Login$Query$AuthResponse$DeviceFromJson( Me$Query$User _$Me$Query$UserFromJson(Map<String, dynamic> json) {
Map<String, dynamic> json) { return Me$Query$User()
return Login$Query$AuthResponse$Device()..id = json['id'] as String; ..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( Map<String, dynamic> _$Me$Query$UserToJson(Me$Query$User instance) =>
Login$Query$AuthResponse$Device instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, 'id': instance.id,
}; 'createdAt': instance.createdAt?.toIso8601String(),
'updatedAt': instance.updatedAt?.toIso8601String(),
Login$Query$AuthResponse _$Login$Query$AuthResponseFromJson( 'email': instance.email,
Map<String, dynamic> json) { 'username': instance.username,
return Login$Query$AuthResponse() 'firstName': instance.firstName,
..result = _$enumDecodeNullable(_$AuthResultEnumMap, json['result'], 'lastName': instance.lastName,
unknownValue: AuthResult.artemisUnknown) 'role': _$RoleEnumMap[instance.role],
..device = json['device'] == null 'authType': _$AuthTypeEnumMap[instance.authType],
? 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(),
}; };
T _$enumDecode<T>( T _$enumDecode<T>(
@@ -67,6 +70,58 @@ T _$enumDecodeNullable<T>(
return _$enumDecode<T>(enumValues, source, unknownValue: unknownValue); 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 = { const _$AuthResultEnumMap = {
AuthResult.success: 'Success', AuthResult.success: 'Success',
AuthResult.failure: 'Failure', AuthResult.failure: 'Failure',
@@ -137,61 +192,322 @@ Map<String, dynamic> _$CreateMediaItem$MutationToJson(
'createMediaItem': instance.createMediaItem?.toJson(), 'createMediaItem': instance.createMediaItem?.toJson(),
}; };
Me$Query$User _$Me$Query$UserFromJson(Map<String, dynamic> json) { MediaItems$Query$MediaItemResponse$MediaItem
return Me$Query$User() _$MediaItems$Query$MediaItemResponse$MediaItemFromJson(
Map<String, dynamic> json) {
return MediaItems$Query$MediaItemResponse$MediaItem()
..id = json['id'] as String ..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 ..createdAt = json['createdAt'] == null
? null ? null
: DateTime.parse(json['createdAt'] as String) : 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> _$Me$Query$UserToJson(Me$Query$User instance) => Map<String, dynamic> _$MediaItems$Query$MediaItemResponse$MediaItemToJson(
MediaItems$Query$MediaItemResponse$MediaItem instance) =>
<String, dynamic>{ <String, dynamic>{
'id': instance.id, '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(), '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 = { MediaItems$Query$MediaItemResponse$PageResponse
Role.admin: 'Admin', _$MediaItems$Query$MediaItemResponse$PageResponseFromJson(
Role.user: 'User', Map<String, dynamic> json) {
Role.artemisUnknown: 'ARTEMIS_UNKNOWN', return MediaItems$Query$MediaItemResponse$PageResponse()
}; ..size = json['size'] as int
..page = json['page'] as int
const _$AuthTypeEnumMap = { ..total = json['total'] as int;
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>{ Map<String, dynamic> _$MediaItems$Query$MediaItemResponse$PageResponseToJson(
'me': instance.me?.toJson(), 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) { LoginArguments _$LoginArgumentsFromJson(Map<String, dynamic> json) {
return LoginArguments( return LoginArguments(
user: json['user'] as String, user: json['user'] as String,
@@ -219,3 +535,25 @@ Map<String, dynamic> _$CreateMediaItemArgumentsToJson(
<String, dynamic>{ <String, dynamic>{
'file': fromDartMultipartFileToGraphQLUpload(instance.file), '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,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:imagini/blocs/home_bloc.dart';
import 'package:imagini/core/app_provider.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
static const String PATH = '/Home'; static const String PATH = '/Home';
@@ -14,12 +19,26 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> { 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(){ void _init(){
// if(null == bloc){ if(bloc != null)
// bloc = HomeBloc(AppProvider.getApplication(context)); return;
// }
bloc = HomeBloc(AppProvider.getApplication(context));
} }
@override @override
@@ -28,78 +47,123 @@ class _HomeScreenState extends State<HomeScreen> {
// bloc.dispose(); // bloc.dispose();
} }
@override Widget _buildBody() {
Widget build(BuildContext context) { var widgetMap = [
_init(); <Widget>[
_buildAppBar("Gallery"),
_buildGridView()
],
<Widget>[
_buildAppBar("Albums"),
SliverToBoxAdapter()
],
<Widget>[
_buildAppBar("Settings"),
SliverToBoxAdapter()
],
];
return CustomScrollView(
shrinkWrap: true,
slivers: widgetMap[_currentIndex],
);
}
return PlatformScaffold( Widget _buildAppBar(String title) {
body: _buildGridView(), return SliverAppBar(
bottomNavBar: _buildNavBar() 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() { Widget _buildNavBar() {
return PlatformNavBar( return PlatformNavBar(
currentIndex: 0, currentIndex: _currentIndex,
itemChanged: (index) => setState( itemChanged: (index) => setState(() {
() { _currentIndex = index;
// _selectedTabIndex = index; }),
print(index); items: [
}, BottomNavigationBarItem(
label: "Photos",
icon: Icon(isMaterial(context) ? Icons.insert_photo : CupertinoIcons.photo),
), ),
items: [ BottomNavigationBarItem(
BottomNavigationBarItem( label: "Albums",
label: "Gallery", icon: Icon(isMaterial(context) ? Icons.collections : CupertinoIcons.photo_on_rectangle),
icon: Icon(PlatformIcons(context).collections), ),
BottomNavigationBarItem(
label: "Settings",
icon: Icon(PlatformIcons(context).settings),
),
],
);
}
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()
],
), ),
BottomNavigationBarItem( ),
label: "Me", ),
icon: Icon(PlatformIcons(context).person), );
),
BottomNavigationBarItem(
label: "Settings",
icon: Icon(PlatformIcons(context).settings),
),
],
);
} }
Widget _buildGridView() { Widget _buildGridView() {
// return GridView.builder( MediaQueryData queryData = MediaQuery.of(context);
// itemCount: 5,
// gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
// childAspectRatio: (1 / 1),
// crossAxisCount: 2),
// itemBuilder: (BuildContext context, int index) {
// return _buildCard("https://i.imgur.com/CgSGqUz.jpeg");
// }
// );
// Can change this to change desired image size
final int desiredContentWidth = 500;
return new StaggeredGridView.countBuilder( return SliverStaggeredGrid.extentBuilder(
crossAxisCount: 4, itemCount: _totalLength,
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'),
// ),
// )),
staggeredTileBuilder: (int index) =>
// new StaggeredTile.count(2, index.isEven ? 2 : 1),
new StaggeredTile.fit(2),
mainAxisSpacing: 4.0, mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0, crossAxisSpacing: 4.0,
maxCrossAxisExtent: 500 / queryData.devicePixelRatio,
staggeredTileBuilder: (int index) => new StaggeredTile.fit(1),
itemBuilder: (BuildContext context, int index) {
return _buildCard(index, desiredContentWidth);
},
); );
} }
Widget _buildCard(charImageUrl) { Widget _buildCard(index, derivedContentWidth) {
return new Image.network( return FutureBuilder<CachedNetworkImage>(
charImageUrl, future: bloc.getMedia(index, derivedContentWidth),
fit: BoxFit.contain, 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:flutter/material.dart';
import 'package:fluro/fluro.dart'; import 'package:fluro/fluro.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:imagini/blocs/login_bloc.dart';
import 'package:imagini/core/app_provider.dart'; import 'package:imagini/core/app_provider.dart';
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
static const String PATH = '/Login'; static const String PATH = '/';
LoginScreen({Key key}) : super(key: key); LoginScreen({Key key}) : super(key: key);
@@ -16,7 +17,41 @@ class LoginScreen extends StatefulWidget {
class _LoginScreenState extends State<LoginScreen> { 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 @override
void dispose() { void dispose() {
@@ -24,46 +59,90 @@ class _LoginScreenState extends State<LoginScreen> {
// bloc.dispose(); // bloc.dispose();
} }
@override Widget _appLoading(){
Widget build(BuildContext context) { return Center(
_init(); 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( Widget _appLogin(){
body: Center( TextEditingController serverController = new TextEditingController();
child: ConstrainedBox( TextEditingController userController = new TextEditingController();
constraints: BoxConstraints(maxWidth: 500), TextEditingController passwordController = new TextEditingController();
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0), final _formKey = GlobalKey<FormState>();
height: 500,
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( child: Column(
children: <Widget>[ children: <Widget>[
Container( Container(
child: FittedBox( child: Image(image: AssetImage('assets/imagini_full.png')),
fit: BoxFit.contain,
child: const FlutterLogo(),
),
width: 175, width: 175,
margin: EdgeInsets.fromLTRB(0, 0, 0, 50), margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
), ),
Expanded( Expanded(
child: TextField( child: TextFormField(
controller: serverController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter server address';
}
return null;
},
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Server Address' labelText: 'Server Address'
), ),
), ),
), ),
Expanded( Expanded(
child: TextField( child: TextFormField(
controller: userController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter username or email';
}
return null;
},
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Username / Email' labelText: 'Username / Email'
), ),
), ),
), ),
Expanded( Expanded(
child: TextField( child: TextFormField(
obscureText: true, obscureText: true,
enableSuggestions: false, enableSuggestions: false,
autocorrect: false, autocorrect: false,
controller: passwordController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter password';
}
return null;
},
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password' labelText: 'Password'
), ),
@@ -73,11 +152,31 @@ class _LoginScreenState extends State<LoginScreen> {
width: double.infinity, width: double.infinity,
child: PlatformButton( child: PlatformButton(
onPressed: () { 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') 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

@@ -30,12 +30,12 @@ packages:
source: hosted source: hosted
version: "1.6.0" version: "1.6.0"
artemis: artemis:
dependency: "direct main" dependency: "direct dev"
description: description:
name: artemis name: artemis
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.18.3" version: "6.18.4"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -43,13 +43,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.5.0-nullsafety.3" version: "2.5.0-nullsafety.3"
bloc:
dependency: transitive
description:
name: bloc
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.1"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -113,6 +106,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.0.0-nullsafety.0" version: "8.0.0-nullsafety.0"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@@ -246,13 +246,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.0-nullsafety.4" 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"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@@ -272,18 +265,32 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_bloc: flutter_blurhash:
dependency: "direct main" dependency: transitive
description: description:
name: flutter_bloc name: flutter_blurhash
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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: flutter_driver:
dependency: transitive dependency: transitive
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_platform_widgets:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -291,13 +298,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.80.0-beta.0" 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"
flutter_secure_storage: flutter_secure_storage:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -439,6 +439,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.4" version: "3.1.4"
image:
dependency: transitive
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.19"
integration_test: integration_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -500,13 +507,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4"
node_interop: node_interop:
dependency: transitive dependency: transitive
description: description:
@@ -528,6 +528,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.7" 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: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -584,6 +591,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -612,13 +626,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.0-nullsafety.4" version: "4.0.0-nullsafety.4"
provider:
dependency: transitive
description:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.3"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@@ -647,48 +654,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.24.1" 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: shelf:
dependency: transitive dependency: transitive
description: description:
@@ -722,6 +687,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0-nullsafety.4" version: "1.8.0-nullsafety.4"
sqflite:
dependency: transitive
description:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.2+3"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3+1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@@ -757,6 +736,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0+2"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@@ -785,48 +771,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.5" version: "1.3.0-nullsafety.5"
url_launcher: uuid:
dependency: "direct main"
description:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "5.7.10"
url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: uuid
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+4" version: "2.2.2"
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"
uuid_enhanced: uuid_enhanced:
dependency: transitive dependency: transitive
description: description:
@@ -890,6 +841,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.2" version: "0.1.2"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "4.5.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@@ -899,4 +857,4 @@ packages:
version: "2.2.1" version: "2.2.1"
sdks: sdks:
dart: ">=2.12.0-0.0 <3.0.0" dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.22.0" flutter: ">=1.22.2"

View File

@@ -1,21 +1,8 @@
name: imagini 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 publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1
# 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
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"
@@ -28,17 +15,14 @@ dependencies:
fluro: ^1.7.8 fluro: ^1.7.8
equatable: ^1.2.5 equatable: ^1.2.5
http: ^0.12.2 http: ^0.12.2
shared_preferences: ^0.5.12+4
flutter_secure_storage: ^3.3.5 flutter_secure_storage: ^3.3.5
flutter_platform_widgets: ^0.80.0-beta.0 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 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' gql: '>=0.12.3 <1.0.0'
cached_network_image: ^2.5.0
cupertino_icons: ^1.0.1
flutter_launcher_icons: ^0.8.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -49,44 +33,12 @@ dev_dependencies:
build_runner: ^1.10.4 build_runner: ^1.10.4
json_serializable: ^3.5.0 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: 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 uses-material-design: true
assets:
- assets/imagini_full.png
# To add assets to your application, add an assets section, like this: flutter_icons:
# assets: android: "launcher_icon"
# - images/a_dot_burr.jpeg ios: true
# - images/a_dot_ham.jpeg image_path: "assets/icon/icon.png"
# 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

View File

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