Compare commits
85 Commits
feature/au
...
develop
Author | SHA1 | Date | |
---|---|---|---|
4ee3de773c | |||
d4ca478b44 | |||
d10ce7c7ad | |||
655ecd1a0f | |||
70fe86a9a5 | |||
17b44170d0 | |||
f2d6a72204 | |||
f752865d33 | |||
85008d3265 | |||
f34b7e4439 | |||
b36ae791cb | |||
50ec72fcf4 | |||
0b93b0875e | |||
6f757002b1 | |||
18dc4135c4 | |||
54e8cf23f7 | |||
5649cd2aad | |||
f032e83e71 | |||
19f0c8e0a4 | |||
d0cd9842ea | |||
27458b5b94 | |||
e5152aa652 | |||
1eaaa9065f | |||
7d0831ee57 | |||
0859202987 | |||
7a6eedab9d | |||
89a147565c | |||
9b8adafe60 | |||
a3fa793706 | |||
27720219ee | |||
f4528dd087 | |||
fb954a3e5b | |||
92efdbd568 | |||
137709adea | |||
6cdbea92d1 | |||
f169169bc7 | |||
61eacefd6c | |||
11f54ab66e | |||
772b09381c | |||
978cc65c41 | |||
596108b4f4 | |||
6845e1ce50 | |||
04b32772fc | |||
17747e998d | |||
12151ff613 | |||
71102cfb3b | |||
7dad33b6e4 | |||
9c6ebae9bc | |||
3ef495445a | |||
bc56c9dbae | |||
c95fbf6915 | |||
952b1b6a8d | |||
4d5251c724 | |||
44d4db079a | |||
089d91a84c | |||
2d66888ed3 | |||
406202ddc4 | |||
9cb5a63cc9 | |||
0fe6e1f07a | |||
d6eae3a7d3 | |||
39d266f701 | |||
f03a0c96dc | |||
32c19bace3 | |||
5790c91d82 | |||
680614148c | |||
fc4912882a | |||
ac41b301a9 | |||
c0ee95234d | |||
8d9d839acf | |||
e990184a0b | |||
4a340529da | |||
bc9aa1721a | |||
c4373cce46 | |||
8708e30020 | |||
676ddf3bc8 | |||
7bf4c4f080 | |||
303ea6b1d6 | |||
ccf911322b | |||
0cb6c7c67e | |||
08bd11f4d9 | |||
0d308acd5c | |||
36c253d4d7 | |||
ed219ddd11 | |||
758c166f27 | |||
05dd505d6b |
@ -4,4 +4,4 @@ OIDC_CLIENT_SECRET=daddycool
|
||||
OIDC_POST_LOGOUT_REDIRECT_URL=http://localhost:8081/logout/redirect
|
||||
HTTP_COOKIE_AUTHENTICATION_KEY=cL87ucJJSGt7XSjRuQe7GDb2qna8ijfQ
|
||||
HTTP_COOKIE_ENCRYPTION_KEY=cL87ucJJSGt7XSjRuQe7GDb2qna8ijfQ
|
||||
DATABASE_DSN="host=localhost user=daddy database=daddy password=daddy"
|
||||
DATABASE_DSN="host=localhost user=daddy database=daddy password=daddy port=5432 sslmode=disable"
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
/data
|
||||
/bin
|
||||
/.env
|
||||
/release
|
17
Makefile
17
Makefile
@ -1,12 +1,12 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
build: build-docker build-server
|
||||
build: build-server
|
||||
|
||||
build-docker:
|
||||
docker:
|
||||
docker-compose build
|
||||
|
||||
generate:
|
||||
cd internal && go run github.com/99designs/gqlgen generate
|
||||
go generate ./...
|
||||
|
||||
build-server:
|
||||
CGO_ENABLED=0 go build -v -o ./bin/server ./cmd/server
|
||||
@ -15,7 +15,10 @@ deps: generate
|
||||
cd client && npm install
|
||||
go get ./...
|
||||
|
||||
up: build-docker
|
||||
client-dist:
|
||||
cd client && NODE_ENV=production npm run build
|
||||
|
||||
up: docker
|
||||
docker-compose up
|
||||
|
||||
watch:
|
||||
@ -45,7 +48,11 @@ test:
|
||||
hydra-shell:
|
||||
docker-compose exec hydra /bin/sh
|
||||
|
||||
clean: down
|
||||
.PHONY: release
|
||||
release:
|
||||
./misc/script/release
|
||||
|
||||
clean:
|
||||
rm -rf client/node_modules bin data .env internal/graph/generated internal/graph/server.go
|
||||
rm -rf vendor
|
||||
go clean -modcache
|
@ -29,7 +29,7 @@ Les services suivants devraient être disponibles après démarrage de l'environ
|
||||
|Service|Type|Accès|Description|
|
||||
|-------|----|-----|-----------|
|
||||
|Application React|HTTP (UI)|http://localhost:8080/|Page d'accueil de l'application React (serveur Webpack)|
|
||||
|Interface Web GraphQL|HTTP (UI)|http://localhost:8081/api/v1/graphql (GET)|Interface Web de développement de l'API GraphQL (mode debug uniquement, nécessite d'être authentifié)|
|
||||
|Interface Web GraphQL|HTTP (UI)|http://localhost:8081/api/v1/playground|Interface Web de développement de l'API GraphQL (mode debug uniquement, nécessite d'être authentifié)|
|
||||
|Serveur GraphQL|HTTP (GraphQL)|http://localhost:8081/api/v1/graphql (POST)|Point d'entrée de l'API GraphQL|
|
||||
|Serveur Hydra|HTTP (ReST)|http://localhost:4444|Point d'entrée pour l'API OAuth2 d'[Hydra](https://www.ory.sh/hydra/docs/)|
|
||||
|Serveur Hydra Passwordless|HTTP|http://localhost:3000|Point d'entrée pour la ["Login/Consent App"](https://www.ory.sh/hydra/docs/implementing-consent) [hydra-passwordless](https://forge.cadoles.com/wpetit/hydra-passwordless)|
|
||||
|
330
client/package-lock.json
generated
330
client/package-lock.json
generated
@ -1,9 +1,46 @@
|
||||
{
|
||||
"name": "dadd-",
|
||||
"name": "daddy",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.0.2.tgz",
|
||||
"integrity": "sha512-4ighan5Anlj4tK/tdUHs4Mi1njqXZ7AxRCVolz/H702DjPphAJfm+FRkIadPTmwz+OLO+d+tX+6V1VBshf02rg==",
|
||||
"requires": {
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"@wry/context": "^0.5.2",
|
||||
"@wry/equality": "^0.1.9",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"graphql-tag": "^2.10.4",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"optimism": "^0.12.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"symbol-observable": "^1.2.0",
|
||||
"ts-invariant": "^0.4.4",
|
||||
"tslib": "^1.10.0",
|
||||
"zen-observable": "^0.8.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wry/context": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.2.tgz",
|
||||
"integrity": "sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"optimism": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.12.1.tgz",
|
||||
"integrity": "sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ==",
|
||||
"requires": {
|
||||
"@wry/context": "^0.5.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
|
||||
@ -1088,9 +1125,9 @@
|
||||
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
||||
},
|
||||
"@fortawesome/fontawesome-free": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.13.0.tgz",
|
||||
"integrity": "sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.0.tgz",
|
||||
"integrity": "sha512-wXetjQBNMTP59MAYNR1tdahMDOLx3FYj3PKdso7PLFLDpTvmAIqhSSEqnSTmWKahRjD+Sh5I5635+5qaoib5lw==",
|
||||
"dev": true
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
@ -1230,7 +1267,8 @@
|
||||
"@types/node": {
|
||||
"version": "13.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.12.tgz",
|
||||
"integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw=="
|
||||
"integrity": "sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.3",
|
||||
@ -1551,15 +1589,6 @@
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@wry/context": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.4.4.tgz",
|
||||
"integrity": "sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag==",
|
||||
"requires": {
|
||||
"@types/node": ">=6",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@wry/equality": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz",
|
||||
@ -1744,93 +1773,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"apollo-cache": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.5.tgz",
|
||||
"integrity": "sha512-1XoDy8kJnyWY/i/+gLTEbYLnoiVtS8y7ikBr/IfmML4Qb+CM7dEEbIUOjnY716WqmZ/UpXIxTfJsY7rMcqiCXA==",
|
||||
"requires": {
|
||||
"apollo-utilities": "^1.3.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"apollo-cache-inmemory": {
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.6.tgz",
|
||||
"integrity": "sha512-L8pToTW/+Xru2FFAhkZ1OA9q4V4nuvfoPecBM34DecAugUZEBhI2Hmpgnzq2hTKZ60LAMrlqiASm0aqAY6F8/A==",
|
||||
"requires": {
|
||||
"apollo-cache": "^1.3.5",
|
||||
"apollo-utilities": "^1.3.4",
|
||||
"optimism": "^0.10.0",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"apollo-client": {
|
||||
"version": "2.6.10",
|
||||
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.10.tgz",
|
||||
"integrity": "sha512-jiPlMTN6/5CjZpJOkGeUV0mb4zxx33uXWdj/xQCfAMkuNAC3HN7CvYDyMHHEzmcQ5GV12LszWoQ/VlxET24CtA==",
|
||||
"requires": {
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"apollo-cache": "1.3.5",
|
||||
"apollo-link": "^1.0.0",
|
||||
"apollo-utilities": "1.3.4",
|
||||
"symbol-observable": "^1.0.2",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.10.0",
|
||||
"zen-observable": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"apollo-link": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz",
|
||||
"integrity": "sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==",
|
||||
"requires": {
|
||||
"apollo-utilities": "^1.3.0",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.9.3",
|
||||
"zen-observable-ts": "^0.8.21"
|
||||
}
|
||||
},
|
||||
"apollo-link-http": {
|
||||
"version": "1.5.17",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.17.tgz",
|
||||
"integrity": "sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg==",
|
||||
"requires": {
|
||||
"apollo-link": "^1.2.14",
|
||||
"apollo-link-http-common": "^0.2.16",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"apollo-link-http-common": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz",
|
||||
"integrity": "sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg==",
|
||||
"requires": {
|
||||
"apollo-link": "^1.2.14",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"apollo-link-ws": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-ws/-/apollo-link-ws-1.0.20.tgz",
|
||||
"integrity": "sha512-mjSFPlQxmoLArpHBeUb2Xj+2HDYeTaJqFGOqQ+I8NVJxgL9lJe84PDWcPah/yMLv3rB7QgBDSuZ0xoRFBPlySw==",
|
||||
"requires": {
|
||||
"apollo-link": "^1.2.14",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"apollo-utilities": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz",
|
||||
"integrity": "sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==",
|
||||
"requires": {
|
||||
"@wry/equality": "^0.1.2",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"ts-invariant": "^0.4.0",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
|
||||
@ -2897,6 +2839,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"base-x": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
|
||||
"integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
@ -3185,6 +3135,14 @@
|
||||
"pkg-up": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"bs58": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
||||
"integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=",
|
||||
"requires": {
|
||||
"base-x": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"btoa": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
|
||||
@ -3235,14 +3193,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"bulma": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.5.tgz",
|
||||
"integrity": "sha512-cX98TIn0I6sKba/DhW0FBjtaDpxTelU166pf7ICXpCCuplHWyu6C9LYZmL5PEsnePIeJaiorsTEzzNk3Tsm1hw=="
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.0.tgz",
|
||||
"integrity": "sha512-rV75CJkubNUroAt0qCRkjznZLoaXq/ctfMXsMvKSL84UetbSyx5REl96e8GoQ04G4Tkw0XF3STECffTOQrbzOQ=="
|
||||
},
|
||||
"bulma-switch": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.0.tgz",
|
||||
"integrity": "sha512-myD38zeUfjmdduq+pXabhJEe3x2hQP48l/OI+Y0fO3HdDynZUY/VJygucvEAJKRjr4HxD5DnEm4yx+oDOBXpAA=="
|
||||
"bulma-timeline": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/bulma-timeline/-/bulma-timeline-3.0.4.tgz",
|
||||
"integrity": "sha512-gCUOcSUuzHoeVMkCpLF49j5Z5yl78XQ+KgJcT+1ju5WIGgBgVytRUob/dw5NHAxPLO2rmcvwYNbCJFp7w4WT4Q=="
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.0.0",
|
||||
@ -4077,6 +4035,11 @@
|
||||
"randomfill": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"crypto-js": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz",
|
||||
"integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg=="
|
||||
},
|
||||
"css": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
|
||||
@ -4531,9 +4494,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
|
||||
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
@ -5461,6 +5424,11 @@
|
||||
"integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
|
||||
"dev": true
|
||||
},
|
||||
"get-browser-rtc": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz",
|
||||
"integrity": "sha1-u81AyEUaftTvXDc7gWmkCd0dEdk="
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@ -5615,11 +5583,6 @@
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz",
|
||||
"integrity": "sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w=="
|
||||
},
|
||||
"graphql-request": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-2.0.0.tgz",
|
||||
"integrity": "sha512-Ww3Ax+G3l2d+mPT8w7HC9LfrKjutnCKtnDq7ZZp2ghVk5IQDjwAk3/arRF1ix17Ky15rm0hrSKVKxRhIVlSuoQ=="
|
||||
},
|
||||
"graphql-tag": {
|
||||
"version": "2.10.4",
|
||||
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz",
|
||||
@ -6102,8 +6065,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@ -6423,6 +6385,11 @@
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
||||
"dev": true
|
||||
},
|
||||
"isomorphic.js": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.5.tgz",
|
||||
"integrity": "sha512-MkX5lLQApx/8IAIU31PKvpAZosnu2Jqcj1rM8TzxyA4CR96tv3SgMKQNTCxL58G7696Q57zd7ubHV/hTg+5fNA=="
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
@ -6507,11 +6474,6 @@
|
||||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"jwt-decode": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz",
|
||||
"integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
|
||||
},
|
||||
"killable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
|
||||
@ -6548,6 +6510,14 @@
|
||||
"leven": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"lib0": {
|
||||
"version": "0.2.34",
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.34.tgz",
|
||||
"integrity": "sha512-cqsVIMPgFlDtgQcpkt7HOY6W3sbYPIe3qxMnbRSwHTgiQancgm+TRDPx28mC6GUZ6lG6Nr0bIWf4Nog6dWUNUg==",
|
||||
"requires": {
|
||||
"isomorphic.js": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"load-json-file": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
|
||||
@ -6608,9 +6578,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
},
|
||||
"loglevel": {
|
||||
"version": "1.6.8",
|
||||
@ -7109,9 +7079,9 @@
|
||||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
|
||||
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
|
||||
"dev": true
|
||||
},
|
||||
"node-gyp": {
|
||||
@ -7466,14 +7436,6 @@
|
||||
"is-wsl": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"optimism": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.10.3.tgz",
|
||||
"integrity": "sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw==",
|
||||
"requires": {
|
||||
"@wry/context": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"original": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
|
||||
@ -8048,11 +8010,6 @@
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"dev": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.9.4",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
|
||||
"integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ=="
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
@ -8071,11 +8028,15 @@
|
||||
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
|
||||
"dev": true
|
||||
},
|
||||
"queue-microtask": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.1.4.tgz",
|
||||
"integrity": "sha512-eY/4Obve9cE5FK8YvC1cJsm5cr7XvAurul8UtBDJ2PR1p5NmAwHtvAt5ftcLtwYRCUKNhxCneZZlxmUDFoSeKA=="
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
@ -8717,8 +8678,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safe-regex": {
|
||||
"version": "1.1.0",
|
||||
@ -8816,12 +8776,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"selfsigned": {
|
||||
"version": "1.10.7",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
|
||||
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
|
||||
"version": "1.10.8",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
|
||||
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-forge": "0.9.0"
|
||||
"node-forge": "^0.10.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
@ -9034,6 +8994,30 @@
|
||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
||||
"dev": true
|
||||
},
|
||||
"simple-peer": {
|
||||
"version": "9.7.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.7.2.tgz",
|
||||
"integrity": "sha512-xeMyxa9B4V0eA6mf17fVr8nm2QhAYFu+ZZv8zkSFFTjJETGF227CshwobrIYZuspJglMD63egcevQXGOrTIsuA==",
|
||||
"requires": {
|
||||
"debug": "^4.0.1",
|
||||
"get-browser-rtc": "^1.0.0",
|
||||
"queue-microtask": "^1.1.0",
|
||||
"randombytes": "^2.0.3",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"slash": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
|
||||
@ -9463,7 +9447,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
@ -10180,8 +10163,7 @@
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"util.promisify": {
|
||||
"version": "1.0.0",
|
||||
@ -10877,6 +10859,33 @@
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"y-protocols": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.1.tgz",
|
||||
"integrity": "sha512-QP3fCM7c2gGfUi2nqf8gspyO4VW23zv3kNqPNdD3wNxMbuNQenMyoDVZYEo12jzR4RQ3aaDfPK62Sf31SVOmfg==",
|
||||
"requires": {
|
||||
"lib0": "^0.2.28"
|
||||
}
|
||||
},
|
||||
"y-webrtc": {
|
||||
"version": "10.1.6",
|
||||
"resolved": "https://registry.npmjs.org/y-webrtc/-/y-webrtc-10.1.6.tgz",
|
||||
"integrity": "sha512-b3pTIv9LcPuMb4nbDT3/kkgmcuQoTrBmaPbBqPH1LJMzI8HwYnMK8p5r0fBQJBI0YRor+i8BT15Evv1nQBP0zg==",
|
||||
"requires": {
|
||||
"lib0": "^0.2.32",
|
||||
"simple-peer": "^9.7.2",
|
||||
"ws": "^7.2.0",
|
||||
"y-protocols": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
|
||||
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
@ -11002,19 +11011,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"yjs": {
|
||||
"version": "13.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.4.1.tgz",
|
||||
"integrity": "sha512-kIh0sprCTzIm2qyr1VsovkvjKzD2GR4WcU/McJpLAEvImCJHA78Q3S6uSLnhZX0i7FQdrLPCRT8DtTPEH73jnw==",
|
||||
"requires": {
|
||||
"lib0": "^0.2.33"
|
||||
}
|
||||
},
|
||||
"zen-observable": {
|
||||
"version": "0.8.15",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
|
||||
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
|
||||
},
|
||||
"zen-observable-ts": {
|
||||
"version": "0.8.21",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz",
|
||||
"integrity": "sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3",
|
||||
"zen-observable": "^0.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "dadd-",
|
||||
"name": "daddy",
|
||||
"version": "0.0.0",
|
||||
"description": "Daddy",
|
||||
"main": "index.js",
|
||||
@ -25,7 +25,7 @@
|
||||
"@babel/plugin-transform-runtime": "^7.7.4",
|
||||
"@babel/preset-env": "^7.7.1",
|
||||
"@babel/preset-react": "^7.7.4",
|
||||
"@fortawesome/fontawesome-free": "^5.11.2",
|
||||
"@fortawesome/fontawesome-free": "^5.14.0",
|
||||
"@types/node": "^13.13.4",
|
||||
"@types/react-dom": "^16.9.7",
|
||||
"@types/react-redux": "^7.1.7",
|
||||
@ -51,20 +51,13 @@
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.0.2",
|
||||
"@types/qs": "^6.9.3",
|
||||
"apollo-cache-inmemory": "^1.6.6",
|
||||
"apollo-client": "^2.6.10",
|
||||
"apollo-link": "^1.2.14",
|
||||
"apollo-link-http": "^1.5.17",
|
||||
"apollo-link-ws": "^1.0.20",
|
||||
"apollo-utilities": "^1.3.4",
|
||||
"bulma": "^0.7.2",
|
||||
"bulma-switch": "^2.0.0",
|
||||
"bs58": "^4.0.1",
|
||||
"bulma": "^0.9.0",
|
||||
"bulma-timeline": "^3.0.4",
|
||||
"crypto-js": "^4.0.0",
|
||||
"graphql": "^15.3.0",
|
||||
"graphql-request": "^2.0.0",
|
||||
"graphql-tag": "^2.10.4",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-redux": "^7.1.3",
|
||||
@ -74,6 +67,8 @@
|
||||
"redux-saga": "^1.1.3",
|
||||
"styled-components": "^4.4.1",
|
||||
"subscriptions-transport-ws": "^0.9.17",
|
||||
"typescript": "^3.8.3"
|
||||
"typescript": "^3.8.3",
|
||||
"y-webrtc": "^10.1.6",
|
||||
"yjs": "^13.4.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,87 @@
|
||||
import React from 'react';
|
||||
import React, { FunctionComponent, useState, useEffect, Suspense } from 'react';
|
||||
import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom";
|
||||
import { HomePage } from './HomePage/HomePage';
|
||||
import { store } from '../store/store';
|
||||
import { Provider } from 'react-redux';
|
||||
import { useUserProfile } from '../gql/queries/profile';
|
||||
import { LoggedInContext, getSavedLoggedIn, saveLoggedIn } from '../hooks/useLoggedIn';
|
||||
import { PrivateRoute } from './PrivateRoute';
|
||||
import { useKonamiCode } from '../hooks/useKonamiCode';
|
||||
import { Modal } from './Modal';
|
||||
import { createClient } from '../util/apollo';
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
import { AppLoader } from './AppLoader';
|
||||
|
||||
const LazyHomePage = React.lazy(() => import(/* webpackChunkName: "HomePage" */'./HomePage/HomePage'));
|
||||
const LazyDashboardPage = React.lazy(() => import(/* webpackChunkName: "DashboardPage" */'./DashboardPage/DashboardPage'));
|
||||
const LazyUnauthorizedPage = React.lazy(() => import(/* webpackChunkName: "UnauthorizedPage" */'./UnauthorizedPage/UnauthorizedPage'));
|
||||
const LazyConferencePage = React.lazy(() => import(/* webpackChunkName: "ConferencePage" */'./ConferencePage/ConferencePage'));
|
||||
const LazyDecisionSupportFilePage = React.lazy(() => import(/* webpackChunkName: "DecisionSupportFilePage" */'./DecisionSupportFilePage/DecisionSupportFilePage'));
|
||||
const LazyProfilePage = React.lazy(() => import(/* webpackChunkName: "ProfilePage" */'./ProfilePage/ProfilePage'));
|
||||
const LazyWorkgroupPage = React.lazy(() => import(/* webpackChunkName: "WorkgroupPage" */'./WorkgroupPage/WorkgroupPage'));
|
||||
const LazyLogoutPage = React.lazy(() => import(/* webpackChunkName: "LogoutPage" */'./LogoutPage'));
|
||||
|
||||
export interface AppProps {
|
||||
|
||||
}
|
||||
|
||||
export const App: FunctionComponent<AppProps> = () => {
|
||||
const [ loggedIn, setLoggedIn ] = useState(getSavedLoggedIn());
|
||||
|
||||
const client = createClient((loggedIn) => {
|
||||
setLoggedIn(loggedIn);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
saveLoggedIn(loggedIn);
|
||||
}, [loggedIn]);
|
||||
|
||||
const [ showBoneyM, setShowBoneyM ] = useState(false);
|
||||
useKonamiCode(() => setShowBoneyM(true));
|
||||
|
||||
export class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Suspense fallback={<AppLoader />}>
|
||||
<ApolloProvider client={client}>
|
||||
<LoggedInContext.Provider value={loggedIn}>
|
||||
<UserSessionCheck setLoggedIn={setLoggedIn} />
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Route path="/" exact component={HomePage} />
|
||||
<Route path="/" exact component={LazyHomePage} />
|
||||
<Route path="/unauthorized" exact component={LazyUnauthorizedPage} />
|
||||
<PrivateRoute path="/profile" exact component={LazyProfilePage} />
|
||||
<PrivateRoute path="/conference" exact component={LazyConferencePage} />
|
||||
<PrivateRoute path="/workgroups/:id" exact component={LazyWorkgroupPage} />
|
||||
<PrivateRoute path="/decisions/:id" component={LazyDecisionSupportFilePage} />
|
||||
<PrivateRoute path="/dashboard" exact component={LazyDashboardPage} />
|
||||
<PrivateRoute path="/logout" exact component={LazyLogoutPage} />
|
||||
<Route component={() => <Redirect to="/" />} />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
{
|
||||
showBoneyM ?
|
||||
<Modal active={true} showCloseButton={true} onClose={() => setShowBoneyM(false)}>
|
||||
<iframe width={560} height={315}
|
||||
frameBorder={0}
|
||||
allowFullScreen={true}
|
||||
src="https://www.youtube.com/embed/uVzT5QEEQ2c?autoplay=1" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture">
|
||||
</iframe>
|
||||
</Modal> :
|
||||
null
|
||||
}
|
||||
</LoggedInContext.Provider>
|
||||
</ApolloProvider>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
interface UserSessionCheckProps {
|
||||
setLoggedIn: (boolean) => void
|
||||
}
|
||||
|
||||
const UserSessionCheck: FunctionComponent<UserSessionCheckProps> = ({ setLoggedIn }) => {
|
||||
const { user, loading } = useUserProfile();
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
setLoggedIn(user && user.id !== '');
|
||||
}, [user]);
|
||||
|
||||
return null;
|
||||
};
|
9
client/src/components/AppLoader.tsx
Normal file
9
client/src/components/AppLoader.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
export const AppLoader:FunctionComponent = () => {
|
||||
return (
|
||||
<div className="app-loader">
|
||||
<i className="fas fa-spinner fa-spin fa-5x"></i>
|
||||
</div>
|
||||
)
|
||||
}
|
142
client/src/components/ConferencePage/ConferencePage.tsx
Normal file
142
client/src/components/ConferencePage/ConferencePage.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import React, { FunctionComponent, useEffect } from 'react';
|
||||
import { Config } from '../../config';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { useConference } from '../../hooks/useConference';
|
||||
import { Page } from '../Page';
|
||||
import { Gravatar } from './Gravatar';
|
||||
|
||||
export interface ConferencePageProps {
|
||||
|
||||
}
|
||||
|
||||
const StatusHandRaised = 'hand-raised';
|
||||
const StatusThumbsUp = 'thumbs-up';
|
||||
const StatusThumbsDown = 'thumbs-down';
|
||||
const StatusNoVote = 'no-vote';
|
||||
|
||||
export const ConferencePage:FunctionComponent<ConferencePageProps> = () => {
|
||||
const { user } = useUserProfile();
|
||||
const { uuid, data, setNickname, setEmail, ping, setStatus, forget } = useConference();
|
||||
|
||||
const currentStatus = data.statuses[uuid];
|
||||
|
||||
useEffect(() => {
|
||||
if (!user.name && !user.email) return;
|
||||
setNickname(user.name || user.email.split('@')[0]);
|
||||
setEmail(user.email);
|
||||
}, [user.name, user.email]);
|
||||
|
||||
useEffect(() => {
|
||||
ping();
|
||||
const intervalId = setInterval(() => ping(), Config.conferenceHeartbeatInterval + (Math.random() * Config.conferenceHeartbeatInterval/2));
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
const onStatusChange = (status: string) => {
|
||||
setStatus(currentStatus === status ? '' : status);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page title="Conference">
|
||||
<div className="container is-fluid">
|
||||
<section className="mt-5">
|
||||
<h3 className="is-size-3">Mes actions</h3>
|
||||
<div className="buttons has-addons">
|
||||
<button
|
||||
className={`button is-medium ${currentStatus === StatusHandRaised ? 'is-info is-selected' : ''}`}
|
||||
onClick={onStatusChange.bind(null, StatusHandRaised)}>
|
||||
<span className="icon">
|
||||
<i className="fa fa-hand-paper"></i>
|
||||
</span>
|
||||
<span>Lever la main</span>
|
||||
</button>
|
||||
<button
|
||||
className={`button is-medium ${currentStatus === StatusThumbsUp ? 'is-success is-selected' : ''}`}
|
||||
onClick={onStatusChange.bind(null, StatusThumbsUp)}>
|
||||
<span className="icon">
|
||||
<i className="fa fa-thumbs-up"></i>
|
||||
</span>
|
||||
<span>Voter pour</span>
|
||||
</button>
|
||||
<button
|
||||
className={`button is-medium ${currentStatus === StatusNoVote ? 'is-warning is-selected' : ''}`}
|
||||
onClick={onStatusChange.bind(null, StatusNoVote)}>
|
||||
<span className="icon">
|
||||
<i className="fa fa-mitten"></i>
|
||||
</span>
|
||||
<span>Ne se prononce pas</span>
|
||||
</button>
|
||||
<button
|
||||
className={`button is-medium ${currentStatus === StatusThumbsDown ? 'is-danger is-selected' : ''}`}
|
||||
onClick={onStatusChange.bind(null, StatusThumbsDown)}>
|
||||
<span className="icon">
|
||||
<i className="fa fa-thumbs-down"></i>
|
||||
</span>
|
||||
<span>Voter contre</span>
|
||||
</button>
|
||||
</div>
|
||||
<h3 className="is-size-3">Assemblée</h3>
|
||||
<div className="columns mt-1 is-multiline">
|
||||
<UserCard className="column is-narrow"
|
||||
nickname={data.nicknames[uuid]}
|
||||
status={currentStatus}
|
||||
email={user.email} />
|
||||
{
|
||||
Object.keys(data.peers).map(p => {
|
||||
const now = new Date();
|
||||
const lastHeartBeat = new Date(data.peers[p]);
|
||||
|
||||
if (p === uuid) return null;
|
||||
|
||||
if (now.getTime() > lastHeartBeat.getTime() + Config.conferenceHeartbeatInterval*2) {
|
||||
forget(p);
|
||||
return null;
|
||||
}
|
||||
|
||||
const nickname = data.nicknames[p] || '???';
|
||||
const email = data.emails[p] || '';
|
||||
return (
|
||||
<UserCard key={`peer-${p}`} className="column is-narrow"
|
||||
nickname={nickname}
|
||||
status={data.statuses[p]}
|
||||
email={email} />
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConferencePage;
|
||||
|
||||
export interface UserCardProps {
|
||||
nickname: string
|
||||
email: string
|
||||
className?: string
|
||||
status: string
|
||||
};
|
||||
|
||||
export const UserCard:FunctionComponent<UserCardProps> = ({ nickname, email, className, status }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="box">
|
||||
<div className="has-text-centered">
|
||||
<div className="mb-1">
|
||||
{ !status ? <span className="icon"><i className="far fa-2x fa-meh-blank"></i></span> : null }
|
||||
{ status === StatusHandRaised ? <span className="icon has-text-info"><i className="fa fa-2x fa-hand-paper"></i></span> : null }
|
||||
{ status === StatusThumbsUp ? <span className="icon has-text-success"><i className="fa fa-2x fa-thumbs-up"></i></span> : null }
|
||||
{ status === StatusNoVote ? <span className="icon has-text-warning"><i className="fa fa-2x fa-mitten"></i></span> : null }
|
||||
{ status === StatusThumbsDown ? <span className="icon has-text-danger"><i className="fa fa-2x fa-thumbs-down"></i></span> : null }
|
||||
</div>
|
||||
<figure className="image is-128x128 is-inline-block">
|
||||
<Gravatar className="is-rounded" email={email} />
|
||||
</figure>
|
||||
<h4 className="is-size-4">{nickname}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
20
client/src/components/ConferencePage/Gravatar.tsx
Normal file
20
client/src/components/ConferencePage/Gravatar.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
import md5 from 'crypto-js/md5';
|
||||
|
||||
export interface GravatarProps {
|
||||
className?: string
|
||||
email: string
|
||||
}
|
||||
|
||||
const defaultAvatarUrl = 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mp&s=128';
|
||||
|
||||
export const Gravatar:FunctionComponent<GravatarProps> = ({ className, email }) => {
|
||||
const [ avatarUrl, setAvatarUrl ] = useState(defaultAvatarUrl);
|
||||
useEffect(() => {
|
||||
const hash = md5(email.trim().toLowerCase());
|
||||
setAvatarUrl(`https://www.gravatar.com/avatar/${hash}?d=mp&s=128`);
|
||||
}, [email]);
|
||||
return (
|
||||
<img className={className} src={avatarUrl} />
|
||||
);
|
||||
}
|
48
client/src/components/DashboardPage/Dashboard.tsx
Normal file
48
client/src/components/DashboardPage/Dashboard.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { WorkgroupsPanel } from './WorkgroupsPanel';
|
||||
import { DecisionSupportFilePanel } from './DecisionSupportFilePanel';
|
||||
import { Timeline } from '../Timeline';
|
||||
import { useEvents } from '../../gql/queries/event';
|
||||
|
||||
const from = new Date();
|
||||
from.setDate(from.getDate() - 7);
|
||||
|
||||
export function Dashboard() {
|
||||
const { events } = useEvents({
|
||||
variables: {
|
||||
filter: {
|
||||
from,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="columns">
|
||||
<div className="column is-4">
|
||||
<DecisionSupportFilePanel />
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<WorkgroupsPanel />
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<div className="panel is-info">
|
||||
<div className="level panel-heading mb-0">
|
||||
<div className="level-left">
|
||||
<div className="level-item">
|
||||
Ces 7 derniers jours
|
||||
</div>
|
||||
</div>
|
||||
<div className="level-right">
|
||||
<button disabled={true} className="button level-item is-outlined is-info is-inverted">
|
||||
<i className="icon fa fa-sliders-h"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-block">
|
||||
<Timeline events={events} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
17
client/src/components/DashboardPage/DashboardPage.tsx
Normal file
17
client/src/components/DashboardPage/DashboardPage.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { Page } from '../Page';
|
||||
import { Dashboard } from './Dashboard';
|
||||
|
||||
export function DashboardPage() {
|
||||
return (
|
||||
<Page title={'Tableau de bord'}>
|
||||
<div className="container is-fluid">
|
||||
<section className="mt-5">
|
||||
<Dashboard />
|
||||
</section>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardPage;
|
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import { DecisionSupportFile, DecisionSupportFileStatus } from '../../types/decision';
|
||||
import { ItemPanel, TabDefinition, Item } from '../ItemPanel';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { inWorkgroup } from '../../types/workgroup';
|
||||
import { useDecisionSupportFiles } from '../../gql/queries/dsf';
|
||||
|
||||
export function DecisionSupportFilePanel() {
|
||||
const { user } = useUserProfile();
|
||||
const { decisionSupportFiles } = useDecisionSupportFiles();
|
||||
|
||||
const tabs: TabDefinition[] = [
|
||||
{
|
||||
label: 'Mes dossiers en cours',
|
||||
itemFilter: (item: Item) => {
|
||||
const dsf = item as DecisionSupportFile;
|
||||
return (dsf.status === DecisionSupportFileStatus.Draft || dsf.status === DecisionSupportFileStatus.Ready) && inWorkgroup(user, dsf.workgroup);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Brouillons',
|
||||
itemFilter: (item: Item) => (item as DecisionSupportFile).status === DecisionSupportFileStatus.Draft
|
||||
},
|
||||
{
|
||||
label: 'À voter',
|
||||
itemFilter: (item: Item) => (item as DecisionSupportFile).status === DecisionSupportFileStatus.Ready
|
||||
},
|
||||
{
|
||||
label: 'Votés',
|
||||
itemFilter: (item: Item) => (item as DecisionSupportFile).status === DecisionSupportFileStatus.Voted
|
||||
},
|
||||
{
|
||||
label: 'Clos',
|
||||
itemFilter: (item: Item) => (item as DecisionSupportFile).status === DecisionSupportFileStatus.Closed
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ItemPanel
|
||||
className='is-link'
|
||||
title="Dossiers"
|
||||
newItemUrl="/decisions/new"
|
||||
items={decisionSupportFiles}
|
||||
tabs={tabs}
|
||||
itemIconClassName='fas fa-folder'
|
||||
itemKey={item => item.id}
|
||||
itemLabel={item => item.title}
|
||||
itemUrl={item => `/decisions/${item.id}`}
|
||||
/>
|
||||
);
|
||||
}
|
42
client/src/components/DashboardPage/WorkgroupsPanel.tsx
Normal file
42
client/src/components/DashboardPage/WorkgroupsPanel.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { } from 'react';
|
||||
import { Workgroup, inWorkgroup } from '../../types/workgroup';
|
||||
import { useWorkgroups } from '../../gql/queries/workgroups';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { ItemPanel, Item } from '../ItemPanel';
|
||||
|
||||
export function WorkgroupsPanel() {
|
||||
const { workgroups } = useWorkgroups();
|
||||
const { user } = useUserProfile();
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: "Mes groupes en cours",
|
||||
itemFilter: (item: Item) => {
|
||||
const wg = item as Workgroup;
|
||||
return wg.closedAt === null && inWorkgroup(user, wg);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Ouverts",
|
||||
itemFilter: (item: Item) => !(item as Workgroup).closedAt
|
||||
},
|
||||
{
|
||||
label: "Clos",
|
||||
itemFilter: (item: Item) => !!(item as Workgroup).closedAt
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ItemPanel
|
||||
className='is-info'
|
||||
title="Groupes de travail"
|
||||
newItemUrl="/workgroups/new"
|
||||
items={workgroups}
|
||||
tabs={tabs}
|
||||
itemIconClassName='fas fa-users'
|
||||
itemKey={item => item.id}
|
||||
itemLabel={item => item.name}
|
||||
itemUrl={item => `/workgroups/${item.id}`}
|
||||
/>
|
||||
);
|
||||
}
|
25
client/src/components/DecisionSupportFileLink.tsx
Normal file
25
client/src/components/DecisionSupportFileLink.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useWorkgroups } from "../gql/queries/workgroups";
|
||||
import { useDecisionSupportFiles } from "../gql/queries/dsf";
|
||||
|
||||
export interface DecisioSupportFileLinkProps {
|
||||
decisionSupportFileId: number|string
|
||||
}
|
||||
|
||||
export const DecisionSupportFileLink: FunctionComponent<DecisioSupportFileLinkProps> = ({ decisionSupportFileId }) => {
|
||||
const { decisionSupportFiles } = useDecisionSupportFiles({
|
||||
fetchPolicy: "cache-first",
|
||||
variables: {
|
||||
filter: {
|
||||
ids: [decisionSupportFileId]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const title = decisionSupportFiles.length > 0 ? decisionSupportFiles[0].title : `#${decisionSupportFileId}`;
|
||||
|
||||
return (
|
||||
<Link to={`/decisions/${decisionSupportFileId}`}>{title}</Link>
|
||||
);
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import { DecisionSupportFile } from '../../types/decision';
|
||||
|
||||
export interface AppendixPanelProps {
|
||||
dsf: DecisionSupportFile,
|
||||
};
|
||||
|
||||
export const AppendixPanel: FunctionComponent<AppendixPanelProps> = ({ dsf }) => {
|
||||
return (
|
||||
<nav className="panel">
|
||||
<p className="panel-heading">
|
||||
Annexes
|
||||
</p>
|
||||
<div className="panel-block">
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
@ -0,0 +1,140 @@
|
||||
import React, { FunctionComponent, useState, ChangeEvent, useEffect } from 'react';
|
||||
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
import { asDate } from '../../util/date';
|
||||
|
||||
export interface ClarificationSectionProps extends DecisionSupportFileUpdaterProps {};
|
||||
|
||||
const ClarificationSectionName = 'clarification';
|
||||
|
||||
export const ClarificationSection: FunctionComponent<ClarificationSectionProps> = ({ dsf, updateDSF, readOnly }) => {
|
||||
const [ state, setState ] = useState({
|
||||
changed: false,
|
||||
section: {
|
||||
objectives: '',
|
||||
motivations: '',
|
||||
scope: '',
|
||||
nature: '',
|
||||
deadline: undefined,
|
||||
hasDeadline: false,
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.changed) return;
|
||||
updateDSF({ ...dsf, sections: { ...dsf.sections, [ClarificationSectionName]: { ...state.section }} })
|
||||
setState(state => ({ ...state, changed: false }));
|
||||
}, [state.changed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dsf.sections[ClarificationSectionName]) return;
|
||||
setState(state => ({ ...state, changed: false, section: {...state.section, ...dsf.sections[ClarificationSectionName] }}));
|
||||
}, [dsf.sections[ClarificationSectionName]]);
|
||||
|
||||
const onTitleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const title = (evt.currentTarget).value;
|
||||
updateDSF({ ...dsf, title });
|
||||
};
|
||||
|
||||
const onSectionAttrChange = (attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const target = evt.currentTarget;
|
||||
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
|
||||
setState(state => ({ ...state, changed: true, section: {...state.section, [attrName]: value }}));
|
||||
};
|
||||
|
||||
const onDeadlineChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const deadline = evt.currentTarget.valueAsDate;
|
||||
setState(state => ({ ...state, changed: true, section: { ...state.section, deadline }}));
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="field">
|
||||
<label className="label is-medium">Intitulé du dossier</label>
|
||||
<div className="control">
|
||||
<input className="input is-medium" type="text" readOnly={readOnly} value={dsf.title} onChange={onTitleChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label is-medium">Quelle décision devons nous prendre ?</label>
|
||||
<div className="control">
|
||||
<textarea className="textarea is-medium"
|
||||
readOnly={readOnly}
|
||||
value={state.section.objectives}
|
||||
onChange={onSectionAttrChange.bind(null, 'objectives')}
|
||||
placeholder="Décrire globalement les tenants et aboutissants de la décision à prendre."
|
||||
rows={10}>
|
||||
</textarea>
|
||||
</div>
|
||||
<p className="help is-info"><i className="fa fa-info-circle"></i> Ne pas essayer de rentrer trop dans les détails ici. Préférer l'utilisation des annexes et y faire référence.</p>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label is-medium">Pourquoi devons nous prendre cette décision ?</label>
|
||||
<div className="control">
|
||||
<textarea className="textarea is-medium"
|
||||
readOnly={readOnly}
|
||||
value={state.section.motivations}
|
||||
onChange={onSectionAttrChange.bind(null, 'motivations')}
|
||||
placeholder="Décrire pourquoi il est important de prendre cette décision."
|
||||
rows={10}>
|
||||
</textarea>
|
||||
</div>
|
||||
<p className="help is-info"><i className="fa fa-info-circle"></i> Penser à indiquer si des obligations légales pèsent sur cette prise de décision.</p>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label is-medium">Portée de la décision</label>
|
||||
<div className="control">
|
||||
<div className="select is-medium">
|
||||
<select
|
||||
disabled={readOnly}
|
||||
onChange={onSectionAttrChange.bind(null, 'scope')}
|
||||
value={state.section.scope}>
|
||||
<option></option>
|
||||
<option value="individual">Individuelle</option>
|
||||
<option value="identified-group">Groupe identifié</option>
|
||||
<option value="collective">Collective</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label is-medium">Nature de la décision</label>
|
||||
<div className="control">
|
||||
<div className="select is-medium">
|
||||
<select
|
||||
disabled={readOnly}
|
||||
onChange={onSectionAttrChange.bind(null, 'nature')}
|
||||
value={state.section.nature}>
|
||||
<option></option>
|
||||
<option value="operational">Opérationnelle</option>
|
||||
<option value="tactic">Tactique</option>
|
||||
<option value="strategic">Stratégique</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<label className="checkbox">
|
||||
<input type="checkbox"
|
||||
className="is-medium"
|
||||
disabled={readOnly}
|
||||
onChange={onSectionAttrChange.bind(null, 'hasDeadline')}
|
||||
checked={state.section.hasDeadline} />
|
||||
<span className="ml-1 has-text-weight-bold is-size-5">Existe t'il une échéance particulière pour cette décision ?</span>
|
||||
</label>
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
<input disabled={!state.section.hasDeadline}
|
||||
readOnly={readOnly}
|
||||
value={state.section.deadline ? asDate(state.section.deadline).toISOString().substr(0, 10) : ''}
|
||||
onChange={onDeadlineChange}
|
||||
type="date" className="input is-medium" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
import React, { FunctionComponent, useState, ChangeEvent, useEffect } from 'react';
|
||||
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
||||
|
||||
export interface DecisionReportSectionProps extends DecisionSupportFileUpdaterProps {};
|
||||
|
||||
const DecisionReportSectionName = 'decision-report';
|
||||
|
||||
export const DecisionReportSection: FunctionComponent<DecisionReportSectionProps> = ({ dsf, updateDSF, readOnly }) => {
|
||||
const [ state, setState ] = useState({
|
||||
changed: false,
|
||||
section: {
|
||||
report: "",
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.changed) return;
|
||||
updateDSF({ ...dsf, sections: { ...dsf.sections, [DecisionReportSectionName]: { ...state.section }} })
|
||||
setState(state => ({ ...state, changed: false }));
|
||||
}, [state.changed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dsf.sections[DecisionReportSectionName]) return;
|
||||
setState(state => ({ ...state, changed: false, section: {...state.section, ...dsf.sections[DecisionReportSectionName] }}));
|
||||
}, [dsf.sections[DecisionReportSectionName]]);
|
||||
|
||||
const onSectionAttrChange = (attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const target = evt.currentTarget;
|
||||
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
|
||||
setState(state => ({ ...state, changed: true, section: {...state.section, [attrName]: value }}));
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="field">
|
||||
<label className="label is-medium">Compte rendu du vote</label>
|
||||
<div className="control">
|
||||
<textarea className="textarea is-medium"
|
||||
readOnly={readOnly}
|
||||
value={state.section.report}
|
||||
onChange={onSectionAttrChange.bind(null, 'report')}
|
||||
rows={20}>
|
||||
</textarea>
|
||||
</div>
|
||||
<p className="help is-info"><i className="fa fa-info-circle"></i> Penser à indiquer le résultat du vote et les éléments de contexte liés à la prise de décision.</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
@ -0,0 +1,164 @@
|
||||
import React, { FunctionComponent, useState, useEffect } from 'react';
|
||||
import { Page } from '../Page';
|
||||
import { ClarificationSection } from './ClarificationSection';
|
||||
import { MetadataPanel } from './MetadataPanel';
|
||||
import { AppendixPanel } from './AppendixPanel';
|
||||
import { DecisionSupportFile, newDecisionSupportFile, DecisionSupportFileStatus } from '../../types/decision';
|
||||
import { useParams, useHistory } from 'react-router';
|
||||
import { useDecisionSupportFiles } from '../../gql/queries/dsf';
|
||||
import { useCreateDecisionSupportFileMutation, useUpdateDecisionSupportFileMutation } from '../../gql/mutations/dsf';
|
||||
import { OptionsSection } from './OptionsSection';
|
||||
import { useIsAuthorized } from '../../gql/queries/authorization';
|
||||
import { TimelinePanel } from './TimelinePanel';
|
||||
import { DecisionReportSection } from './DecisionReportSection';
|
||||
import { RoutedTabs } from '../RoutedTabs';
|
||||
|
||||
export interface DecisionSupportFilePageProps {
|
||||
|
||||
};
|
||||
|
||||
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
|
||||
const { id } = useParams<any>();
|
||||
const history = useHistory();
|
||||
const { decisionSupportFiles } = useDecisionSupportFiles({
|
||||
variables:{
|
||||
filter: {
|
||||
ids: id !== 'new' ? [id] : undefined,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [ state, setState ] = useState({
|
||||
dsf: newDecisionSupportFile(),
|
||||
saved: true,
|
||||
});
|
||||
|
||||
const { isAuthorized } = useIsAuthorized({
|
||||
variables: {
|
||||
action: 'update',
|
||||
object: {
|
||||
decisionSupportFileId: state.dsf.id,
|
||||
}
|
||||
}
|
||||
}, id === 'new');
|
||||
|
||||
useEffect(() => {
|
||||
const dsf = decisionSupportFiles.length > 0 && decisionSupportFiles[0].id === id ? decisionSupportFiles[0] : {};
|
||||
setState(state => ({ ...state, dsf: { ...state.dsf, ...dsf }}))
|
||||
}, [ decisionSupportFiles ]);
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: "Clarifier la proposition",
|
||||
icon: "fas fa-pen",
|
||||
route: '/info',
|
||||
render: () => (<ClarificationSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />)
|
||||
},
|
||||
{
|
||||
name: "Explorer les options",
|
||||
icon: "fas fa-search",
|
||||
route: '/options',
|
||||
render: () => (<OptionsSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />)
|
||||
},
|
||||
{
|
||||
name: "Prendre la décision",
|
||||
icon: "fas fa-person-booth",
|
||||
route: '/vote',
|
||||
render: () => (<DecisionReportSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />)
|
||||
}
|
||||
];
|
||||
|
||||
const updateDSF = (dsf: DecisionSupportFile) => {
|
||||
setState(state => {
|
||||
return { ...state, saved: false, dsf: { ...state.dsf, ...dsf } };
|
||||
});
|
||||
};
|
||||
|
||||
const [ createDecisionSupportFile ] = useCreateDecisionSupportFileMutation();
|
||||
const [ updateDecisionSupportFile ] = useUpdateDecisionSupportFileMutation();
|
||||
|
||||
const saveDSF = () => {
|
||||
const changes = {
|
||||
title: state.dsf.title !== '' ? state.dsf.title : undefined,
|
||||
status: state.dsf.status,
|
||||
workgroupId: state.dsf.workgroup ? state.dsf.workgroup.id : undefined,
|
||||
sections: state.dsf.sections,
|
||||
};
|
||||
|
||||
if (!changes.workgroupId) return;
|
||||
|
||||
if (state.dsf.id === '') {
|
||||
createDecisionSupportFile({
|
||||
variables: { changes },
|
||||
}).then(({ data }) => {
|
||||
history.push(`/decisions/${data.createDecisionSupportFile.id}`);
|
||||
});
|
||||
} else {
|
||||
updateDecisionSupportFile({
|
||||
variables: { changes, id: state.dsf.id },
|
||||
}).then(({ data }) => {
|
||||
setState(state => {
|
||||
return { ...state, saved: true, dsf: { ...state.dsf, ...data.updateDecisionSupportFile } };
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const canSave = !!state.dsf.workgroup && !state.saved;
|
||||
const isNew = state.dsf.id === '';
|
||||
const isClosed = state.dsf.status === DecisionSupportFileStatus.Closed;
|
||||
|
||||
return (
|
||||
<Page title="Dossier d'Aide à la Décision">
|
||||
<div className="container is-fluid">
|
||||
<section className="mt-5">
|
||||
<div className="level">
|
||||
<div className="level-left">
|
||||
{
|
||||
isNew ?
|
||||
<div className="level-item">
|
||||
<div>
|
||||
<h2 className="is-size-3 title is-spaced">Nouveau</h2>
|
||||
<h3 className="is-size-5 subtitle">Dossier d'Aide à la Décision</h3>
|
||||
</div>
|
||||
</div> :
|
||||
<div className="level-item">
|
||||
<div>
|
||||
<h2 className="is-size-3 title is-spaced">{state.dsf.title} <span className={`ml-3 tag is-warning is-medium ${ isAuthorized ? 'is-hidden' : ''}`}>Lecture seule</span></h2>
|
||||
<h3 className="is-size-5 subtitle">Dossier d'Aide à la Décision <span className="is-italic">{ isClosed ? '(clos)' : null }</span></h3>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="level-right">
|
||||
<div className="level-item buttons">
|
||||
{
|
||||
isAuthorized ?
|
||||
<button className="button is-medium is-success" disabled={!canSave} onClick={saveDSF}>
|
||||
<span className="icon"><i className="fa fa-save"></i></span>
|
||||
<span>Enregistrer</span>
|
||||
</button> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div className="columns mt-3">
|
||||
<div className="column is-8">
|
||||
<div className="box">
|
||||
<RoutedTabs baseRoute={`/decisions/${id}`} tabs={tabs} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<MetadataPanel readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />
|
||||
{/* <AppendixPanel dsf={state.dsf} /> */}
|
||||
<TimelinePanel dsf={state.dsf} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default DecisionSupportFilePage;
|
@ -0,0 +1,7 @@
|
||||
import { DecisionSupportFile } from "../../types/decision";
|
||||
|
||||
export interface DecisionSupportFileUpdaterProps {
|
||||
dsf: DecisionSupportFile
|
||||
updateDSF: (dsf: DecisionSupportFile) => void
|
||||
readOnly: boolean
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
import React, { FunctionComponent, useState, useEffect, ChangeEvent } from 'react';
|
||||
import { DecisionSupportFile, DecisionSupportFileStatus } from '../../types/decision';
|
||||
import { useWorkgroups } from '../../gql/queries/workgroups';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { inWorkgroup } from '../../types/workgroup';
|
||||
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
||||
import { asDate, formatDate } from '../../util/date';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export interface MetadataPanelProps extends DecisionSupportFileUpdaterProps {};
|
||||
|
||||
export const MetadataPanel: FunctionComponent<MetadataPanelProps> = ({ dsf, updateDSF, readOnly }) => {
|
||||
const { user } = useUserProfile();
|
||||
const { workgroups } = useWorkgroups();
|
||||
|
||||
const [ userOpenedWorkgroups, setUserOpenedWorkgroups ] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = workgroups.filter(wg => !wg.closedAt && inWorkgroup(user, wg));
|
||||
setUserOpenedWorkgroups(filtered);
|
||||
}, [workgroups, user])
|
||||
|
||||
const onStatusChanged = (evt: ChangeEvent<HTMLSelectElement>) => {
|
||||
const status = evt.currentTarget.value as DecisionSupportFileStatus;
|
||||
updateDSF({ ...dsf, status });
|
||||
};
|
||||
|
||||
const onWorkgroupChanged = (evt: ChangeEvent<HTMLSelectElement>) => {
|
||||
const workgroupId = evt.currentTarget.value;
|
||||
const workgroup = workgroups.find(wg => wg.id === workgroupId);
|
||||
updateDSF({ ...dsf, workgroup });
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="panel">
|
||||
<p className="panel-heading">
|
||||
Métadonnées
|
||||
</p>
|
||||
<div className="panel-block">
|
||||
<div style={{width:'100%'}}>
|
||||
<div className="field">
|
||||
<div className="label">Groupe de travail</div>
|
||||
{
|
||||
readOnly && dsf.workgroup !== null ?
|
||||
<Link to={`/workgroups/${dsf.workgroup.id}`}>{dsf.workgroup.name}</Link> :
|
||||
<div className="control is-expanded">
|
||||
<div className="select is-fullwidth">
|
||||
<select
|
||||
disabled={readOnly}
|
||||
onChange={onWorkgroupChanged}
|
||||
value={dsf.workgroup ? dsf.workgroup.id : ''}>
|
||||
<option></option>
|
||||
{
|
||||
userOpenedWorkgroups.map(wg => {
|
||||
return (
|
||||
<option key={`wg-${wg.id}`} value={wg.id}>{wg.name}</option>
|
||||
);
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="label">Statut</div>
|
||||
<div className="control is-expanded">
|
||||
<div className="select is-fullwidth">
|
||||
<select
|
||||
disabled={readOnly}
|
||||
onChange={onStatusChanged}
|
||||
value={dsf.status}>
|
||||
<option value="draft">Brouillon</option>
|
||||
<option value="ready">Prêt à voter</option>
|
||||
<option value="voted">Voté</option>
|
||||
<option value="closed">Clôs</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="label">Créé le</div>
|
||||
<div className="control">
|
||||
<p>{formatDate(dsf.createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<div className="label">Voté le</div>
|
||||
<div className="control">
|
||||
<p>{dsf.votedAt ? formatDate(dsf.votedAt) : '--'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
158
client/src/components/DecisionSupportFilePage/OptionsSection.tsx
Normal file
158
client/src/components/DecisionSupportFilePage/OptionsSection.tsx
Normal file
@ -0,0 +1,158 @@
|
||||
import React, { FunctionComponent, useState, useEffect, ChangeEvent, MouseEvent } from 'react';
|
||||
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
|
||||
import { base58UUID } from "../../util/uuid";
|
||||
|
||||
export interface OptionsSectionProps extends DecisionSupportFileUpdaterProps {};
|
||||
|
||||
const OptionsSectionName = 'options';
|
||||
|
||||
export const OptionsSection: FunctionComponent<OptionsSectionProps> = ({ dsf, updateDSF, readOnly }) => {
|
||||
interface OptionsSectionState {
|
||||
changed: boolean
|
||||
section: OptionsSection
|
||||
}
|
||||
|
||||
interface OptionsSection {
|
||||
options: Option[]
|
||||
}
|
||||
|
||||
interface Option {
|
||||
id: string
|
||||
label: string
|
||||
pros: string
|
||||
cons: string
|
||||
}
|
||||
|
||||
const [ state, setState ] = useState<OptionsSectionState>({
|
||||
changed: false,
|
||||
section: {
|
||||
options: [],
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.changed) return;
|
||||
updateDSF({ ...dsf, sections: { ...dsf.sections, [OptionsSectionName]: { ...state.section }} })
|
||||
setState(state => ({ ...state, changed: false }));
|
||||
}, [state.changed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dsf.sections[OptionsSectionName]) return;
|
||||
setState(state => ({ ...state, changed: false, section: {...state.section, ...dsf.sections[OptionsSectionName] }}));
|
||||
}, [dsf.sections[OptionsSectionName]]);
|
||||
|
||||
function newOption(label: string, pros: string, cons: string): Option {
|
||||
return {
|
||||
id: base58UUID(),
|
||||
label,
|
||||
pros,
|
||||
cons
|
||||
};
|
||||
}
|
||||
|
||||
const onAddOptionClick = (evt: MouseEvent) => {
|
||||
const options = JSON.parse(JSON.stringify(state.section.options))
|
||||
const option = newOption("Décision", "", "");
|
||||
options.push(option);
|
||||
setState(state => ({ ...state, changed: true, section: { ...state.section, options }}));
|
||||
};
|
||||
|
||||
const onOptionChange = (id: number, attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const target = evt.currentTarget;
|
||||
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
|
||||
const options = JSON.parse(JSON.stringify(state.section.options))
|
||||
options[id][attrName] = value;
|
||||
setState(state => ({ ...state, changed: true, section: { ...state.section, options }}));
|
||||
};
|
||||
|
||||
const onRemoveOptionClick = (id: number, evt: MouseEvent) => {
|
||||
if(confirm('Voulez-vous supprimer cette option ?')){
|
||||
const options = JSON.parse(JSON.stringify(state.section.options))
|
||||
options.splice(id, 1);
|
||||
setState(state => ({ ...state, changed: true, section: { ...state.section, options }}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h4 id="options-section" className="is-size-4 title is-spaced">Explorer les options</h4>
|
||||
<div className="table-container">
|
||||
<table className={`table is-bordered is-striped is-hoverable is-fullwidth`}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Décision</th>
|
||||
<th>Pours</th>
|
||||
<th>Contres</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
state.section.options.map((o, index) => {
|
||||
return (
|
||||
<tr key={`option-${o.id}`}>
|
||||
<td>
|
||||
<button
|
||||
disabled={readOnly}
|
||||
onClick={onRemoveOptionClick.bind(null, index)}
|
||||
className="button is-danger is-small is-outlined">
|
||||
🗑️
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<textarea className="textarea"
|
||||
readOnly={readOnly}
|
||||
value={o.label}
|
||||
onChange={onOptionChange.bind(null, index, 'label')}
|
||||
placeholder="Décrire cette décision."
|
||||
rows={10}>
|
||||
</textarea>
|
||||
</td>
|
||||
<td>
|
||||
<textarea className="textarea is-success"
|
||||
value={o.pros}
|
||||
readOnly={readOnly}
|
||||
onChange={onOptionChange.bind(null, index, 'pros')}
|
||||
placeholder="Décrire les avantages de cette décision."
|
||||
rows={10}>
|
||||
</textarea>
|
||||
</td>
|
||||
<td>
|
||||
<textarea className="textarea is-danger"
|
||||
value={o.cons}
|
||||
readOnly={readOnly}
|
||||
onChange={onOptionChange.bind(null, index, 'cons')}
|
||||
placeholder="Décrire les désavantages de cette décision."
|
||||
rows={10}>
|
||||
</textarea>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
{
|
||||
state.section.options.length === 0 ?
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={4}>Aucune option pour l'instant.</td>
|
||||
</tr> :
|
||||
null
|
||||
}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={5}>
|
||||
<button
|
||||
disabled={readOnly}
|
||||
className="button is-primary is-pulled-right"
|
||||
onClick={onAddOptionClick}>
|
||||
Ajouter
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import { DecisionSupportFile } from '../../types/decision';
|
||||
import { Timeline } from '../Timeline';
|
||||
import { useEvents } from '../../gql/queries/event';
|
||||
|
||||
export interface TimelinePanelProps {
|
||||
dsf: DecisionSupportFile,
|
||||
};
|
||||
|
||||
export const TimelinePanel: FunctionComponent<TimelinePanelProps> = ({ dsf }) => {
|
||||
const { events } = useEvents({
|
||||
variables: {
|
||||
filter: {
|
||||
objectType: 'dsf',
|
||||
objectId: dsf.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="panel">
|
||||
<div className="level panel-heading mb-0">
|
||||
<div className="level-left">
|
||||
<div className="level-item">
|
||||
Suivi des opérations
|
||||
</div>
|
||||
</div>
|
||||
<div className="level-right">
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-block">
|
||||
<Timeline events={events} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,28 +1,23 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Page } from '../Page';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { RootState } from '../../store/reducers/root';
|
||||
import { WelcomeContent } from './WelcomeContent';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useLoggedIn } from '../../hooks/useLoggedIn';
|
||||
|
||||
export function HomePage() {
|
||||
const currentUser = useSelector((state: RootState) => state.auth.currentUser);
|
||||
const loggedIn = useLoggedIn();
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
if (loggedIn) history.push('/dashboard');
|
||||
}, [loggedIn])
|
||||
|
||||
return (
|
||||
<Page title="Daddy - Accueil">
|
||||
<div className="container is-fluid">
|
||||
<section className="section">
|
||||
<div className="columns">
|
||||
<div className="column is-4 is-offset-4">
|
||||
<div className="box">
|
||||
{
|
||||
currentUser && currentUser.email ?
|
||||
<p>Bonjour <span className="has-text-weight-bold">{currentUser.email}</span> !</p> :
|
||||
<p>Veuillez vous authentifier.</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<Page title="Accueil">
|
||||
<WelcomeContent />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default HomePage;
|
75
client/src/components/HomePage/WelcomeContent.tsx
Normal file
75
client/src/components/HomePage/WelcomeContent.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import React, { FunctionComponent, Fragment } from "react";
|
||||
|
||||
export interface WelcomeContentProps {
|
||||
|
||||
}
|
||||
|
||||
export const WelcomeContent: FunctionComponent<WelcomeContentProps> = () => {
|
||||
return (
|
||||
<Fragment>
|
||||
<section className="hero is-normal is-light is-bold">
|
||||
<div className="hero-body has-text-centered">
|
||||
<div className="container">
|
||||
<h1 className="title">
|
||||
Bienvenue sur Daddy !
|
||||
</h1>
|
||||
<h2 className="subtitle">
|
||||
L'outil de suivi de la vie d'entreprise démocratique.
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div className="box cta">
|
||||
<p className="has-text-centered">
|
||||
<span className="tag is-info">Attention</span> Le service est actuellement en alpha. L'accès est restreint aux adresses autorisées.
|
||||
</p>
|
||||
</div>
|
||||
<section className="container mt-5">
|
||||
<div className="columns">
|
||||
<div className="column is-4">
|
||||
<div className="card is-shady">
|
||||
<div className="card-image has-text-centered">
|
||||
<i className="fa fa-at mt-5" style={{fontSize: '8rem'}}></i>
|
||||
</div>
|
||||
<div className="card-content">
|
||||
<div className="content">
|
||||
<h4>Une adresse courriel et c'est parti !</h4>
|
||||
<p>Pas de création de compte, pas de mot de passe à retenir. Entrez votre adresse courriel et commencez directement à travailler !</p>
|
||||
{/* <p><a href="#">En savoir plus</a></p> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<div className="card is-shady">
|
||||
<div className="card-image has-text-centered">
|
||||
<i className="fa fa-edit mt-5" style={{fontSize: '8rem'}}></i>
|
||||
</div>
|
||||
<div className="card-content">
|
||||
<div className="content">
|
||||
<h4>Préparer vos dossiers d'aide à la décision</h4>
|
||||
<p>Une décision à prendre ? Un nouveau projet à lancer ? Crééz votre groupe de travail et rédigez un dossier pour faciliter la prise de décision collective !</p>
|
||||
{/* <p><a href="#">En savoir plus</a></p> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<div className="card is-shady">
|
||||
<div className="card-image has-text-centered">
|
||||
<i className="fa fa-users mt-5" style={{fontSize: '8rem'}}></i>
|
||||
</div>
|
||||
<div className="card-content">
|
||||
<div className="content">
|
||||
<h4>Travaillez collaborativement</h4>
|
||||
<p>Éditez à plusieurs vos dossiers d'aide à la décision, suivi l'avancée des débats et retrouvez simplement les décisions prises dans l'entreprise.</p>
|
||||
{/* <p><a href="#">En savoir plus</a></p> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
118
client/src/components/ItemPanel.tsx
Normal file
118
client/src/components/ItemPanel.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import React, { FunctionComponent, useState, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export interface Item {
|
||||
id: string
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
export interface TabDefinition {
|
||||
label: string
|
||||
itemFilter?: (item: Item) => boolean
|
||||
}
|
||||
export interface ItemPanelProps {
|
||||
className?: string
|
||||
itemIconClassName?: string
|
||||
title?: string
|
||||
newItemUrl: string
|
||||
isLoading?: boolean
|
||||
items: Item[]
|
||||
tabs?: TabDefinition[],
|
||||
itemKey: (item: Item, index: number) => string
|
||||
itemLabel: (item: Item, index: number) => string
|
||||
itemUrl: (item: Item, index: number) => string
|
||||
}
|
||||
|
||||
export const ItemPanel: FunctionComponent<ItemPanelProps> = (props) => {
|
||||
const {
|
||||
title, className, newItemUrl,
|
||||
itemKey, itemLabel,
|
||||
itemIconClassName, itemUrl,
|
||||
} = props;
|
||||
|
||||
const items = props.items || [];
|
||||
const tabs = props.tabs || [];
|
||||
|
||||
const [ state, setState ] = useState({ selectedTab: 0, filteredItems: [] });
|
||||
|
||||
const filterItemsForTab = (tab: TabDefinition, items: Item[]) => {
|
||||
const itemFilter = tab && typeof tab.itemFilter === 'function' ? tab.itemFilter : () => true;
|
||||
return items.filter(itemFilter);
|
||||
};
|
||||
|
||||
const selectTab = (tabIndex: number) => {
|
||||
setState(state => {
|
||||
const newTab = Array.isArray(tabs) && tabs.length > 0 ? tabs[tabIndex] : null;
|
||||
return {
|
||||
...state,
|
||||
selectedTab: tabIndex,
|
||||
filteredItems: filterItemsForTab(newTab, items)
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setState(state => {
|
||||
const { tabs, items } = props;
|
||||
const newTab = Array.isArray(tabs) && tabs.length > 0 ? tabs[state.selectedTab] : null;
|
||||
return {
|
||||
...state,
|
||||
filteredItems: filterItemsForTab(newTab, items),
|
||||
}
|
||||
});
|
||||
}, [items, tabs]);
|
||||
|
||||
const itemElements = state.filteredItems.map((item: Item, i: number) => {
|
||||
return (
|
||||
<Link to={itemUrl(item, i)} key={`item-${itemKey(item, i)}`} className="panel-block">
|
||||
<span className="panel-icon">
|
||||
<i className={itemIconClassName} aria-hidden="true"></i>
|
||||
</span>
|
||||
{itemLabel(item, i)}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<nav className={`panel ${className}`}>
|
||||
<div className="level is-mobile panel-heading mb-0">
|
||||
<div className="level-left">
|
||||
<p className="level-item">{title}</p>
|
||||
</div>
|
||||
<div className="level-right">
|
||||
<Link to={newItemUrl} className="button level-item is-outlined is-info is-inverted">
|
||||
<i className="icon fa fa-plus"></i>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-block">
|
||||
<p className="control has-icons-left">
|
||||
<input disabled={true} className="input" type="text" placeholder="Filtrer..." />
|
||||
<span className="icon is-left">
|
||||
<i className="fas fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p className="panel-tabs">
|
||||
{
|
||||
tabs.map((tab, i) => {
|
||||
return (
|
||||
<a key={`workgroup-tab-${i}`}
|
||||
onClick={selectTab.bind(null, i)}
|
||||
className={i === state.selectedTab ? 'is-active' : ''}>
|
||||
{tab.label}
|
||||
</a>
|
||||
)
|
||||
})
|
||||
}
|
||||
</p>
|
||||
{
|
||||
itemElements.length > 0 ?
|
||||
itemElements :
|
||||
<a className="panel-block has-text-centered is-block">
|
||||
<em>Aucun élément pour l'instant.</em>
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
)
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export class Loader extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="loader-container">
|
||||
<div className="lds-ripple">
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
17
client/src/components/LogoutPage.tsx
Normal file
17
client/src/components/LogoutPage.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React, { FunctionComponent, useEffect } from "react";
|
||||
import { saveLoggedIn } from "../hooks/useLoggedIn";
|
||||
import { Config } from "../config";
|
||||
|
||||
export interface LogoutPageProps {
|
||||
|
||||
}
|
||||
|
||||
export const LogoutPage: FunctionComponent<LogoutPageProps> = () => {
|
||||
useEffect(() => {
|
||||
saveLoggedIn(false);
|
||||
window.location.replace(Config.logoutURL);
|
||||
}, []);
|
||||
return null;
|
||||
};
|
||||
|
||||
export default LogoutPage;
|
@ -1,48 +1,81 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import logo from '../resources/logo.svg';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '../store/reducers/root';
|
||||
import { Config } from '../config';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useLoggedIn } from '../hooks/useLoggedIn';
|
||||
|
||||
export function Navbar() {
|
||||
const isAuthenticated = useSelector<RootState>(state => state.auth.isAuthenticated);
|
||||
const loggedIn = useLoggedIn();
|
||||
const [ isActive, setActive ] = useState(false);
|
||||
|
||||
const toggleMenu = () => {
|
||||
setActive(active => !active);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="navbar" role="navigation" aria-label="main navigation">
|
||||
<nav className="navbar is-fixed-top" role="navigation" aria-label="main navigation">
|
||||
<div className="container is-fluid">
|
||||
<div className="navbar-brand">
|
||||
<a className="navbar-item" href="#/">
|
||||
<Link className="navbar-item" to="/">
|
||||
<img src={logo} style={{marginRight:'5px',width:'28px',height:'28px'}} />
|
||||
<h1 className="is-size-4">Daddy</h1>
|
||||
</a>
|
||||
<a role="button" className="navbar-burger" aria-label="menu" aria-expanded="false">
|
||||
</Link>
|
||||
<a role="button"
|
||||
className={`navbar-burger ${isActive ? 'is-active' : ''}`}
|
||||
onClick={toggleMenu}
|
||||
aria-label="menu"
|
||||
aria-expanded="false">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="navbar-menu">
|
||||
<div className={`navbar-menu ${isActive ? 'is-active' : ''}`}>
|
||||
<div className="navbar-start">
|
||||
{
|
||||
loggedIn ?
|
||||
<React.Fragment>
|
||||
<Link to="/dashboard" className="navbar-item">
|
||||
<i className="fa fa-columns"></i> Tableau de bord
|
||||
</Link>
|
||||
<Link to="/conference" className="navbar-item">
|
||||
<i className="fa fa-users"></i> Conférence
|
||||
</Link>
|
||||
</React.Fragment> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<div className="navbar-end">
|
||||
<div className="navbar-item">
|
||||
<div className="buttons">
|
||||
{
|
||||
isAuthenticated ?
|
||||
<a className="button is-small" href={Config.logoutURL}>
|
||||
loggedIn ?
|
||||
<Fragment>
|
||||
<Link to="/profile" className="button">
|
||||
<span className="icon">
|
||||
<i className="fas fa-user"></i>
|
||||
</span>
|
||||
<span>Mon profil</span>
|
||||
</Link>
|
||||
<Link className="button is-warning is-small" to="/logout">
|
||||
<span className="icon">
|
||||
<i className="fas fa-sign-out-alt"></i>
|
||||
</span>
|
||||
<span>Se déconnecter</span>
|
||||
</a> :
|
||||
<a className="button is-small" href={Config.loginURL}>
|
||||
<span>Déconnexion</span>
|
||||
</Link>
|
||||
</Fragment> :
|
||||
<a className="button is-primary" href={Config.loginURL}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-sign-in-alt"></i>
|
||||
</span>
|
||||
<span>Se connecter</span>
|
||||
<span>S'identifier</span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
@ -25,6 +25,6 @@ export class Page extends React.PureComponent<PageProps> {
|
||||
|
||||
updateTitle() {
|
||||
const { title } = this.props;
|
||||
if (title !== undefined) window.document.title = title;
|
||||
if (title !== undefined) window.document.title = title + ' - Daddy';
|
||||
}
|
||||
}
|
18
client/src/components/PrivateRoute.tsx
Normal file
18
client/src/components/PrivateRoute.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React, { FunctionComponent, Component, ReactType } from "react"
|
||||
import { Route, Redirect, RouteProps } from "react-router"
|
||||
import { useLoggedIn } from "../hooks/useLoggedIn";
|
||||
|
||||
export interface PrivateRouteProps extends RouteProps {
|
||||
}
|
||||
|
||||
export const PrivateRoute: FunctionComponent<PrivateRouteProps> = ({component: Component, ...rest}) => {
|
||||
const loggedIn = useLoggedIn();
|
||||
return (
|
||||
<Route
|
||||
{...rest}
|
||||
render={(props) => loggedIn === true
|
||||
? <Component {...props} />
|
||||
: <Redirect to={{pathname: '/', state: {from: props.location}}} />}
|
||||
/>
|
||||
)
|
||||
}
|
37
client/src/components/ProfilePage/ProfilePage.tsx
Normal file
37
client/src/components/ProfilePage/ProfilePage.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Page } from '../Page';
|
||||
import { UserForm } from '../UserForm';
|
||||
import { User } from '../../types/user';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { useUpdateUserProfileMutation } from '../../gql/mutations/profile';
|
||||
|
||||
export function ProfilePage() {
|
||||
const { user, loading } = useUserProfile();
|
||||
const [ updateProfile, updateUserProfileMutation ] = useUpdateUserProfileMutation();
|
||||
const isLoading = updateUserProfileMutation.loading || loading;
|
||||
|
||||
const onUserChange = (user: User) => {
|
||||
if (user.name !== user.name) {
|
||||
updateProfile({ variables: {changes: { name: user.name }}});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Page title="Mon profil">
|
||||
<div className="container is-fluid">
|
||||
<section className="section">
|
||||
<div className="columns">
|
||||
<div className="column is-6 is-offset-3">
|
||||
<div className="box">
|
||||
<h2 className="is-size-2 subtitle">Mon profil</h2>
|
||||
{ !isLoading ? <UserForm onChange={onUserChange} user={user} /> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfilePage;
|
79
client/src/components/RoutedTabs.tsx
Normal file
79
client/src/components/RoutedTabs.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { FunctionComponent, ReactNode, useEffect, useState } from 'react';
|
||||
import { useHistory, useLocation, useRouteMatch } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export interface Tab {
|
||||
route: string
|
||||
name: string
|
||||
icon ?: string
|
||||
render: (tab: Tab) => ReactNode
|
||||
}
|
||||
|
||||
export interface RoutedTabsProps {
|
||||
tabs: Tab[]
|
||||
baseRoute?: string
|
||||
defaultTabIndex?: number
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const RoutedTabs: FunctionComponent<RoutedTabsProps> = ({ tabs, baseRoute, defaultTabIndex }) => {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
const tabRoute = (route: string): string => {
|
||||
return `${baseRoute}${route}`;
|
||||
};
|
||||
|
||||
const [ selectedTabIndex, setSelectedTabIndex ] = useState(defaultTabIndex || 0);
|
||||
const expectedTab = tabs[selectedTabIndex];
|
||||
const expectedTabRoute = tabRoute(expectedTab.route);
|
||||
|
||||
let matchExpectedTabRoute = useRouteMatch(expectedTabRoute);
|
||||
|
||||
useEffect(() => {
|
||||
if (matchExpectedTabRoute) return;
|
||||
|
||||
const newTabIndex = tabs.findIndex(t => location.pathname === tabRoute(t.route));
|
||||
|
||||
if (newTabIndex !== -1) {
|
||||
selectTab(newTabIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(expectedTabRoute);
|
||||
}, [matchExpectedTabRoute]);
|
||||
|
||||
const selectTab = (tabIndex: number) => {
|
||||
setSelectedTabIndex(tabIndex);
|
||||
const newTab = tabs[tabIndex];
|
||||
history.push(tabRoute(newTab.route));
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="tabs is-medium is-boxed">
|
||||
<ul>
|
||||
{
|
||||
tabs.map((t: Tab, i: number) => {
|
||||
return (
|
||||
<li key={`tab-${i}`} className={`has-background-white ${selectedTabIndex === i ? 'is-active': ''}`}
|
||||
onClick={selectTab.bind(null, i)}>
|
||||
<a>
|
||||
{
|
||||
t.icon ?
|
||||
<span className="icon is-small"><i className={t.icon} aria-hidden="true"></i></span> :
|
||||
null
|
||||
}
|
||||
<span>{t.name}</span>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
{ expectedTab.render(expectedTab) }
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
232
client/src/components/Timeline.tsx
Normal file
232
client/src/components/Timeline.tsx
Normal file
@ -0,0 +1,232 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { formatDate } from "../util/date";
|
||||
import { Event } from "../types/event";
|
||||
import { Link } from "react-router-dom";
|
||||
import { WorkgroupLink } from "./WorkgroupLink";
|
||||
import { DecisionSupportFileLink } from "./DecisionSupportFileLink";
|
||||
|
||||
export interface TimelineProps {
|
||||
events?: Event[]
|
||||
}
|
||||
|
||||
export const Timeline: FunctionComponent<TimelineProps> = ({ events }) => {
|
||||
events = debounceEvents(events) || [];
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="timeline" style={{width: '100%'}}>
|
||||
{
|
||||
events.map(evt => {
|
||||
return (
|
||||
<div key={evt.id} className="timeline-item">
|
||||
{renderEventMarker(evt)}
|
||||
<div className="timeline-content">
|
||||
<p className="heading">{formatDate(evt.createdAt)}</p>
|
||||
{renderEventContent(evt)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
{
|
||||
events.length === 0 ?
|
||||
<p className="has-text-centered is-italic mb-1 mt-1">Aucun évènement.</p> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function debounceEvents(events: Event[]): Event[] {
|
||||
const debounced = [];
|
||||
for(let evt: Event, i = 0; (evt = events[i]); ++i) {
|
||||
const prev = i > 0 ? events[i-1] : null;
|
||||
|
||||
if (!prev) {
|
||||
debounced.push(evt);
|
||||
continue;
|
||||
}
|
||||
|
||||
const isSame = evt.objectId === prev.objectId &&
|
||||
evt.objectType === prev.objectType &&
|
||||
evt.type === prev.type &&
|
||||
evt.user.id === prev.user.id
|
||||
;
|
||||
|
||||
if (isSame) continue;
|
||||
|
||||
debounced.push(evt);
|
||||
}
|
||||
|
||||
return debounced;
|
||||
}
|
||||
|
||||
const eventMarkerMap = {
|
||||
"closed": (evt:Event) => (
|
||||
<div className="timeline-marker is-icon is-danger">
|
||||
<i className="fa fa-times"></i>
|
||||
</div>
|
||||
),
|
||||
"created": (evt:Event) => (
|
||||
<div className="timeline-marker is-icon is-success">
|
||||
<i className="fa fa-plus"></i>
|
||||
</div>
|
||||
),
|
||||
"updated": (evt:Event) => (
|
||||
<div className="timeline-marker is-icon is-info">
|
||||
<i className="fa fa-pen"></i>
|
||||
</div>
|
||||
),
|
||||
"title-changed": (evt:Event) => (
|
||||
<div className="timeline-marker is-icon is-info">
|
||||
<i className="fa fa-pen"></i>
|
||||
</div>
|
||||
),
|
||||
"status-changed": (evt:Event) => (
|
||||
<div className="timeline-marker is-icon is-primary">
|
||||
<i className="fa fa-star"></i>
|
||||
</div>
|
||||
),
|
||||
"joined": (evt:Event) => (
|
||||
<div className="timeline-marker is-icon is-info">
|
||||
<i className="fa fa-users"></i>
|
||||
</div>
|
||||
),
|
||||
"leaved": (evt:Event) => (
|
||||
<div className="timeline-marker is-icon is-warning">
|
||||
<i className="fas fa-users-slash"></i>
|
||||
</div>
|
||||
),
|
||||
"voted": (evt:Event) => (
|
||||
<div className="timeline-marker is-icon is-success">
|
||||
<i className="fas fa-thumbs-up"></i>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
function renderEventMarker(evt: Event) {
|
||||
const render = eventMarkerMap[evt.type];
|
||||
if (!render) return ( <div className="timeline-marker"></div> );
|
||||
return render(evt);
|
||||
}
|
||||
|
||||
const eventContentMap = {
|
||||
"created": {
|
||||
"workgroup": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a créé le groupe de travail `}</span>
|
||||
"<WorkgroupLink workgroupId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
"dsf": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a créé le dossier d'aide à la décision `}</span>
|
||||
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
"title-changed": {
|
||||
"dsf": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le titre du dossier d'aide à la décision `}</span>
|
||||
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
},
|
||||
"status-changed": {
|
||||
"dsf": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le statut du dossier d'aide à la décision `}</span>
|
||||
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
},
|
||||
"joined": {
|
||||
"workgroup": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a rejoint le groupe de travail `}</span>
|
||||
"<WorkgroupLink workgroupId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
"updated": {
|
||||
"workgroup": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a mis à jour le groupe de travail `}</span>
|
||||
"<WorkgroupLink workgroupId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
"dsf": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le dossier d'aide à la décision `}</span>
|
||||
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
"leaved": {
|
||||
"workgroup": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a quitté le groupe de travail `}</span>
|
||||
"<WorkgroupLink workgroupId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
"closed": {
|
||||
"dsf": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a clos le dossier d'aide à la décision `}</span>
|
||||
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
"workgroup": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a clos le groupe de travail `}</span>
|
||||
"<WorkgroupLink workgroupId={evt.objectId} />".
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
"voted": {
|
||||
"dsf": (evt:Event) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<span>{`Le dossier d'aide à la décision `}</span>
|
||||
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />"
|
||||
<span> a été voté.</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function renderEventContent(evt: Event) {
|
||||
const eventTypeMap = eventContentMap[evt.type];
|
||||
const render = eventTypeMap && eventTypeMap[evt.objectType];
|
||||
|
||||
if (!eventTypeMap || !render) {
|
||||
return (
|
||||
<span className="is-italic">{`Type d'évènement "${evt.type}/${evt.objectType}" inconnu.`}</span>
|
||||
);
|
||||
}
|
||||
|
||||
return render(evt);
|
||||
}
|
39
client/src/components/UnauthorizedPage/UnauthorizedPage.tsx
Normal file
39
client/src/components/UnauthorizedPage/UnauthorizedPage.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Config } from '../../config';
|
||||
import { Page } from '../Page';
|
||||
|
||||
export interface UnauthorizedPageProps {
|
||||
|
||||
}
|
||||
|
||||
export const UnauthorizedPage:FunctionComponent<UnauthorizedPageProps> = () => {
|
||||
return (
|
||||
<Page title="Non autorisé">
|
||||
<div className="container is-fluid">
|
||||
<section className="section">
|
||||
<div className="columns">
|
||||
<div className="column is-6 is-offset-3">
|
||||
<div className="message is-danger">
|
||||
<div className="message-header">
|
||||
<p><i className="fa fa-ban"></i> Non autorisé</p>
|
||||
</div>
|
||||
<div className="message-body">
|
||||
<p>Vous n'êtes pas autorisé à accéder à cette page.</p>
|
||||
<br />
|
||||
<p>Votre compte est peut être désactivé, votre adresse courriel ne fait peut être
|
||||
pas partie des domaines autorisés ou vous n'avez peut être pas les droits nécessaires pour effectuer cette opération.</p>
|
||||
<div className="has-text-centered mt-5">
|
||||
<a href={Config.logoutURL} className="is-warning button"><i className="fa fa-sign-out-alt"></i> Forcer la déconnexion</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default UnauthorizedPage;
|
81
client/src/components/UserForm.tsx
Normal file
81
client/src/components/UserForm.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import React, { useState, ChangeEvent, useEffect } from 'react';
|
||||
import { User } from '../types/user';
|
||||
import { formatDate } from '../util/date';
|
||||
|
||||
export interface UserFormProps {
|
||||
user: User
|
||||
onChange?: (user: User) => void
|
||||
}
|
||||
|
||||
export function UserForm({ user, onChange }: UserFormProps) {
|
||||
const [ state, setState ] = useState({
|
||||
changed: false,
|
||||
user: {
|
||||
id: user && user.id ? user.id : '',
|
||||
name: user && user.name ? user.name : '',
|
||||
email: user && user.email ? user.email : '',
|
||||
createdAt: user && user.createdAt ? user.createdAt : null,
|
||||
connectedAt: user && user.connectedAt ? user.connectedAt : null,
|
||||
}
|
||||
});
|
||||
|
||||
const onSaveClick = () => {
|
||||
if (!state.changed) return;
|
||||
if (typeof onChange !== 'function') return;
|
||||
onChange(state.user);
|
||||
setState(state => {
|
||||
return {
|
||||
...state,
|
||||
changed: false,
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
const onUserAttrChange = function(attrName: string, evt: ChangeEvent<HTMLInputElement>) {
|
||||
const value = evt.currentTarget.value;
|
||||
setState(state => {
|
||||
return {
|
||||
...state,
|
||||
changed: true,
|
||||
user: {
|
||||
...state.user,
|
||||
[attrName]: value,
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form">
|
||||
<div className="field">
|
||||
<label className="label">Nom d'utilisateur</label>
|
||||
<div className="control">
|
||||
<input type="text" className="input" value={state.user.name}
|
||||
onChange={onUserAttrChange.bind(null, "name")} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label">Adresse courriel</label>
|
||||
<div className="control">
|
||||
<p className="input is-static">{state.user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label">Date de dernière connexion</label>
|
||||
<div className="control">
|
||||
<p className="input is-static">{formatDate(state.user.connectedAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label">Date de création</label>
|
||||
<div className="control">
|
||||
<p className="input is-static">{formatDate(state.user.createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="buttons is-right">
|
||||
<button disabled={!state.changed}
|
||||
className="button is-primary" onClick={onSaveClick}>Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
24
client/src/components/WorkgroupLink.tsx
Normal file
24
client/src/components/WorkgroupLink.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useWorkgroups } from "../gql/queries/workgroups";
|
||||
|
||||
export interface WorkgroupLinkProps {
|
||||
workgroupId: number
|
||||
}
|
||||
|
||||
export const WorkgroupLink: FunctionComponent<WorkgroupLinkProps> = ({ workgroupId }) => {
|
||||
const { workgroups } = useWorkgroups({
|
||||
fetchPolicy: "cache-first",
|
||||
variables: {
|
||||
filter: {
|
||||
ids: [workgroupId]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const workgroupName = workgroups.length > 0 ? workgroups[0].name : `#${workgroupId}`;
|
||||
|
||||
return (
|
||||
<Link to={`/workgroups/${workgroupId}`}>{workgroupName}</Link>
|
||||
);
|
||||
};
|
@ -0,0 +1,48 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useDecisionSupportFiles } from '../../gql/queries/dsf';
|
||||
import { DecisionSupportFile } from '../../types/decision';
|
||||
import { User } from '../../types/user';
|
||||
import { DecisionSupportFileLink } from '../DecisionSupportFileLink';
|
||||
import { WorkgroupLink } from '../WorkgroupLink';
|
||||
|
||||
export interface DecisionSupportFilePanelProps {
|
||||
workgroupId: string
|
||||
}
|
||||
|
||||
export const DecisionSupportFilePanel: FunctionComponent<DecisionSupportFilePanelProps> = ({ workgroupId }) => {
|
||||
const { decisionSupportFiles } = useDecisionSupportFiles({
|
||||
variables: {
|
||||
filter: {
|
||||
workgroups: [workgroupId],
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<nav className="panel">
|
||||
<p className="panel-heading">
|
||||
Dossiers d'aide à la décision
|
||||
</p>
|
||||
{
|
||||
decisionSupportFiles.map((dsf: DecisionSupportFile) => {
|
||||
return (
|
||||
<Link to={`/decisions/${dsf.id}`} key={`dsf-${dsf.id}`} className="panel-block">
|
||||
<span className="panel-icon">
|
||||
<i className="fas fa-file" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span>{dsf.title}</span>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
}
|
||||
{
|
||||
decisionSupportFiles.length === 0 ?
|
||||
<a className="panel-block has-text-centered is-block">
|
||||
<p className="is-italic">Aucun dossier pour l'instant.</p>
|
||||
</a> :
|
||||
null
|
||||
}
|
||||
</nav>
|
||||
);
|
||||
}
|
108
client/src/components/WorkgroupPage/InfoForm.tsx
Normal file
108
client/src/components/WorkgroupPage/InfoForm.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import React, { useState, ChangeEvent, useEffect } from 'react';
|
||||
import { Workgroup } from '../../types/workgroup';
|
||||
import { useIsAuthorized } from '../../gql/queries/authorization';
|
||||
import { formatDate } from '../../util/date';
|
||||
|
||||
export interface InfoFormProps {
|
||||
workgroup: Workgroup
|
||||
onChange?: (workgroup: Workgroup) => void
|
||||
}
|
||||
|
||||
export function InfoForm({ workgroup, onChange }: InfoFormProps) {
|
||||
const [ state, setState ] = useState({
|
||||
changed: false,
|
||||
workgroup: {
|
||||
id: workgroup && workgroup.id ? workgroup.id : '',
|
||||
name: workgroup && workgroup.name ? workgroup.name : '',
|
||||
createdAt: workgroup && workgroup.createdAt ? workgroup.createdAt : null,
|
||||
closedAt: workgroup && workgroup.closedAt ? workgroup.closedAt : null,
|
||||
}
|
||||
});
|
||||
|
||||
const { isAuthorized } = useIsAuthorized({
|
||||
variables: {
|
||||
action: 'update',
|
||||
object: {
|
||||
workgroupId: state.workgroup.id,
|
||||
}
|
||||
}
|
||||
}, state.workgroup.id === '' ? true : false);
|
||||
|
||||
useEffect(() => {
|
||||
setState({
|
||||
changed: false,
|
||||
workgroup: {
|
||||
id: workgroup && workgroup.id ? workgroup.id : '',
|
||||
name: workgroup && workgroup.name ? workgroup.name : '',
|
||||
createdAt: workgroup && workgroup.createdAt ? workgroup.createdAt : null,
|
||||
closedAt: workgroup && workgroup.closedAt ? workgroup.closedAt : null,
|
||||
}
|
||||
});
|
||||
}, [workgroup]);
|
||||
|
||||
const onSaveClick = () => {
|
||||
if (!state.changed) return;
|
||||
if (typeof onChange !== 'function') return;
|
||||
onChange(state.workgroup as Workgroup);
|
||||
setState(state => {
|
||||
return {
|
||||
...state,
|
||||
changed: false,
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
const onWorkgroupAttrChange = function(attrName: string, evt: ChangeEvent<HTMLInputElement>) {
|
||||
const value = evt.currentTarget.value;
|
||||
setState(state => {
|
||||
return {
|
||||
...state,
|
||||
changed: true,
|
||||
workgroup: {
|
||||
...state.workgroup,
|
||||
[attrName]: value,
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="form" style={{width: '100%'}}>
|
||||
<div className="field">
|
||||
<label className="label">Nom du groupe</label>
|
||||
<div className="control">
|
||||
<input type="text" className="input" value={state.workgroup.name}
|
||||
disabled={!isAuthorized}
|
||||
onChange={onWorkgroupAttrChange.bind(null, "name")} />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
state.workgroup.createdAt ?
|
||||
<div className="field">
|
||||
<label className="label">Date de création</label>
|
||||
<div className="control">
|
||||
<p className="input is-static">{formatDate(state.workgroup.createdAt)}</p>
|
||||
</div>
|
||||
</div>:
|
||||
null
|
||||
}
|
||||
{
|
||||
state.workgroup.closedAt ?
|
||||
<div className="field">
|
||||
<label className="label">Date de clôture</label>
|
||||
<div className="control">
|
||||
<p className="input is-static">{formatDate(state.workgroup.closedAt)}</p>
|
||||
</div>
|
||||
</div>:
|
||||
null
|
||||
}
|
||||
<div className="buttons is-right">
|
||||
<button disabled={!state.changed || !isAuthorized}
|
||||
className="button is-success" onClick={onSaveClick}>
|
||||
<span>Enregistrer</span>
|
||||
<span className="icon"><i className="fa fa-save"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
48
client/src/components/WorkgroupPage/InfoPanel.tsx
Normal file
48
client/src/components/WorkgroupPage/InfoPanel.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Workgroup } from '../../types/workgroup';
|
||||
import { InfoForm } from './InfoForm';
|
||||
import { useUpdateWorkgroupMutation, useCreateWorkgroupMutation } from '../../gql/mutations/workgroups';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
export interface InfoPanelProps {
|
||||
workgroup: Workgroup
|
||||
}
|
||||
|
||||
export const InfoPanel: FunctionComponent<InfoPanelProps> = ({ workgroup }) => {
|
||||
const [ updateWorkgroup, updateWorkgroupMutation ] = useUpdateWorkgroupMutation();
|
||||
const [ createWorkgroup, createWorkgroupMutation ] = useCreateWorkgroupMutation();
|
||||
const history = useHistory();
|
||||
const isLoading = updateWorkgroupMutation.loading || createWorkgroupMutation.loading;
|
||||
|
||||
const onWorkgroupChange = (formWorkgroup: Workgroup) => {
|
||||
const variables: any = { changes: {} };
|
||||
|
||||
if (workgroup.name !== formWorkgroup.name) {
|
||||
variables.changes.name = formWorkgroup.name;
|
||||
}
|
||||
|
||||
if (Object.keys(variables.changes).length === 0) return;
|
||||
|
||||
const isCreation = workgroup.id === '';
|
||||
if (isCreation) {
|
||||
createWorkgroup({variables})
|
||||
.then(({ data: { createWorkgroup } }) => {
|
||||
history.push(`/workgroups/${createWorkgroup.id}`);
|
||||
});
|
||||
} else {
|
||||
variables.workgroupId = workgroup.id;
|
||||
updateWorkgroup({variables});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="panel">
|
||||
<p className="panel-heading">
|
||||
Informations
|
||||
</p>
|
||||
<div className="panel-block">
|
||||
{ !isLoading ? <InfoForm workgroup={workgroup} onChange={onWorkgroupChange} /> : null }
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
35
client/src/components/WorkgroupPage/MembersPanel.tsx
Normal file
35
client/src/components/WorkgroupPage/MembersPanel.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { User } from '../../types/user';
|
||||
|
||||
export interface MembersPanelProps {
|
||||
users: User[]
|
||||
}
|
||||
|
||||
export const MembersPanel: FunctionComponent<MembersPanelProps> = ({ users }) => {
|
||||
return (
|
||||
<nav className="panel">
|
||||
<p className="panel-heading">
|
||||
Membres
|
||||
</p>
|
||||
{
|
||||
users.map(u => {
|
||||
return (
|
||||
<div key={`user-${u.id}`} className="panel-block">
|
||||
<span className="panel-icon">
|
||||
<i className="fas fa-user" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span>{`${ u.name ? (u.name + ' - ') : '' }`}</span><span className="is-italic">{`${u.email}`}</span>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
{
|
||||
users.length === 0 ?
|
||||
<a className="panel-block has-text-centered is-block">
|
||||
<p className="is-italic">Aucun membre pour l'instant.</p>
|
||||
</a> :
|
||||
null
|
||||
}
|
||||
</nav>
|
||||
);
|
||||
}
|
37
client/src/components/WorkgroupPage/TimelinePanel.tsx
Normal file
37
client/src/components/WorkgroupPage/TimelinePanel.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import { DecisionSupportFile } from '../../types/decision';
|
||||
import { Timeline } from '../Timeline';
|
||||
import { useEvents } from '../../gql/queries/event';
|
||||
import { Workgroup } from '../../types/workgroup';
|
||||
|
||||
export interface TimelinePanelProps {
|
||||
workgroup: Workgroup,
|
||||
};
|
||||
|
||||
export const TimelinePanel: FunctionComponent<TimelinePanelProps> = ({ workgroup }) => {
|
||||
const { events } = useEvents({
|
||||
variables: {
|
||||
filter: {
|
||||
objectType: 'workgroup',
|
||||
objectId: workgroup.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="panel">
|
||||
<div className="level panel-heading mb-0">
|
||||
<div className="level-left">
|
||||
<div className="level-item">
|
||||
Suivi des opérations
|
||||
</div>
|
||||
</div>
|
||||
<div className="level-right">
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-block">
|
||||
<Timeline events={events} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
155
client/src/components/WorkgroupPage/WorkgroupPage.tsx
Normal file
155
client/src/components/WorkgroupPage/WorkgroupPage.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
import React, { useEffect, useState, Fragment } from 'react';
|
||||
import { Page } from '../Page';
|
||||
import { useParams } from 'react-router';
|
||||
import { useWorkgroups } from '../../gql/queries/workgroups';
|
||||
import { useUserProfile } from '../../gql/queries/profile';
|
||||
import { MembersPanel } from './MembersPanel';
|
||||
import { User } from '../../types/user';
|
||||
import { InfoPanel } from './InfoPanel';
|
||||
import { Workgroup } from '../../types/workgroup';
|
||||
import { useJoinWorkgroupMutation, useLeaveWorkgroupMutation, useCloseWorkgroupMutation } from '../../gql/mutations/workgroups';
|
||||
import { TimelinePanel } from './TimelinePanel';
|
||||
import { DecisionSupportFilePanel } from './DecisionSupportFilePanel';
|
||||
|
||||
export function WorkgroupPage() {
|
||||
const { id } = useParams<any>();
|
||||
const { workgroups } = useWorkgroups({
|
||||
variables:{
|
||||
filter: {
|
||||
ids: [id],
|
||||
}
|
||||
}
|
||||
});
|
||||
const { user } = useUserProfile();
|
||||
|
||||
const [ joinWorkgroup ] = useJoinWorkgroupMutation();
|
||||
const [ leaveWorkgroup ] = useLeaveWorkgroupMutation();
|
||||
const [ closeWorkgroup ] = useCloseWorkgroupMutation();
|
||||
|
||||
const [ state, setState ] = useState({
|
||||
userProfileId: '',
|
||||
workgroup: {
|
||||
id: '',
|
||||
name: '',
|
||||
closedAt: null,
|
||||
createdAt: null,
|
||||
members: [],
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setState(state => ({...state, workgroup:{ ...state.workgroup, ...workgroups[0]}}));
|
||||
}, [workgroups]);
|
||||
|
||||
useEffect(() => {
|
||||
setState(state => ({...state, userProfileId: user.id }));
|
||||
}, [user]);
|
||||
|
||||
const onJoinWorkgroupClick = () => {
|
||||
joinWorkgroup({
|
||||
variables: {
|
||||
workgroupId: state.workgroup.id,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const onLeaveWorkgroupClick = () => {
|
||||
leaveWorkgroup({
|
||||
update: (cache, result) => {
|
||||
cache.modify({
|
||||
id: cache.identify(result.data.leaveWorkgroup),
|
||||
fields: {
|
||||
members(existingMembers, { readField }) {
|
||||
return existingMembers.filter(
|
||||
user => state.userProfileId !== readField('id', user)
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
variables: {
|
||||
workgroupId: state.workgroup.id,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const onCloseWorkgroupClick = () => {
|
||||
closeWorkgroup({
|
||||
variables: {
|
||||
workgroupId: state.workgroup.id,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const isNew = state.workgroup.id === '';
|
||||
const isWorkgroupMember = state.workgroup.members.some(u => u.id === state.userProfileId);
|
||||
const isClosed = state.workgroup.closedAt !== null;
|
||||
|
||||
return (
|
||||
<Page title="Groupe de travail">
|
||||
<div className="container is-fluid">
|
||||
<section className="mt-5">
|
||||
<div className="level">
|
||||
<div className="level-left">
|
||||
{
|
||||
isNew ?
|
||||
<div className="level-item">
|
||||
<div>
|
||||
<h2 className="is-size-3 title is-spaced">Nouveau</h2>
|
||||
<h3 className="is-size-5 subtitle">Groupe de travail</h3>
|
||||
</div>
|
||||
</div> :
|
||||
<div className="level-item">
|
||||
<div>
|
||||
<h2 className="is-size-3 title is-spaced">{state.workgroup.name}</h2>
|
||||
<h3 className="is-size-5 subtitle">Groupe de travail <span className="is-italic">{ isClosed ? '(clos)' : null }</span></h3>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="level-right">
|
||||
<div className="buttons is-right level-item">
|
||||
{
|
||||
isNew || isClosed ? null :
|
||||
<Fragment>
|
||||
{
|
||||
isWorkgroupMember ?
|
||||
<Fragment>
|
||||
<button onClick={onLeaveWorkgroupClick} className="button is-info is-warning is-medium">
|
||||
<span>Quitter</span>
|
||||
<span className="icon"><i className="fas fa-sign-out-alt"></i></span>
|
||||
</button>
|
||||
<button onClick={onCloseWorkgroupClick} className="button is-danger is-medium">
|
||||
<span>Clore</span>
|
||||
<span className="icon"><i className="far fa-times-circle"></i></span>
|
||||
</button>
|
||||
</Fragment> :
|
||||
<button onClick={onJoinWorkgroupClick} className="button is-info is-medium">
|
||||
<span>Rejoindre</span>
|
||||
<span className="icon"><i className="fas fa-user-plus"></i></span>
|
||||
</button>
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column is-4">
|
||||
<InfoPanel workgroup={state.workgroup as Workgroup} />
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<MembersPanel users={state.workgroup.members as User[]} />
|
||||
<DecisionSupportFilePanel workgroupId={state.workgroup.id} />
|
||||
</div>
|
||||
<div className="column is-4">
|
||||
<TimelinePanel workgroup={state.workgroup} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default WorkgroupPage;
|
@ -3,6 +3,8 @@ export const Config = {
|
||||
logoutURL: get<string>("logoutURL", "http://localhost:8081/logout"),
|
||||
graphQLEndpoint: get<string>("graphQLEndpoint", "http://localhost:8081/api/v1/graphql"),
|
||||
subscriptionEndpoint: get<string>("subscriptionEndpoint", "ws://localhost:8081/api/v1/graphql"),
|
||||
conferenceHeartbeatInterval: get<number>("conferenceHeartbeatInterval", 10000),
|
||||
frontendBaseURL: get<string>("frontendBaseURL", window.location.protocol + '//' + window.location.host + '/'),
|
||||
};
|
||||
|
||||
function get<T>(key: string, defaultValue: T):T {
|
||||
|
58
client/src/gql/mutations/dsf.tsx
Normal file
58
client/src/gql/mutations/dsf.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { gql, useQuery, useMutation } from '@apollo/client';
|
||||
import { QUERY_DECISION_SUPPORT_FILES } from '../queries/dsf';
|
||||
|
||||
export const MUTATION_CREATE_DECISION_SUPPORT_FILE = gql`
|
||||
mutation createDecisionSupportFile($changes: DecisionSupportFileChanges!) {
|
||||
createDecisionSupportFile(changes: $changes) {
|
||||
id,
|
||||
title,
|
||||
status,
|
||||
sections,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
workgroup {
|
||||
id,
|
||||
name,
|
||||
members {
|
||||
id,
|
||||
email,
|
||||
name
|
||||
}
|
||||
},
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useCreateDecisionSupportFileMutation() {
|
||||
return useMutation(MUTATION_CREATE_DECISION_SUPPORT_FILE, {
|
||||
refetchQueries: [{query: QUERY_DECISION_SUPPORT_FILES}],
|
||||
});
|
||||
}
|
||||
|
||||
export const MUTATION_UPDATE_DECISION_SUPPORT_FILE = gql`
|
||||
mutation updateDecisionSupportFile($id: ID!, $changes: DecisionSupportFileChanges!) {
|
||||
updateDecisionSupportFile(id: $id, changes: $changes) {
|
||||
id,
|
||||
title,
|
||||
status,
|
||||
sections,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
workgroup {
|
||||
id,
|
||||
name,
|
||||
members {
|
||||
id,
|
||||
email,
|
||||
name
|
||||
}
|
||||
},
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useUpdateDecisionSupportFileMutation() {
|
||||
return useMutation(MUTATION_UPDATE_DECISION_SUPPORT_FILE, {
|
||||
refetchQueries: [{
|
||||
query: QUERY_DECISION_SUPPORT_FILES,
|
||||
}],
|
||||
});
|
||||
}
|
15
client/src/gql/mutations/profile.tsx
Normal file
15
client/src/gql/mutations/profile.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { gql, useQuery, useMutation } from '@apollo/client';
|
||||
|
||||
export const MUTATION_UPDATE_USER_PROFILE = gql`
|
||||
mutation updateUserProfile($changes: ProfileChanges!) {
|
||||
updateProfile(changes: $changes) {
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
connectedAt,
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useUpdateUserProfileMutation() {
|
||||
return useMutation(MUTATION_UPDATE_USER_PROFILE);
|
||||
}
|
132
client/src/gql/mutations/workgroups.tsx
Normal file
132
client/src/gql/mutations/workgroups.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import { gql, useQuery, useMutation, FetchResult } from '@apollo/client';
|
||||
import { QUERY_WORKGROUP } from '../queries/workgroups';
|
||||
import { QUERY_IS_AUTHORIZED } from '../queries/authorization';
|
||||
|
||||
export const MUTATION_UPDATE_WORKGROUP = gql`
|
||||
mutation updateWorkgroup($workgroupId: ID!, $changes: WorkgroupChanges!) {
|
||||
updateWorkgroup(workgroupId: $workgroupId, changes: $changes) {
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
closedAt,
|
||||
members {
|
||||
id,
|
||||
name,
|
||||
email
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useUpdateWorkgroupMutation() {
|
||||
return useMutation(MUTATION_UPDATE_WORKGROUP);
|
||||
}
|
||||
|
||||
export const MUTATION_CREATE_WORKGROUP = gql`
|
||||
mutation createWorkgroup($changes: WorkgroupChanges!) {
|
||||
createWorkgroup(changes: $changes) {
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
closedAt,
|
||||
members {
|
||||
id,
|
||||
name,
|
||||
email
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useCreateWorkgroupMutation() {
|
||||
return useMutation(MUTATION_CREATE_WORKGROUP, {
|
||||
refetchQueries: [{query: QUERY_WORKGROUP}],
|
||||
});
|
||||
}
|
||||
|
||||
export const MUTATION_JOIN_WORKGROUP = gql`
|
||||
mutation joinWorkgroup($workgroupId: ID!) {
|
||||
joinWorkgroup(workgroupId: $workgroupId) {
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
closedAt,
|
||||
members {
|
||||
id,
|
||||
name,
|
||||
email
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useJoinWorkgroupMutation() {
|
||||
return useMutation(MUTATION_JOIN_WORKGROUP, {
|
||||
refetchQueries: ({ data }: FetchResult) => {
|
||||
return [{
|
||||
query: QUERY_IS_AUTHORIZED,
|
||||
variables: {
|
||||
action: 'update',
|
||||
object: {
|
||||
workgroupId: data.joinWorkgroup.id,
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const MUTATION_LEAVE_WORKGROUP = gql`
|
||||
mutation leaveWorkgroup($workgroupId: ID!) {
|
||||
leaveWorkgroup(workgroupId: $workgroupId) {
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
closedAt,
|
||||
members {
|
||||
id,
|
||||
name,
|
||||
email
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useLeaveWorkgroupMutation() {
|
||||
return useMutation(MUTATION_LEAVE_WORKGROUP, {
|
||||
refetchQueries: ({ data }: FetchResult) => {
|
||||
return [{
|
||||
query: QUERY_WORKGROUP,
|
||||
variables: {
|
||||
filter: {
|
||||
ids: [data.leaveWorkgroup.id],
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
query: QUERY_IS_AUTHORIZED,
|
||||
variables: {
|
||||
action: 'update',
|
||||
object: {
|
||||
workgroupId: data.leaveWorkgroup.id,
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const MUTATION_CLOSE_WORKGROUP = gql`
|
||||
mutation closeWorkgroup($workgroupId: ID!) {
|
||||
closeWorkgroup(workgroupId: $workgroupId) {
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
closedAt,
|
||||
members {
|
||||
id,
|
||||
name,
|
||||
email
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useCloseWorkgroupMutation() {
|
||||
return useMutation(MUTATION_CLOSE_WORKGROUP);
|
||||
}
|
25
client/src/gql/queries/authorization.tsx
Normal file
25
client/src/gql/queries/authorization.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { gql, useQuery, QueryHookOptions } from '@apollo/client';
|
||||
import { useGraphQLData } from './helper';
|
||||
|
||||
export const QUERY_IS_AUTHORIZED = gql`
|
||||
query isAuthorized($action: String!, $object: AuthorizationObject!) {
|
||||
isAuthorized(action: $action, object: $object)
|
||||
}
|
||||
`;
|
||||
|
||||
export function useIsAuthorizedQuery<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
options = Object.assign({
|
||||
fetchPolicy: 'cache-and-network'
|
||||
}, options);
|
||||
return useQuery(QUERY_IS_AUTHORIZED, options);
|
||||
}
|
||||
|
||||
export function useIsAuthorized<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}, defaultValue = false) {
|
||||
options = Object.assign({
|
||||
fetchPolicy: 'cache-and-network'
|
||||
}, options);
|
||||
const { data, loading, error } = useGraphQLData<boolean>(
|
||||
QUERY_IS_AUTHORIZED, 'isAuthorized', defaultValue, options
|
||||
);
|
||||
return { isAuthorized: data, loading, error };
|
||||
}
|
37
client/src/gql/queries/dsf.tsx
Normal file
37
client/src/gql/queries/dsf.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { gql, useQuery, QueryHookOptions } from '@apollo/client';
|
||||
import { DecisionSupportFile } from '../../types/decision';
|
||||
import { useGraphQLData } from './helper';
|
||||
|
||||
export const QUERY_DECISION_SUPPORT_FILES = gql`
|
||||
query decisionSupportFiles($filter: DecisionSupportFileFilter) {
|
||||
decisionSupportFiles(filter: $filter) {
|
||||
id,
|
||||
title,
|
||||
sections,
|
||||
createdAt,
|
||||
closedAt,
|
||||
votedAt,
|
||||
status,
|
||||
workgroup {
|
||||
id,
|
||||
name,
|
||||
members {
|
||||
id,
|
||||
email,
|
||||
name
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useDecisionSupportFilesQuery<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
return useQuery(QUERY_DECISION_SUPPORT_FILES, options);
|
||||
}
|
||||
|
||||
export function useDecisionSupportFiles<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
const { data, loading, error } = useGraphQLData<DecisionSupportFile[]>(
|
||||
QUERY_DECISION_SUPPORT_FILES, 'decisionSupportFiles', [], options
|
||||
);
|
||||
return { decisionSupportFiles: data, loading, error };
|
||||
}
|
31
client/src/gql/queries/event.ts
Normal file
31
client/src/gql/queries/event.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { gql, useQuery, QueryHookOptions } from '@apollo/client';
|
||||
import { useGraphQLData } from './helper';
|
||||
import { Event } from '../../types/event';
|
||||
|
||||
export const QUERY_EVENTS = gql`
|
||||
query events($filter: EventFilter) {
|
||||
events(filter: $filter) {
|
||||
id
|
||||
user {
|
||||
id
|
||||
name
|
||||
email
|
||||
}
|
||||
type
|
||||
objectType
|
||||
objectId
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useEventsQuery<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
return useQuery(QUERY_EVENTS, options);
|
||||
}
|
||||
|
||||
export function useEvents<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
const { data, loading, error } = useGraphQLData<Event[]>(
|
||||
QUERY_EVENTS, 'events', [], options
|
||||
);
|
||||
return { events: data, loading, error };
|
||||
}
|
11
client/src/gql/queries/helper.ts
Normal file
11
client/src/gql/queries/helper.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { useQuery, DocumentNode, QueryHookOptions } from "@apollo/client";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export function useGraphQLData<T, A = any, R = Record<string, any>>(q: DocumentNode, key: string, defaultValue: T, options: QueryHookOptions<A, R> = {}) {
|
||||
const query = useQuery(q, options);
|
||||
const [ data, setData ] = useState<T>(defaultValue);
|
||||
useEffect(() => {
|
||||
setData(query.data ? query.data[key] as T : defaultValue);
|
||||
}, [query.loading, query.data]);
|
||||
return { data, loading: query.loading, error: query.error };
|
||||
}
|
26
client/src/gql/queries/profile.tsx
Normal file
26
client/src/gql/queries/profile.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { gql, useQuery, QueryHookOptions } from '@apollo/client';
|
||||
import { User } from '../../types/user';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useGraphQLData } from './helper';
|
||||
|
||||
export const QUERY_USER_PROFILE = gql`
|
||||
query userProfile {
|
||||
userProfile {
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
createdAt,
|
||||
connectedAt
|
||||
}
|
||||
}`;
|
||||
|
||||
export function useUserProfileQuery<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
return useQuery(QUERY_USER_PROFILE, options);
|
||||
}
|
||||
|
||||
export function useUserProfile<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
const { data, loading, error } = useGraphQLData<User>(
|
||||
QUERY_USER_PROFILE, 'userProfile', {id: '', email: ''}, options
|
||||
);
|
||||
return { user: data, loading, error };
|
||||
}
|
31
client/src/gql/queries/workgroups.tsx
Normal file
31
client/src/gql/queries/workgroups.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { gql, useQuery, QueryHookOptions } from '@apollo/client';
|
||||
import { Workgroup } from '../../types/workgroup';
|
||||
import { useGraphQLData } from './helper';
|
||||
|
||||
export const QUERY_WORKGROUP = gql`
|
||||
query workgroups($filter: WorkgroupsFilter) {
|
||||
workgroups(filter: $filter) {
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
closedAt,
|
||||
members {
|
||||
id,
|
||||
email,
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useWorkgroupsQuery<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
return useQuery(QUERY_WORKGROUP, options);
|
||||
}
|
||||
|
||||
export function useWorkgroups<A = any, R = Record<string, any>>(options: QueryHookOptions<A, R> = {}) {
|
||||
const { data, loading, error } = useGraphQLData<Workgroup[]>(
|
||||
QUERY_WORKGROUP, 'workgroups', [],
|
||||
options
|
||||
);
|
||||
return { workgroups: data, loading, error };
|
||||
}
|
90
client/src/hooks/useConference.tsx
Normal file
90
client/src/hooks/useConference.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import * as Y from 'yjs'
|
||||
import { WebrtcProvider, } from 'y-webrtc'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { uuidV4 } from '../util/uuid';
|
||||
|
||||
const UUIDKey = 'conference-uuid';
|
||||
let uuid = localStorage.getItem(UUIDKey);
|
||||
|
||||
if (!uuid) {
|
||||
uuid = uuidV4();
|
||||
localStorage.setItem(UUIDKey, uuid);
|
||||
}
|
||||
|
||||
export function useConference() {
|
||||
const docRef = useRef(new Y.Doc());
|
||||
|
||||
const [ state, setState ] = useState({
|
||||
data: {
|
||||
emails: {},
|
||||
nicknames: {},
|
||||
statuses: {},
|
||||
peers: {},
|
||||
},
|
||||
uuid,
|
||||
});
|
||||
|
||||
const setData = (key: string, value: any) => {
|
||||
setState(state => ({...state, data: { ...state.data, [key]: value }}));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const doc = docRef.current;
|
||||
const roomName = `${window.location.protocol}//${window.location.host}/daddy/conference`;
|
||||
const provider = new WebrtcProvider(roomName, docRef.current);
|
||||
|
||||
const peers = doc.getMap('peers');
|
||||
peers.observe(evt => setData('peers', evt.currentTarget.toJSON()));
|
||||
|
||||
const nicknames = doc.getMap('nicknames');
|
||||
nicknames.observe(evt => setData('nicknames', evt.currentTarget.toJSON()));
|
||||
|
||||
const emails = doc.getMap('emails');
|
||||
emails.observe(evt => setData('emails', evt.currentTarget.toJSON()));
|
||||
|
||||
const statuses = doc.getMap('statuses');
|
||||
statuses.observe(evt => setData('statuses', evt.currentTarget.toJSON()));
|
||||
|
||||
return () => {
|
||||
provider.destroy();
|
||||
docRef.current.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data: state.data,
|
||||
uuid: state.uuid,
|
||||
|
||||
setStatus: (status: string) => {
|
||||
const doc = docRef.current;
|
||||
const statuses = doc.getMap('statuses');
|
||||
statuses.set(state.uuid, status);
|
||||
},
|
||||
|
||||
ping: () => {
|
||||
const doc = docRef.current;
|
||||
const peers = doc.getMap('peers');
|
||||
peers.set(state.uuid, (new Date()).toJSON());
|
||||
},
|
||||
|
||||
setNickname: (nickname: string) => {
|
||||
console.log('setNickname', nickname);
|
||||
const doc = docRef.current;
|
||||
const nicknames = doc.getMap('nicknames');
|
||||
nicknames.set(state.uuid, nickname);
|
||||
},
|
||||
|
||||
setEmail: (email: string) => {
|
||||
console.log('setEmail', email);
|
||||
const doc = docRef.current;
|
||||
const emails = doc.getMap('emails');
|
||||
emails.set(state.uuid, email);
|
||||
},
|
||||
|
||||
forget: (uuid: string) => {
|
||||
const doc = docRef.current;
|
||||
const peers = doc.getMap('peers');
|
||||
peers.delete(uuid);
|
||||
},
|
||||
};
|
||||
}
|
19
client/src/hooks/useDebounce.tsx
Normal file
19
client/src/hooks/useDebounce.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useDebounce(value, delay) {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
},
|
||||
[value, delay]
|
||||
);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
20
client/src/hooks/useKonamiCode.tsx
Normal file
20
client/src/hooks/useKonamiCode.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function useKonamiCode(cb: Function) {
|
||||
const KONAMI_CODE = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
|
||||
let cursor = 0;
|
||||
|
||||
useEffect(()=> {
|
||||
const onKeyDown = (e) => {
|
||||
cursor = (e.keyCode == KONAMI_CODE[cursor]) ? cursor + 1 : 0;
|
||||
if (cursor == KONAMI_CODE.length) {
|
||||
cb();
|
||||
cursor = 0;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
|
||||
return () => document.removeEventListener('keydown', onKeyDown);
|
||||
}, []);
|
||||
}
|
22
client/src/hooks/useLoggedIn.tsx
Normal file
22
client/src/hooks/useLoggedIn.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
|
||||
const LOGGED_IN_KEY = 'loggedIn';
|
||||
|
||||
export const LoggedInContext = React.createContext(getSavedLoggedIn());
|
||||
|
||||
export const useLoggedIn = () => {
|
||||
return useContext(LoggedInContext);
|
||||
};
|
||||
|
||||
export function saveLoggedIn(loggedIn: boolean) {
|
||||
window.localStorage.setItem(LOGGED_IN_KEY, JSON.stringify(loggedIn));
|
||||
}
|
||||
|
||||
export function getSavedLoggedIn(): boolean {
|
||||
try {
|
||||
const loggedIn = JSON.parse(window.localStorage.getItem(LOGGED_IN_KEY));
|
||||
return !!loggedIn;
|
||||
} catch(err) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="has-navbar-fixed-top">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { Config } from './config';
|
||||
|
||||
declare var __webpack_public_path__: string;
|
||||
__webpack_public_path__ = Config.frontendBaseURL;
|
||||
|
||||
import './sass/_all.scss';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { App } from './components/App';
|
||||
import { Config } from './config';
|
||||
|
||||
import '@fortawesome/fontawesome-free/js/fontawesome'
|
||||
import '@fortawesome/fontawesome-free/js/solid'
|
||||
@ -10,6 +14,7 @@ import '@fortawesome/fontawesome-free/js/regular'
|
||||
import '@fortawesome/fontawesome-free/js/brands'
|
||||
import './resources/favicon.png';
|
||||
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.getElementById('app')
|
||||
|
@ -1,4 +1,5 @@
|
||||
@import 'bulma/bulma.sass';
|
||||
@import 'bulma-switch/dist/css/bulma-switch.sass';
|
||||
@import 'bulma-timeline/dist/css/bulma-timeline.sass';
|
||||
@import '_bulma-timeline.scss';
|
||||
@import '_base.scss';
|
||||
@import '_loader.scss';
|
@ -1,6 +1,8 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
background-color: #f7f7f7;
|
||||
background-color: #ffffff;
|
||||
// Generated with https://www.svgbackgrounds.com/
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='351' height='292.5' viewBox='0 0 1080 900'%3E%3Cg fill-opacity='0.04'%3E%3Cpolygon fill='%23444' points='90 150 0 300 180 300'/%3E%3Cpolygon points='90 150 180 0 0 0'/%3E%3Cpolygon fill='%23AAA' points='270 150 360 0 180 0'/%3E%3Cpolygon fill='%23DDD' points='450 150 360 300 540 300'/%3E%3Cpolygon fill='%23999' points='450 150 540 0 360 0'/%3E%3Cpolygon points='630 150 540 300 720 300'/%3E%3Cpolygon fill='%23DDD' points='630 150 720 0 540 0'/%3E%3Cpolygon fill='%23444' points='810 150 720 300 900 300'/%3E%3Cpolygon fill='%23FFF' points='810 150 900 0 720 0'/%3E%3Cpolygon fill='%23DDD' points='990 150 900 300 1080 300'/%3E%3Cpolygon fill='%23444' points='990 150 1080 0 900 0'/%3E%3Cpolygon fill='%23DDD' points='90 450 0 600 180 600'/%3E%3Cpolygon points='90 450 180 300 0 300'/%3E%3Cpolygon fill='%23666' points='270 450 180 600 360 600'/%3E%3Cpolygon fill='%23AAA' points='270 450 360 300 180 300'/%3E%3Cpolygon fill='%23DDD' points='450 450 360 600 540 600'/%3E%3Cpolygon fill='%23999' points='450 450 540 300 360 300'/%3E%3Cpolygon fill='%23999' points='630 450 540 600 720 600'/%3E%3Cpolygon fill='%23FFF' points='630 450 720 300 540 300'/%3E%3Cpolygon points='810 450 720 600 900 600'/%3E%3Cpolygon fill='%23DDD' points='810 450 900 300 720 300'/%3E%3Cpolygon fill='%23AAA' points='990 450 900 600 1080 600'/%3E%3Cpolygon fill='%23444' points='990 450 1080 300 900 300'/%3E%3Cpolygon fill='%23222' points='90 750 0 900 180 900'/%3E%3Cpolygon points='270 750 180 900 360 900'/%3E%3Cpolygon fill='%23DDD' points='270 750 360 600 180 600'/%3E%3Cpolygon points='450 750 540 600 360 600'/%3E%3Cpolygon points='630 750 540 900 720 900'/%3E%3Cpolygon fill='%23444' points='630 750 720 600 540 600'/%3E%3Cpolygon fill='%23AAA' points='810 750 720 900 900 900'/%3E%3Cpolygon fill='%23666' points='810 750 900 600 720 600'/%3E%3Cpolygon fill='%23999' points='990 750 900 900 1080 900'/%3E%3Cpolygon fill='%23999' points='180 0 90 150 270 150'/%3E%3Cpolygon fill='%23444' points='360 0 270 150 450 150'/%3E%3Cpolygon fill='%23FFF' points='540 0 450 150 630 150'/%3E%3Cpolygon points='900 0 810 150 990 150'/%3E%3Cpolygon fill='%23222' points='0 300 -90 450 90 450'/%3E%3Cpolygon fill='%23FFF' points='0 300 90 150 -90 150'/%3E%3Cpolygon fill='%23FFF' points='180 300 90 450 270 450'/%3E%3Cpolygon fill='%23666' points='180 300 270 150 90 150'/%3E%3Cpolygon fill='%23222' points='360 300 270 450 450 450'/%3E%3Cpolygon fill='%23FFF' points='360 300 450 150 270 150'/%3E%3Cpolygon fill='%23444' points='540 300 450 450 630 450'/%3E%3Cpolygon fill='%23222' points='540 300 630 150 450 150'/%3E%3Cpolygon fill='%23AAA' points='720 300 630 450 810 450'/%3E%3Cpolygon fill='%23666' points='720 300 810 150 630 150'/%3E%3Cpolygon fill='%23FFF' points='900 300 810 450 990 450'/%3E%3Cpolygon fill='%23999' points='900 300 990 150 810 150'/%3E%3Cpolygon points='0 600 -90 750 90 750'/%3E%3Cpolygon fill='%23666' points='0 600 90 450 -90 450'/%3E%3Cpolygon fill='%23AAA' points='180 600 90 750 270 750'/%3E%3Cpolygon fill='%23444' points='180 600 270 450 90 450'/%3E%3Cpolygon fill='%23444' points='360 600 270 750 450 750'/%3E%3Cpolygon fill='%23999' points='360 600 450 450 270 450'/%3E%3Cpolygon fill='%23666' points='540 600 630 450 450 450'/%3E%3Cpolygon fill='%23222' points='720 600 630 750 810 750'/%3E%3Cpolygon fill='%23FFF' points='900 600 810 750 990 750'/%3E%3Cpolygon fill='%23222' points='900 600 990 450 810 450'/%3E%3Cpolygon fill='%23DDD' points='0 900 90 750 -90 750'/%3E%3Cpolygon fill='%23444' points='180 900 270 750 90 750'/%3E%3Cpolygon fill='%23FFF' points='360 900 450 750 270 750'/%3E%3Cpolygon fill='%23AAA' points='540 900 630 750 450 750'/%3E%3Cpolygon fill='%23FFF' points='720 900 810 750 630 750'/%3E%3Cpolygon fill='%23222' points='900 900 990 750 810 750'/%3E%3Cpolygon fill='%23222' points='1080 300 990 450 1170 450'/%3E%3Cpolygon fill='%23FFF' points='1080 300 1170 150 990 150'/%3E%3Cpolygon points='1080 600 990 750 1170 750'/%3E%3Cpolygon fill='%23666' points='1080 600 1170 450 990 450'/%3E%3Cpolygon fill='%23DDD' points='1080 900 1170 750 990 750'/%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.is-fullheight {
|
||||
@ -19,3 +21,7 @@ html, body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: #ffffff;
|
||||
}
|
12
client/src/sass/_bulma-timeline.scss
Normal file
12
client/src/sass/_bulma-timeline.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.timeline {
|
||||
.timeline-item {
|
||||
.timeline-marker {
|
||||
&.is-icon {
|
||||
> svg {
|
||||
color: $white;
|
||||
font-size: $timeline-icon-size !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +1,16 @@
|
||||
.loader-container {
|
||||
.app-loader {
|
||||
@extend body;
|
||||
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.lds-ripple {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
.lds-ripple div {
|
||||
position: absolute;
|
||||
border: 4px solid $grey;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
|
||||
.lds-ripple div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
@keyframes lds-ripple {
|
||||
0% {
|
||||
top: 36px;
|
||||
left: 36px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
opacity: 0;
|
||||
}
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { Action } from "redux";
|
||||
|
||||
export const SET_CURRENT_USER = 'SET_CURRENT_USER';
|
||||
|
||||
export interface setCurrentUserAction extends Action {
|
||||
email: string
|
||||
}
|
||||
|
||||
export function setCurrentUser(email: string): setCurrentUserAction {
|
||||
return { type: SET_CURRENT_USER, email };
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import { Action } from "redux";
|
||||
import { User } from "../../types/user";
|
||||
|
||||
export const FETCH_PROFILE_REQUEST = 'FETCH_PROFILE_REQUEST';
|
||||
export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
|
||||
export const FETCH_PROFILE_FAILURE = 'FETCH_PROFILE_FAILURE';
|
||||
|
||||
export interface fetchProfileRequestAction extends Action {
|
||||
|
||||
}
|
||||
|
||||
export interface fetchProfileSuccessAction extends Action {
|
||||
profile: User
|
||||
}
|
||||
|
||||
|
||||
export function fetchProfile(): fetchProfileRequestAction {
|
||||
return { type: FETCH_PROFILE_REQUEST }
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import { Action } from "redux";
|
||||
import { User } from "../../types/user";
|
||||
import { SET_CURRENT_USER, setCurrentUserAction } from "../actions/auth";
|
||||
import { FETCH_PROFILE_SUCCESS, fetchProfileSuccessAction } from "../actions/profile";
|
||||
|
||||
export interface AuthState {
|
||||
isAuthenticated: boolean
|
||||
currentUser: User
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
isAuthenticated: false,
|
||||
currentUser: null,
|
||||
};
|
||||
|
||||
export function authReducer(state = defaultState, action: Action): AuthState {
|
||||
switch (action.type) {
|
||||
case SET_CURRENT_USER:
|
||||
return handleSetCurrentUser(state, action as setCurrentUserAction);
|
||||
case FETCH_PROFILE_SUCCESS:
|
||||
return handleFetchProfileSuccess(state, action as fetchProfileSuccessAction);
|
||||
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function handleSetCurrentUser(state: AuthState, { email }: setCurrentUserAction): AuthState {
|
||||
return {
|
||||
...state,
|
||||
isAuthenticated: true,
|
||||
currentUser: {
|
||||
email
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function handleFetchProfileSuccess(state: AuthState, { profile }: fetchProfileSuccessAction): AuthState {
|
||||
return {
|
||||
...state,
|
||||
isAuthenticated: true,
|
||||
currentUser: {
|
||||
email: profile.email,
|
||||
connectedAt: profile.connectedAt,
|
||||
createdAt: profile.createdAt,
|
||||
}
|
||||
};
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
import { Action } from "redux";
|
||||
|
||||
export interface FlagsState {
|
||||
actions: { [actionName: string]: ActionState }
|
||||
}
|
||||
|
||||
export interface ActionState {
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
actions: {}
|
||||
};
|
||||
|
||||
export function flagsReducer(state = defaultState, action: Action): FlagsState {
|
||||
const matches = (/^(.*)_((SUCCESS)|(FAILURE)|(REQUEST))$/).exec(action.type);
|
||||
|
||||
if(!matches) return state;
|
||||
|
||||
const actionPrefix = matches[1];
|
||||
|
||||
return {
|
||||
...state,
|
||||
actions: {
|
||||
...state.actions,
|
||||
[actionPrefix]: {
|
||||
isLoading: matches[2] === 'REQUEST'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { flagsReducer, FlagsState } from './flags';
|
||||
import { authReducer, AuthState } from './auth';
|
||||
|
||||
export interface RootState {
|
||||
auth: AuthState,
|
||||
flags: FlagsState,
|
||||
}
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
flags: flagsReducer,
|
||||
auth: authReducer,
|
||||
});
|
@ -1,21 +0,0 @@
|
||||
import { UnauthorizedError } from "../../util/daddy";
|
||||
import { all, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
export function* failureRootSaga() {
|
||||
yield all([
|
||||
takeEvery(patternFromRegExp(/^.*_FAILURE/), failuresSaga),
|
||||
]);
|
||||
}
|
||||
|
||||
export function* failuresSaga(action) {
|
||||
if (action.error instanceof UnauthorizedError) {
|
||||
// TODO Implements better authorization error handling
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
export function patternFromRegExp(re: any) {
|
||||
return (action: any) => {
|
||||
return re.test(action.type);
|
||||
};
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { all, put } from "redux-saga/effects";
|
||||
import { fetchProfile } from "../actions/profile";
|
||||
|
||||
export function* initRootSaga() {
|
||||
yield all([
|
||||
fetchUserProfileSaga(),
|
||||
]);
|
||||
}
|
||||
|
||||
export function* fetchUserProfileSaga() {
|
||||
yield put(fetchProfile());
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { all } from 'redux-saga/effects';
|
||||
import { failureRootSaga } from './failure';
|
||||
import { initRootSaga } from './init';
|
||||
import { usersRootSaga } from './users';
|
||||
|
||||
export function* rootSaga() {
|
||||
yield all([
|
||||
initRootSaga(),
|
||||
failureRootSaga(),
|
||||
usersRootSaga(),
|
||||
]);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import { DaddyClient, getClient } from "../../util/daddy";
|
||||
import { Config } from "../../config";
|
||||
import { all, takeLatest, put, select } from "redux-saga/effects";
|
||||
import { FETCH_PROFILE_REQUEST, fetchProfile, FETCH_PROFILE_FAILURE, FETCH_PROFILE_SUCCESS } from "../actions/profile";
|
||||
import { SET_CURRENT_USER } from "../actions/auth";
|
||||
import { RootState } from "../reducers/root";
|
||||
import { User } from "../../types/user";
|
||||
|
||||
export function* usersRootSaga() {
|
||||
yield all([
|
||||
takeLatest(SET_CURRENT_USER, onCurrentUserChangeSaga),
|
||||
takeLatest(FETCH_PROFILE_REQUEST, fetchProfileSaga),
|
||||
]);
|
||||
}
|
||||
|
||||
export function* onCurrentUserChangeSaga() {
|
||||
yield put(fetchProfile());
|
||||
}
|
||||
|
||||
export function* fetchProfileSaga() {
|
||||
const client = getClient(Config.graphQLEndpoint, Config.subscriptionEndpoint);
|
||||
|
||||
let profile: User;
|
||||
try {
|
||||
profile = yield client.fetchProfile().then(result => result.userProfile);
|
||||
console.log(profile);
|
||||
} catch(err) {
|
||||
yield put({ type: FETCH_PROFILE_FAILURE, err });
|
||||
return;
|
||||
}
|
||||
|
||||
yield put({type: FETCH_PROFILE_SUCCESS, profile });
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
export function selectFlagsIsLoading(state: any, ...actionPrefixes: any[]) {
|
||||
const { actions } = state.flags;
|
||||
return actionPrefixes.reduce((isLoading, prefix) => {
|
||||
if (!(prefix in actions)) return isLoading;
|
||||
return isLoading || actions[prefix].isLoading;
|
||||
}, false);
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import { createStore, applyMiddleware, compose } from 'redux'
|
||||
import createSagaMiddleware from 'redux-saga'
|
||||
import { rootReducer } from './reducers/root'
|
||||
import { rootSaga } from './sagas/root'
|
||||
|
||||
let reduxMiddlewares = [];
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const createLogger = require('redux-logger').createLogger;
|
||||
const loggerMiddleware = createLogger({
|
||||
collapsed: true,
|
||||
diff: true
|
||||
});
|
||||
reduxMiddlewares.push(loggerMiddleware);
|
||||
}
|
||||
|
||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
||||
// create the saga middleware
|
||||
const sagaMiddleware = createSagaMiddleware()
|
||||
reduxMiddlewares.push(sagaMiddleware);
|
||||
|
||||
// mount it on the Store
|
||||
export const store = createStore(
|
||||
rootReducer,
|
||||
composeEnhancers(applyMiddleware(...reduxMiddlewares)),
|
||||
)
|
||||
|
||||
// then run the saga
|
||||
sagaMiddleware.run(rootSaga);
|
35
client/src/types/decision.tsx
Normal file
35
client/src/types/decision.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { Workgroup } from "./workgroup";
|
||||
|
||||
export enum DecisionSupportFileStatus {
|
||||
Draft = "draft",
|
||||
Ready = "ready",
|
||||
Voted = "voted",
|
||||
Closed = "closed",
|
||||
}
|
||||
|
||||
export interface DecisionSupportFileSection {
|
||||
name: string
|
||||
}
|
||||
|
||||
// aka Dossier d'aide à la décision
|
||||
export interface DecisionSupportFile {
|
||||
id: string
|
||||
title: string
|
||||
sections: {[name: string]: any}
|
||||
status: DecisionSupportFileStatus
|
||||
workgroup?: Workgroup,
|
||||
createdAt: Date
|
||||
votedAt?: Date
|
||||
closedAt?: Date
|
||||
}
|
||||
|
||||
export function newDecisionSupportFile(): DecisionSupportFile {
|
||||
return {
|
||||
id: '',
|
||||
title: '',
|
||||
sections: {},
|
||||
status: DecisionSupportFileStatus.Draft,
|
||||
workgroup: null,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
}
|
11
client/src/types/event.ts
Normal file
11
client/src/types/event.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { User } from "./user";
|
||||
|
||||
export interface Event {
|
||||
id: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
user: User
|
||||
objectType: string
|
||||
objectId: number
|
||||
type: string
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
export interface User {
|
||||
id: string
|
||||
email: string
|
||||
name?: string
|
||||
connectedAt?: Date
|
||||
createdAt?: Date
|
||||
}
|
18
client/src/types/workgroup.ts
Normal file
18
client/src/types/workgroup.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { User } from "./user";
|
||||
export interface Workgroup {
|
||||
id: string
|
||||
name: string
|
||||
createdAt: Date
|
||||
closedAt: Date
|
||||
members: User[]
|
||||
}
|
||||
|
||||
export function inWorkgroup(u: User, wg: Workgroup): boolean {
|
||||
for (let m, i = 0; (m = wg.members[i]); i++) {
|
||||
if(m.id === u.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
87
client/src/util/apollo.ts
Normal file
87
client/src/util/apollo.ts
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
import { ApolloClient, InMemoryCache, HttpLink, from } from '@apollo/client';
|
||||
import { Config } from '../config';
|
||||
import { WebSocketLink } from "@apollo/client/link/ws";
|
||||
import { RetryLink } from "@apollo/client/link/retry";
|
||||
import { onError } from "@apollo/client/link/error";
|
||||
import { SubscriptionClient } from "subscriptions-transport-ws";
|
||||
import { User } from '../types/user';
|
||||
|
||||
export function createClient(setLoggedIn: (boolean) => void) {
|
||||
const subscriptionClient = new SubscriptionClient(Config.subscriptionEndpoint, {
|
||||
reconnect: true,
|
||||
});
|
||||
|
||||
const errorLink = onError(({ operation }) => {
|
||||
const { response } = operation.getContext();
|
||||
if (response.status === 401) setLoggedIn(false);
|
||||
});
|
||||
|
||||
const retryLink = new RetryLink({attempts: {max: 2}}).split(
|
||||
(operation) => operation.operationName === 'subscription',
|
||||
new WebSocketLink(subscriptionClient),
|
||||
new HttpLink({
|
||||
uri: Config.graphQLEndpoint,
|
||||
credentials: 'include',
|
||||
})
|
||||
);
|
||||
|
||||
const cache = new InMemoryCache({
|
||||
typePolicies: {
|
||||
Workgroup: {
|
||||
fields: {
|
||||
members: {
|
||||
merge: mergeArrayByField<User>("id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new ApolloClient<any>({
|
||||
cache: cache,
|
||||
link: from([
|
||||
errorLink,
|
||||
retryLink
|
||||
]),
|
||||
defaultOptions: {
|
||||
watchQuery: {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
errorPolicy: 'ignore',
|
||||
},
|
||||
query: {
|
||||
fetchPolicy: 'network-only',
|
||||
errorPolicy: 'all',
|
||||
},
|
||||
mutate: {
|
||||
errorPolicy: 'all',
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function mergeArrayByField<T>(fieldName: string) {
|
||||
return (existing: T[] = [], incoming: T[], { readField, mergeObjects }) => {
|
||||
const merged: any[] = existing ? existing.slice(0) : [];
|
||||
|
||||
const objectFieldToIndex: Record<string, number> = Object.create(null);
|
||||
if (existing) {
|
||||
existing.forEach((obj, index) => {
|
||||
objectFieldToIndex[readField(fieldName, obj)] = index;
|
||||
});
|
||||
}
|
||||
|
||||
incoming.forEach(obj => {
|
||||
const field = readField(fieldName, obj);
|
||||
const index = objectFieldToIndex[field];
|
||||
if (typeof index === "number") {
|
||||
merged[index] = mergeObjects(merged[index], obj);
|
||||
} else {
|
||||
objectFieldToIndex[name] = merged.length;
|
||||
merged.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
return merged;
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
import ApolloClient from 'apollo-client';
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
||||
import { split } from 'apollo-link';
|
||||
import { HttpLink } from 'apollo-link-http';
|
||||
import { WebSocketLink } from 'apollo-link-ws';
|
||||
import { getMainDefinition } from 'apollo-utilities';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export class UnauthorizedError extends Error {
|
||||
constructor(...args: any[]) {
|
||||
super(...args)
|
||||
Object.setPrototypeOf(this, UnauthorizedError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
let client: DaddyClient
|
||||
|
||||
export function getClient(graphQLEndpoint: string, subscriptionEndpoint: string): DaddyClient {
|
||||
if (!client) {
|
||||
client = new DaddyClient(graphQLEndpoint, subscriptionEndpoint);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export class DaddyClient {
|
||||
|
||||
gql: ApolloClient<InMemoryCache>
|
||||
|
||||
constructor(graphQLEndpoint: string, subscriptionEndpoint: string) {
|
||||
const wsLink = new WebSocketLink({
|
||||
uri: subscriptionEndpoint,
|
||||
options: {
|
||||
reconnect: true
|
||||
}
|
||||
});
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
uri: graphQLEndpoint,
|
||||
fetchOptions: {
|
||||
mode: 'cors',
|
||||
credentials: 'include',
|
||||
}
|
||||
});
|
||||
|
||||
const link = split(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === 'OperationDefinition' &&
|
||||
definition.operation === 'subscription'
|
||||
);
|
||||
},
|
||||
wsLink,
|
||||
httpLink,
|
||||
);
|
||||
|
||||
this.gql = new ApolloClient<any>({
|
||||
link: link,
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
}
|
||||
|
||||
fetchProfile() {
|
||||
return this.gql.query({
|
||||
query: gql`
|
||||
query {
|
||||
userProfile {
|
||||
email,
|
||||
createdAt,
|
||||
connectedAt
|
||||
}
|
||||
}`
|
||||
})
|
||||
.then(this.assertAuthorization)
|
||||
}
|
||||
|
||||
assertAuthorization({ status, data }: any) {
|
||||
if (status === 401) return Promise.reject(new UnauthorizedError());
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
16
client/src/util/date.ts
Normal file
16
client/src/util/date.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export function asDate(d: string|Date): Date {
|
||||
if (typeof d === 'string') return new Date(d);
|
||||
return d;
|
||||
}
|
||||
|
||||
const intl = Intl.DateTimeFormat(navigator.language, {
|
||||
weekday: 'long',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric', minute: 'numeric', second: 'numeric',
|
||||
});
|
||||
|
||||
export function formatDate(d: Date|string): string {
|
||||
d = asDate(d);
|
||||
return intl.format(d);
|
||||
}
|
53
client/src/util/uuid.ts
Normal file
53
client/src/util/uuid.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import bs58 from 'bs58';
|
||||
|
||||
const hex: string[] = [];
|
||||
|
||||
for (var i = 0; i < 256; i++) {
|
||||
hex[i] = (i < 16 ? '0' : '') + (i).toString(16);
|
||||
}
|
||||
|
||||
export function uuidV4(): string {
|
||||
const r = crypto.getRandomValues(new Uint8Array(16));
|
||||
|
||||
r[6] = r[6] & 0x0f | 0x40;
|
||||
r[8] = r[8] & 0x3f | 0x80;
|
||||
|
||||
return (
|
||||
hex[r[0]] +
|
||||
hex[r[1]] +
|
||||
hex[r[2]] +
|
||||
hex[r[3]] +
|
||||
"-" +
|
||||
hex[r[4]] +
|
||||
hex[r[5]] +
|
||||
"-" +
|
||||
hex[r[6]] +
|
||||
hex[r[7]] +
|
||||
"-" +
|
||||
hex[r[8]] +
|
||||
hex[r[9]] +
|
||||
"-" +
|
||||
hex[r[10]] +
|
||||
hex[r[11]] +
|
||||
hex[r[12]] +
|
||||
hex[r[13]] +
|
||||
hex[r[14]] +
|
||||
hex[r[15]]
|
||||
);
|
||||
}
|
||||
|
||||
export function toUTF8Bytes(str: string): number[] {
|
||||
var utf8 = unescape(encodeURIComponent(str));
|
||||
|
||||
var arr: number[] = [];
|
||||
for (var i = 0; i < utf8.length; i++) {
|
||||
arr.push(utf8.charCodeAt(i));
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
||||
export function base58UUID(): string {
|
||||
const uuid = uuidV4();
|
||||
return bs58.encode(toUTF8Bytes(uuid));
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "es6",
|
||||
"module": "es2020",
|
||||
"lib": ["dom", "es6"],
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
|
@ -11,7 +11,7 @@ const env = process.env;
|
||||
module.exports = {
|
||||
mode: `${env.NODE_ENV ? env.NODE_ENV : 'production'}`,
|
||||
entry: './src/index.tsx',
|
||||
devtool: 'inline-source-map',
|
||||
devtool: env.NODE_ENV === 'production' ? 'source-map' : 'eval-source-map',
|
||||
output: {
|
||||
filename: '[name].[contenthash].js',
|
||||
path: path.join(__dirname, 'dist')
|
||||
|
@ -3,17 +3,21 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/migration"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/mail"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/voter"
|
||||
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/database"
|
||||
"github.com/wader/gormstore"
|
||||
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/auth"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/config"
|
||||
oidc "forge.cadoles.com/wpetit/goweb-oidc"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
"gitlab.com/wpetit/goweb/service/build"
|
||||
@ -51,23 +55,37 @@ func getServiceContainer(ctx context.Context, conf *config.Config) (*service.Con
|
||||
conf.HTTP.CookieEncryptionKey = string(cookieEncryptionKey)
|
||||
}
|
||||
|
||||
ctn.Provide(orm.ServiceName, orm.ServiceProvider("postgres", conf.Database.DSN, conf.Debug))
|
||||
|
||||
orm, err := orm.From(ctn)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// Create and initialize HTTP session service provider
|
||||
cookieStore := sessions.NewCookieStore(
|
||||
sessionStore := gormstore.NewOptions(
|
||||
orm.DB(),
|
||||
gormstore.Options{
|
||||
TableName: "sessions",
|
||||
SkipCreateTable: false,
|
||||
},
|
||||
[]byte(conf.HTTP.CookieAuthenticationKey),
|
||||
[]byte(conf.HTTP.CookieEncryptionKey),
|
||||
)
|
||||
|
||||
quit := make(chan struct{})
|
||||
go sessionStore.PeriodicCleanup(1*time.Hour, quit)
|
||||
|
||||
// Define default cookie options
|
||||
cookieStore.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
MaxAge: conf.HTTP.CookieMaxAge,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
}
|
||||
sessionStore.SessionOpts.Path = "/"
|
||||
sessionStore.SessionOpts.HttpOnly = true
|
||||
sessionStore.SessionOpts.Secure = conf.HTTP.CookieSecure
|
||||
sessionStore.SessionOpts.MaxAge = conf.HTTP.CookieMaxAge
|
||||
sessionStore.SessionOpts.SameSite = http.SameSiteLaxMode
|
||||
|
||||
ctn.Provide(
|
||||
session.ServiceName,
|
||||
gorilla.ServiceProvider("daddy", cookieStore),
|
||||
gorilla.ServiceProvider("daddy", sessionStore),
|
||||
)
|
||||
|
||||
// Create and expose config service provider
|
||||
@ -84,21 +102,19 @@ func getServiceContainer(ctx context.Context, conf *config.Config) (*service.Con
|
||||
oidc.WithScopes("email", "openid"),
|
||||
))
|
||||
|
||||
ctn.Provide(database.ServiceName, database.ServiceProvider(conf.Database.DSN))
|
||||
ctn.Provide(auth.ServiceName, auth.ServiceProvider(conf.Auth.Rules))
|
||||
|
||||
dbpool, err := database.From(ctn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve database service")
|
||||
}
|
||||
ctn.Provide(voter.ServiceName, voter.ServiceProvider(
|
||||
voter.StrategyUnanimous,
|
||||
model.NewDecisionSupportFileVoter(),
|
||||
model.NewWorkgroupVoter(),
|
||||
))
|
||||
|
||||
versionResolver := database.NewVersionResolver(dbpool)
|
||||
if err := versionResolver.Init(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize database version resolver")
|
||||
}
|
||||
|
||||
ctn.Provide(migration.ServiceName, migration.ServiceProvider(versionResolver))
|
||||
|
||||
ctn.Provide(cqrs.ServiceName, cqrs.ServiceProvider())
|
||||
ctn.Provide(mail.ServiceName, mail.ServiceProvider(
|
||||
mail.WithServer(conf.SMTP.Host, conf.SMTP.Port),
|
||||
mail.WithCredentials(conf.SMTP.User, conf.SMTP.Password),
|
||||
mail.WithTLS(conf.SMTP.UseStartTLS, conf.SMTP.InsecureSkipVerify),
|
||||
))
|
||||
|
||||
return ctn, nil
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/command"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/query"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
)
|
||||
|
||||
func initCommands(ctn *service.Container) error {
|
||||
dispatcher, err := cqrs.From(ctn)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
dispatcher.RegisterCommand(
|
||||
cqrs.MatchCommandRequest(&command.CreateUserCommandRequest{}),
|
||||
cqrs.CommandHandlerFunc(command.HandleCreateUserCommand),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initQueries(ctn *service.Container) error {
|
||||
dispatcher, err := cqrs.From(ctn)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
dispatcher.RegisterQuery(
|
||||
cqrs.MatchQueryRequest(&query.FindUserQueryRequest{}),
|
||||
cqrs.QueryHandlerFunc(query.HandleFindUserQuery),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/config"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/route"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
@ -17,6 +18,7 @@ import (
|
||||
|
||||
"os"
|
||||
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
@ -129,6 +131,34 @@ func main() {
|
||||
logger.Debug(ctx, "setting log level", logger.F("level", conf.Log.Level.String()))
|
||||
logger.SetLevel(conf.Log.Level)
|
||||
|
||||
useSentry := conf.Sentry.DSN != ""
|
||||
|
||||
if useSentry {
|
||||
var sentryEnv string
|
||||
if conf.Sentry.Environment == "" {
|
||||
sentryEnv, _ = os.Hostname()
|
||||
} else {
|
||||
sentryEnv = conf.Sentry.Environment
|
||||
}
|
||||
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: conf.Sentry.DSN,
|
||||
Debug: conf.Debug,
|
||||
SampleRate: conf.Sentry.ServerSampleRate,
|
||||
Release: ProjectVersion + "-" + GitRef,
|
||||
Environment: sentryEnv,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Fatal(
|
||||
ctx,
|
||||
"could initialize sentry",
|
||||
logger.E(err),
|
||||
)
|
||||
}
|
||||
|
||||
defer sentry.Flush(conf.Sentry.ServerFlushTimeout)
|
||||
}
|
||||
|
||||
// Create service container
|
||||
ctn, err := getServiceContainer(ctx, conf)
|
||||
if err != nil {
|
||||
@ -153,22 +183,7 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Init commands and queries
|
||||
if err := initCommands(ctn); err != nil {
|
||||
logger.Fatal(
|
||||
ctx,
|
||||
"could not init commands",
|
||||
logger.E(err),
|
||||
)
|
||||
}
|
||||
|
||||
if err := initQueries(ctn); err != nil {
|
||||
logger.Fatal(
|
||||
ctx,
|
||||
"could not init queries",
|
||||
logger.E(err),
|
||||
)
|
||||
}
|
||||
go runTaskScheduler(ctx, conf)
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
@ -176,6 +191,14 @@ func main() {
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
if useSentry {
|
||||
sentryMiddleware := sentryhttp.New(sentryhttp.Options{
|
||||
Repanic: true,
|
||||
})
|
||||
|
||||
r.Use(sentryMiddleware.Handle)
|
||||
}
|
||||
|
||||
// Expose service container on router
|
||||
r.Use(container.ServiceContainer(ctn))
|
||||
|
||||
|
@ -3,9 +3,9 @@ package main
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/database"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/migration"
|
||||
"github.com/jackc/pgx/v4"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/orm"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
@ -18,11 +18,13 @@ const (
|
||||
)
|
||||
|
||||
func applyMigration(ctx context.Context, ctn *service.Container) error {
|
||||
migr, err := migration.From(ctn)
|
||||
orm, err := orm.From(ctn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
migr := orm.Migration()
|
||||
|
||||
// Register available migrations
|
||||
migr.Register(
|
||||
m000initialSchema(),
|
||||
@ -74,29 +76,34 @@ func applyMigration(ctx context.Context, ctn *service.Container) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func m000initialSchema() migration.Migration {
|
||||
return database.NewMigration(
|
||||
// nolint: gochecknoglobals
|
||||
var initialModels = []interface{}{
|
||||
&model.User{},
|
||||
&model.Workgroup{},
|
||||
&model.DecisionSupportFile{},
|
||||
&model.Event{},
|
||||
}
|
||||
|
||||
func m000initialSchema() orm.Migration {
|
||||
return orm.NewDBMigration(
|
||||
"00_initial_schema",
|
||||
func(ctx context.Context, tx pgx.Tx) error {
|
||||
_, err := tx.Exec(ctx, `
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT,
|
||||
email TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
connected_at TIMESTAMPTZ,
|
||||
CONSTRAINT unique_email unique(email)
|
||||
);
|
||||
`)
|
||||
func(ctx context.Context, tx *gorm.DB) error {
|
||||
for _, m := range initialModels {
|
||||
if err := tx.AutoMigrate(m).Error; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
},
|
||||
func(ctx context.Context, tx pgx.Tx) error {
|
||||
_, err := tx.Exec(ctx, `
|
||||
DROP TABLE users;
|
||||
`)
|
||||
func(ctx context.Context, tx *gorm.DB) error {
|
||||
for i := len(initialModels) - 1; i >= 0; i-- {
|
||||
if err := tx.DropTableIfExists(initialModels[i]).Error; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
85
cmd/server/scheduler.go
Normal file
85
cmd/server/scheduler.go
Normal file
@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/config"
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/task"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
type cronLogger struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (l *cronLogger) Info(msg string, keysAndValues ...interface{}) {
|
||||
fields := l.createFields(keysAndValues)
|
||||
logger.Info(l.ctx, msg, fields...)
|
||||
}
|
||||
|
||||
func (l *cronLogger) Error(err error, msg string, keysAndValues ...interface{}) {
|
||||
fields := l.createFields(keysAndValues)
|
||||
fields = append(fields, logger.E(err))
|
||||
logger.Error(l.ctx, msg, fields...)
|
||||
}
|
||||
|
||||
func (l *cronLogger) createFields(keysAndValues ...interface{}) []logger.Field {
|
||||
fields := make([]logger.Field, 0)
|
||||
|
||||
var key string
|
||||
|
||||
for _, v := range keysAndValues {
|
||||
children, ok := v.([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, vv := range children {
|
||||
if i%2 == 0 {
|
||||
key = fmt.Sprintf("%v", vv)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
fields = append(fields, logger.F(key, vv))
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func runTaskScheduler(ctx context.Context, conf *config.Config) {
|
||||
c := cron.New(
|
||||
cron.WithLogger(&cronLogger{ctx}),
|
||||
)
|
||||
|
||||
tasks := map[string]task.Task{
|
||||
conf.Task.Newsletter.CronSpec: task.NewNewsletter(
|
||||
ctx,
|
||||
conf.Task.Newsletter.TimeRange,
|
||||
conf.Task.Newsletter.BaseURL,
|
||||
conf.Task.Newsletter.ContentTemplate,
|
||||
conf.Task.Newsletter.SubjectTemplate,
|
||||
conf.SMTP.SenderAddress,
|
||||
),
|
||||
}
|
||||
|
||||
for spec, task := range tasks {
|
||||
if _, err := c.AddFunc(spec, task.Run); err != nil {
|
||||
logger.Fatal(
|
||||
ctx,
|
||||
"could not schedule task",
|
||||
logger.F("task", task.Name()),
|
||||
logger.E(errors.WithStack(err)),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Start()
|
||||
}
|
@ -32,7 +32,7 @@ services:
|
||||
command: hydra serve all --dangerous-force-http
|
||||
|
||||
hydra-passwordless:
|
||||
image: bornholm/hydra-passwordless:latest
|
||||
image: bornholm/hydra-passwordless:latest@sha256:e6b335e3677dc937c62978890b42312a7486e4fe10208aa2670b1917489ec492
|
||||
ports:
|
||||
- 3000:3000
|
||||
environment:
|
||||
@ -48,6 +48,7 @@ services:
|
||||
- SMTP_INSECURE_SKIP_VERIFY=true
|
||||
- HYDRA_BASE_URL=http://hydra:4445
|
||||
- HYDRA_FAKE_SSL_TERMINATION=false
|
||||
- NO_PROXY=hydra
|
||||
|
||||
smtp:
|
||||
image: bornholm/fake-smtp
|
||||
|
25
go.mod
25
go.mod
@ -3,18 +3,37 @@ module forge.cadoles.com/Cadoles/daddy
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20200619080035-4bbf7b016032
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20201013125038-8d8d1519a52d
|
||||
forge.cadoles.com/wpetit/hydra-passwordless v0.0.0-20200908094025-38ac4422dddc // indirect
|
||||
github.com/99designs/gqlgen v0.11.3
|
||||
github.com/alecthomas/chroma v0.8.1 // indirect
|
||||
github.com/antonmedv/expr v1.8.8
|
||||
github.com/caarlos0/env/v6 v6.2.2
|
||||
github.com/cortesi/modd v0.0.0-20200630120222-8983974e5450 // indirect
|
||||
github.com/go-chi/chi v4.1.0+incompatible
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/getsentry/sentry-go v0.7.0
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/gorilla/sessions v1.2.0
|
||||
github.com/gorilla/websocket v1.2.0
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/jackc/pgx v3.6.2+incompatible
|
||||
github.com/jackc/pgx/v4 v4.7.1
|
||||
github.com/jinzhu/gorm v1.9.14
|
||||
github.com/lithammer/dedent v1.1.0
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
|
||||
github.com/robfig/cron v1.2.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/vektah/gqlparser/v2 v2.0.1
|
||||
github.com/wader/gormstore v0.0.0-20200328121358-65a111a20c23
|
||||
gitlab.com/wpetit/goweb v0.0.0-20200707070104-985ce3eba3c2
|
||||
go.opencensus.io v0.22.5 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee // indirect
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
|
||||
golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a // indirect
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
409
go.sum
409
go.sum
@ -9,31 +9,72 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ=
|
||||
cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20200619080035-4bbf7b016032 h1:qTYaLPsLDlvqDkatONsvrisvfvpHaGe3lQqIaX7FFQQ=
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20200619080035-4bbf7b016032/go.mod h1:gkfqGyk7fCj2Z0ngEOCJ3K0FVmqft/8dFV/OnYT1vec=
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20201013085949-5d5592098f13 h1:gZCo9pX3I3A0xVvXkbhdTDBbW44CypCxjiP5LtNV5bo=
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20201013085949-5d5592098f13/go.mod h1:phGAWHUGKNZj044478BvRg0jk049uK1IiX2Amh8krAk=
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20201013111944-d43b43b636ed h1:7dTCXOGxvAulu9vnOjpt2cTgsuxMHX4FH795/JJgo08=
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20201013111944-d43b43b636ed/go.mod h1:phGAWHUGKNZj044478BvRg0jk049uK1IiX2Amh8krAk=
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20201013125038-8d8d1519a52d h1:o+Ppy/MyT5UgbtUYI2J1YqS3iuThxOuNFenYoPgKZKk=
|
||||
forge.cadoles.com/wpetit/goweb-oidc v0.0.0-20201013125038-8d8d1519a52d/go.mod h1:phGAWHUGKNZj044478BvRg0jk049uK1IiX2Amh8krAk=
|
||||
forge.cadoles.com/wpetit/hydra-passwordless v0.0.0-20200908094025-38ac4422dddc h1:9gc/1qizPtK6/iMVlizknWUFii75ntl2xSUV/FSC92Y=
|
||||
forge.cadoles.com/wpetit/hydra-passwordless v0.0.0-20200908094025-38ac4422dddc/go.mod h1:nANHORi270d5jDXjeJ7B3pMgK9R4J0/17p1IIc+rhOk=
|
||||
github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4=
|
||||
github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
|
||||
github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||
github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
|
||||
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw=
|
||||
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
|
||||
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE=
|
||||
github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
@ -42,17 +83,32 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/antonmedv/expr v1.8.8 h1:uVwIkIBNO2yn4vY2u2DQUqXTmv9jEEMCEcHa19G5weY=
|
||||
github.com/antonmedv/expr v1.8.8/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/bmatcuk/doublestar v1.3.0/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/bmatcuk/doublestar v1.3.1 h1:rT8rxDPsavp9G+4ZULzqhhUSaI/OPsTZNG88Z3i0xvY=
|
||||
github.com/bmatcuk/doublestar v1.3.1/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/caarlos0/env/v6 v6.2.2 h1:R0NIFXaB/LhwuGrjnsldzpnVNjFU/U+hTVHt+cq0yDY=
|
||||
github.com/caarlos0/env/v6 v6.2.2/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
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/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cortesi/modd v0.0.0-20200630120222-8983974e5450 h1:3CQigZV4Vgu4XX34CGsQFHbO5re8boAbn0dqUza1LrQ=
|
||||
@ -61,6 +117,8 @@ github.com/cortesi/moddwatch v0.0.0-20200427000745-d26468c93cf0 h1:7tjBO+RH4BoxJ
|
||||
github.com/cortesi/moddwatch v0.0.0-20200427000745-d26468c93cf0/go.mod h1:QYGP4Q0SeEUNSC+dsNSKTmONSd1PpZVYUXIRAzxxpXo=
|
||||
github.com/cortesi/termlog v0.0.0-20190809035425-7871d363854c h1:D5UylL3xKRrrqZKk/NhrOhoQVdCQwuEeyFgTfN9n9O4=
|
||||
github.com/cortesi/termlog v0.0.0-20190809035425-7871d363854c/go.mod h1:gh6GQA3zOsGU4pz+X6ZHqW63KxI/V7KLmBCG9ODJ+l4=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
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/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
@ -68,60 +126,137 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/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/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
|
||||
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/getsentry/sentry-go v0.7.0 h1:MR2yfR4vFfv/2+iBuSnkdQwVg7N9cJzihZ6KJu7srwQ=
|
||||
github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.1.0+incompatible h1:ETj3cggsVIY2Xao5ExCu6YhEh5MD6JTfcBzS37R260w=
|
||||
github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/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/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
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.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e h1:4WfjkTUTsO6siF8ghDQQk6t7x/FPsv3w6MXkc47do7Q=
|
||||
github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||
github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
@ -131,10 +266,21 @@ github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYb
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
|
||||
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
|
||||
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
|
||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
@ -188,8 +334,29 @@ github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
||||
github.com/jackc/puddle v1.1.1 h1:PJAw7H/9hoWC4Kf3J8iNmL1SwA6E8vfsLqBiL+F6CtI=
|
||||
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
|
||||
github.com/jinzhu/gorm v1.9.14 h1:Kg3ShyTPcM6nzVo148fRrcMO6MNKuqtOUwnzqMgVniM=
|
||||
github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
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/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
|
||||
github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
|
||||
github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
|
||||
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@ -200,12 +367,21 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
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.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
@ -217,6 +393,8 @@ github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+v
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
@ -226,26 +404,57 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
|
||||
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
|
||||
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
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/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e h1:BLqxdwZ6j771IpSCRx7s/GJjXHUE00Hmu7/YegCGdzA=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rjeczalik/notify v0.0.0-20181126183243-629144ba06a1 h1:FLWDC+iIP9BWgYKvWKKtOUZux35LIQNAuIzp/63RQJU=
|
||||
github.com/rjeczalik/notify v0.0.0-20181126183243-629144ba06a1/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM=
|
||||
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
@ -253,13 +462,18 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/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/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
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=
|
||||
@ -267,23 +481,53 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
|
||||
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
|
||||
github.com/wader/gormstore v0.0.0-20200328121358-65a111a20c23 h1:gtfR002LWpH9vQ1/GLbWBOTcS92cBi5PAR021lArKF8=
|
||||
github.com/wader/gormstore v0.0.0-20200328121358-65a111a20c23/go.mod h1:2z7nYWeR0xUeFNCmlyH6Qt6qigF+Kl/k4LbQbj6Ksus=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20200418152305-76dea96a46ce h1:B3inZUHFr/FpA3jb+ZeSSHk3FSpB0xkQ0TjePhRokxw=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20200418152305-76dea96a46ce/go.mod h1:Gfv7cBOw1T2XwXMsLm1d9kAjMAdNtLMjPv+yCzRO9qk=
|
||||
@ -293,6 +537,10 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
@ -301,7 +549,9 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -309,16 +559,24 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -328,42 +586,77 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -374,20 +667,42 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a h1:bhXnJ7fn2SiL+C8iOWPfNBJKDTjUByftpPW7b9CX94U=
|
||||
golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
@ -396,13 +711,17 @@ golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/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-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
@ -418,26 +737,67 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191202203127-2b6af5f9ace7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
|
||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
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/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -446,39 +806,88 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
mvdan.cc/sh v2.6.4+incompatible h1:eD6tDeh0pw+/TOTI1BBEryZ02rD2nMcFsgcvde7jffM=
|
||||
mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
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=
|
||||
|
1
internal/.gitignore
vendored
1
internal/.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
/server.go
|
||||
/graph/generated
|
||||
/model/models_gen.go
|
71
internal/auth/auth.go
Normal file
71
internal/auth/auth.go
Normal file
@ -0,0 +1,71 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/model"
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/antonmedv/expr/vm"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnexpectedRuleResult = errors.New("unexpected rule result")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
rules []*vm.Program
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *Service) LoadRules(rawRules ...string) error {
|
||||
rules := make([]*vm.Program, 0, len(rawRules))
|
||||
|
||||
for _, rr := range rawRules {
|
||||
r, err := expr.Compile(rr)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
rules = append(rules, r)
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
s.rules = rules
|
||||
s.mutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Authorize(user *model.User) (bool, error) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
env := map[string]interface{}{
|
||||
"user": user,
|
||||
}
|
||||
|
||||
for _, r := range s.rules {
|
||||
result, err := expr.Run(r, env)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
authorized, ok := result.(bool)
|
||||
if !ok {
|
||||
return false, errors.WithStack(ErrUnexpectedRuleResult)
|
||||
}
|
||||
|
||||
if !authorized {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{
|
||||
rules: make([]*vm.Program, 0),
|
||||
}
|
||||
}
|
20
internal/auth/provider.go
Normal file
20
internal/auth/provider.go
Normal file
@ -0,0 +1,20 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
)
|
||||
|
||||
func ServiceProvider(rules []string) service.Provider {
|
||||
srv := NewService()
|
||||
|
||||
err := srv.LoadRules(rules...)
|
||||
|
||||
return func(ctn *service.Container) (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
}
|
@ -1,21 +1,20 @@
|
||||
package database
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
)
|
||||
|
||||
const ServiceName service.Name = "database"
|
||||
const ServiceName service.Name = "auth"
|
||||
|
||||
// From retrieves the database pool service in the given container.
|
||||
func From(container *service.Container) (*pgxpool.Pool, error) {
|
||||
// From retrieves the auth service in the given container.
|
||||
func From(container *service.Container) (*Service, error) {
|
||||
service, err := container.Service(ServiceName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error while retrieving '%s' service", ServiceName)
|
||||
}
|
||||
|
||||
srv, ok := service.(*pgxpool.Pool)
|
||||
srv, ok := service.(*Service)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("retrieved service is not a valid '%s' service", ServiceName)
|
||||
}
|
||||
@ -23,8 +22,8 @@ func From(container *service.Container) (*pgxpool.Pool, error) {
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Must retrieves the database pool service in the given container or panic otherwise.
|
||||
func Must(container *service.Container) *pgxpool.Pool {
|
||||
// Must retrieves the auth service in the given container or panic otherwise.
|
||||
func Must(container *service.Container) *Service {
|
||||
srv, err := From(container)
|
||||
if err != nil {
|
||||
panic(err)
|
@ -1,99 +0,0 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
|
||||
"forge.cadoles.com/Cadoles/daddy/internal/database"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/cqrs"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
const (
|
||||
createConnectedUserStatement = `
|
||||
INSERT INTO users (email, connected_at) VALUES ($1, now())
|
||||
ON CONFLICT ON CONSTRAINT unique_email
|
||||
DO UPDATE SET connected_at = now();
|
||||
`
|
||||
createUserStatement = `
|
||||
INSERT INTO users (email) VALUES ($1)
|
||||
ON CONFLICT ON CONSTRAINT unique_email
|
||||
DO NOTHING;
|
||||
`
|
||||
)
|
||||
|
||||
type CreateUserCommandRequest struct {
|
||||
Email string
|
||||
Connected bool
|
||||
}
|
||||
|
||||
func HandleCreateUserCommand(ctx context.Context, cmd cqrs.Command) error {
|
||||
req, ok := cmd.Request().(*CreateUserCommandRequest)
|
||||
if !ok {
|
||||
return errors.WithStack(cqrs.ErrUnexpectedRequest)
|
||||
}
|
||||
|
||||
ctn, err := container.From(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
pool, err := database.From(ctn)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
conn, err := pool.Acquire(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer conn.Release()
|
||||
|
||||
if req.Connected {
|
||||
if err := createConnectedUser(ctx, conn, req.Email); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
} else {
|
||||
if err := createUser(ctx, conn, req.Email); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createConnectedUser(ctx context.Context, conn *pgxpool.Conn, email string) error {
|
||||
_, err := conn.Conn().Prepare(
|
||||
ctx, "create_connected_user",
|
||||
createConnectedUserStatement,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if _, err := conn.Exec(ctx, "create_connected_user", email); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createUser(ctx context.Context, conn *pgxpool.Conn, email string) error {
|
||||
_, err := conn.Conn().Prepare(
|
||||
ctx, "create_user",
|
||||
createUserStatement,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if _, err := conn.Exec(ctx, "create_user", email); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -5,6 +5,8 @@ import (
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/lithammer/dedent"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
@ -18,6 +20,10 @@ type Config struct {
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
OIDC OIDCConfig `yaml:"oidc"`
|
||||
Database DatabaseConfig `yaml:"database"`
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
SMTP SMTPConfig `yaml:"smtp"`
|
||||
Task TaskConfig `yaml:"task"`
|
||||
Sentry SentryConfig `ymal:"sentry"`
|
||||
}
|
||||
|
||||
// NewFromFile retrieves the configuration from the given file
|
||||
@ -41,6 +47,7 @@ type HTTPConfig struct {
|
||||
CookieAuthenticationKey string `yaml:"cookieAuthenticationKey" env:"HTTP_COOKIE_AUTHENTICATION_KEY"`
|
||||
CookieEncryptionKey string `yaml:"cookieEncryptionKey" env:"HTTP_COOKIE_ENCRYPTION_KEY"`
|
||||
CookieMaxAge int `yaml:"cookieMaxAge" env:"HTTP_COOKIE_MAX_AGE"`
|
||||
CookieSecure bool `yaml:"cookieSecure" env:"HTTP_COOKIE_SECURE"`
|
||||
TemplateDir string `yaml:"templateDir" env:"HTTP_TEMPLATE_DIR"`
|
||||
PublicDir string `yaml:"publicDir" env:"HTTP_PUBLIC_DIR"`
|
||||
FrontendURL string `yaml:"frontendURL" env:"HTTP_FRONTEND_URL"`
|
||||
@ -55,7 +62,7 @@ type CORSConfig struct {
|
||||
type OIDCConfig struct {
|
||||
ClientID string `yaml:"clientId" env:"OIDC_CLIENT_ID"`
|
||||
ClientSecret string `yaml:"clientSecret" env:"OIDC_CLIENT_SECRET"`
|
||||
IssuerURL string `ymal:"issuerUrl" env:"OIDC_ISSUER_URL"`
|
||||
IssuerURL string `yaml:"issuerUrl" env:"OIDC_ISSUER_URL"`
|
||||
RedirectURL string `yaml:"redirectUrl" env:"OIDC_REDIRECT_URL"`
|
||||
PostLogoutRedirectURL string `yaml:"postLogoutRedirectURL" env:"OIDC_POST_LOGOUT_REDIRECT_URL"`
|
||||
}
|
||||
@ -69,6 +76,41 @@ type DatabaseConfig struct {
|
||||
DSN string `yaml:"dsn" env:"DATABASE_DSN"`
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
Rules []string `yaml:"rules" env:"AUTH_RULES"`
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
Host string `yaml:"host" env:"SMTP_HOST"`
|
||||
Port int `yaml:"port" env:"SMTP_PORT"`
|
||||
UseStartTLS bool `yaml:"useStartTLS" env:"SMTP_USE_START_TLS"`
|
||||
User string `yaml:"user" env:"SMTP_USER"`
|
||||
Password string `yaml:"password" env:"SMTP_PASSWORD"`
|
||||
InsecureSkipVerify bool `yaml:"insecureSkipVerify" env:"SMTP_INSECURE_SKIP_VERIFY"`
|
||||
SenderAddress string `yaml:"senderAddress" env:"SMTP_SENDER_ADDRESS"`
|
||||
SenderName string `yaml:"senderName" env:"SMTP_SENDER_NAME"`
|
||||
}
|
||||
|
||||
type TaskConfig struct {
|
||||
Newsletter NewsletterTaskConfig `yaml:"newsletter"`
|
||||
}
|
||||
|
||||
type NewsletterTaskConfig struct {
|
||||
CronSpec string `yaml:"cronSpec" env:"TASK_NEWSLETTER_CRON_SPEC"`
|
||||
TimeRange time.Duration `yaml:"timeRange" env:"TASK_NEWSLETTER_TIME_RANGE"`
|
||||
BaseURL string `yaml:"baseUrl" env:"TASK_NEWSLETTER_BASE_URL"`
|
||||
ContentTemplate string `yaml:"contentTemplate" env:"TASK_NEWSLETTER_CONTENT_TEMPLATE"`
|
||||
SubjectTemplate string `yaml:"subjectTemplate" env:"TASK_NEWSLETTER_SUBJECT_TEMPLATE"`
|
||||
}
|
||||
|
||||
type SentryConfig struct {
|
||||
DSN string `yaml:"dsn" env:"SENTRY_DSN"`
|
||||
// Server events sampling rate, see https://docs.sentry.io/platforms/go/configuration/options/
|
||||
ServerSampleRate float64 `yaml:"serverSampleRate" env:"SENTRY_SERVER_SAMPLE_RATE"`
|
||||
ServerFlushTimeout time.Duration `yaml:"serverFlushTimeout" env:"SENTRY_SERVER_FLUSH_TIMEOUT"`
|
||||
Environment string `yaml:"environment" env:"SENTRY_ENVIRONMENT"`
|
||||
}
|
||||
|
||||
func NewDumpDefault() *Config {
|
||||
config := NewDefault()
|
||||
return config
|
||||
@ -85,7 +127,7 @@ func NewDefault() *Config {
|
||||
Address: ":8081",
|
||||
CookieAuthenticationKey: "",
|
||||
CookieEncryptionKey: "",
|
||||
CookieMaxAge: int((time.Hour * 1).Seconds()), // 1 hour
|
||||
CookieMaxAge: int((time.Hour * 24).Seconds()), // 24 hours
|
||||
TemplateDir: "template",
|
||||
PublicDir: "public",
|
||||
FrontendURL: "http://localhost:8080",
|
||||
@ -102,6 +144,87 @@ func NewDefault() *Config {
|
||||
Database: DatabaseConfig{
|
||||
DSN: "host=localhost database=daddy",
|
||||
},
|
||||
Auth: AuthConfig{
|
||||
Rules: []string{
|
||||
"user.Email endsWith 'cadoles.com'",
|
||||
},
|
||||
},
|
||||
SMTP: SMTPConfig{
|
||||
Host: "localhost",
|
||||
Port: 2525,
|
||||
User: "",
|
||||
Password: "",
|
||||
SenderAddress: "noreply@localhost",
|
||||
SenderName: "noreply",
|
||||
},
|
||||
Task: TaskConfig{
|
||||
Newsletter: NewsletterTaskConfig{
|
||||
CronSpec: "0 9 * * 1",
|
||||
TimeRange: 24 * 7 * time.Hour,
|
||||
BaseURL: "http://localhost:8080",
|
||||
ContentTemplate: dedent.Dedent(`
|
||||
{{- $root := . -}}
|
||||
Bonjour{{if .User.Name}} {{ .User.Name }}{{end}},
|
||||
|
||||
{{ if not .HasEvents -}}
|
||||
Aucun évènement notoire ces derniers jours.
|
||||
{{ else -}}
|
||||
Voici les évènements de ces derniers jours:
|
||||
{{- end}}
|
||||
|
||||
{{- with .ReadyToVote }}
|
||||
|
||||
Dossiers récemment prêts à voter
|
||||
--------------------------------
|
||||
|
||||
{{range . -}}
|
||||
- "{{ .Title }}" - {{ $root.BaseURL }}/decisions/{{ .ID }} - créé le {{ .CreatedAt.Format "02/01/2006" }}
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
|
||||
{{- with .Voted }}
|
||||
|
||||
Récemment votés
|
||||
---------------
|
||||
|
||||
{{range . -}}
|
||||
- "{{ .Title }}" - {{ $root.BaseURL }}/decisions/{{ .ID }} - voté le {{ .VotedAt.Format "02/01/2006" }}
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
|
||||
{{- with .NewDecisionSupportFiles }}
|
||||
|
||||
Nouveaux dossiers d'aide à la décision
|
||||
--------------------------------------
|
||||
|
||||
{{range . -}}
|
||||
- "{{ .Title }}" - {{ $root.BaseURL }}/decisions/{{ .ID }} - créé le {{ .CreatedAt.Format "02/01/2006" }}
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
|
||||
{{- with .NewWorkgroups}}
|
||||
|
||||
Nouveaux groupes de travail
|
||||
---------------------------
|
||||
|
||||
{{range . -}}
|
||||
- "{{ .Name }}" - {{ $root.BaseURL }}/workgroups/{{ .ID }} - créé le {{ .CreatedAt.Format "02/01/2006" }}
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
|
||||
Bonne semaine,
|
||||
|
||||
Daddy
|
||||
`),
|
||||
SubjectTemplate: `[Daddy] Évènements du {{ .From.Format "02/01/2006" }} au {{ .To.Format "02/01/2006" }}`,
|
||||
},
|
||||
},
|
||||
Sentry: SentryConfig{
|
||||
DSN: "",
|
||||
ServerSampleRate: 1,
|
||||
ServerFlushTimeout: 2 * time.Second,
|
||||
Environment: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,79 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v4"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/middleware/container"
|
||||
)
|
||||
|
||||
type MigrationFunc func(ctx context.Context, tx pgx.Tx) error
|
||||
|
||||
type Migration struct {
|
||||
version string
|
||||
up MigrationFunc
|
||||
down MigrationFunc
|
||||
}
|
||||
|
||||
func (m *Migration) Version() string {
|
||||
return m.version
|
||||
}
|
||||
|
||||
func (m *Migration) Up(ctx context.Context) error {
|
||||
pool, err := m.getDatabaseService(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = WithTx(ctx, pool, func(ctx context.Context, tx pgx.Tx) error {
|
||||
return m.up(ctx, tx)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not apply up migration")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migration) Down(ctx context.Context) error {
|
||||
pool, err := m.getDatabaseService(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = WithTx(ctx, pool, func(ctx context.Context, tx pgx.Tx) error {
|
||||
return m.down(ctx, tx)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not apply down migration")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migration) getDatabaseService(ctx context.Context) (*pgxpool.Pool, error) {
|
||||
ctn, err := container.From(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve service container")
|
||||
}
|
||||
|
||||
pool, err := From(ctn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve database service")
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func NewMigration(version string, up, down MigrationFunc) *Migration {
|
||||
return &Migration{
|
||||
version: version,
|
||||
up: up,
|
||||
down: down,
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/service"
|
||||
)
|
||||
|
||||
func ServiceProvider(dsn string) service.Provider {
|
||||
pool, err := pgxpool.Connect(context.Background(), dsn)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "could not connect to database")
|
||||
}
|
||||
|
||||
return func(ctn *service.Container) (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user