Compare commits
4 Commits
d557ffe6b3
...
0f9fe384a0
Author | SHA1 | Date |
---|---|---|
Arnaud Delcasse | 0f9fe384a0 | |
Arnaud Delcasse | d028256893 | |
Arnaud Delcasse | 0dd4a723be | |
Arnaud Delcasse | f4c2d61dc3 |
33
go.mod
33
go.mod
|
@ -2,22 +2,17 @@ module git.coopgo.io/coopgo-apps/parcoursmob
|
|||
|
||||
go 1.18
|
||||
|
||||
replace git.coopgo.io/coopgo-platform/mobility-accounts => ../../coopgo-platform/mobility-accounts/
|
||||
// replace git.coopgo.io/coopgo-platform/mobility-accounts => ../../coopgo-platform/mobility-accounts/
|
||||
|
||||
replace git.coopgo.io/coopgo-platform/groups-management => ../../coopgo-platform/groups-management/
|
||||
// replace git.coopgo.io/coopgo-platform/groups-management => ../../coopgo-platform/groups-management/
|
||||
|
||||
replace git.coopgo.io/coopgo-platform/fleets => ../../coopgo-platform/fleets/
|
||||
|
||||
replace git.coopgo.io/coopgo-platform/agenda => ../../coopgo-platform/agenda/
|
||||
// replace git.coopgo.io/coopgo-platform/agenda => ../../coopgo-platform/agenda/
|
||||
|
||||
replace git.coopgo.io/coopgo-platform/emailing => ../../coopgo-platform/emailing/
|
||||
// replace git.coopgo.io/coopgo-platform/emailing => ../../coopgo-platform/emailing/
|
||||
|
||||
require (
|
||||
git.coopgo.io/coopgo-platform/agenda v0.0.0-00010101000000-000000000000
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-00010101000000-000000000000
|
||||
git.coopgo.io/coopgo-platform/fleets v0.0.0-00010101000000-000000000000
|
||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-00010101000000-000000000000
|
||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-00010101000000-000000000000
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/fogleman/gg v1.3.0
|
||||
github.com/go-playground/validator/v10 v10.11.0
|
||||
|
@ -34,9 +29,18 @@ require (
|
|||
google.golang.org/protobuf v1.28.1
|
||||
)
|
||||
|
||||
require (
|
||||
git.coopgo.io/coopgo-platform/agenda v0.0.0-20221017030035-4a26fc791c5b
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20221017030337-c71888d90c15
|
||||
git.coopgo.io/coopgo-platform/fleets v0.0.0-20220905052643-be9ee8372fdd
|
||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20221017025751-671dc9a2c544
|
||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20220906130339-b9a32e41bffe
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/go-mail/mail v2.3.1+incompatible // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
|
@ -47,17 +51,26 @@ require (
|
|||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mb0/wkt v0.0.0-20170420051526-a30afd545ee1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.43 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pquerna/cachecontrol v0.1.0 // indirect
|
||||
github.com/rs/xid v1.4.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/afero v1.8.2 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
|
|
34
go.sum
34
go.sum
|
@ -36,6 +36,16 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
|
|||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
git.coopgo.io/coopgo-platform/agenda v0.0.0-20221017030035-4a26fc791c5b h1:7kLW1khfGguZ2aL+QpWFwZmAdEcY1MsUjLdiRufjr2s=
|
||||
git.coopgo.io/coopgo-platform/agenda v0.0.0-20221017030035-4a26fc791c5b/go.mod h1:wqPvfYmzGF2cfXbs8XE1P2j5UYqZwp/La0llkl7dUkc=
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20221017030337-c71888d90c15 h1:+ZI4nGE6mqZ6pc7N/BizheEPRXn6Z84Sj7ikwfP2ZcU=
|
||||
git.coopgo.io/coopgo-platform/emailing v0.0.0-20221017030337-c71888d90c15/go.mod h1:rmbqiHVkONcECOoPlsXlxZnD315Tiz2oRnn1M7646Kg=
|
||||
git.coopgo.io/coopgo-platform/fleets v0.0.0-20220905052643-be9ee8372fdd h1:7k5QMwMm6JQ0S2bNqXEe7Ouh8N9N3yAvcWB2GRcIZLk=
|
||||
git.coopgo.io/coopgo-platform/fleets v0.0.0-20220905052643-be9ee8372fdd/go.mod h1:s9OIFCNcjBAbBzRNHwoCTYV6kAntPG9CpT3GVweGdTY=
|
||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20221017025751-671dc9a2c544 h1:rMLP77uIEequVXXZ0X9G1iK2k+xvW/+58ggwxxI6gqY=
|
||||
git.coopgo.io/coopgo-platform/groups-management v0.0.0-20221017025751-671dc9a2c544/go.mod h1:lozSy6qlIIYhvKKXscZzz28HAtS0qBDUTv5nofLRmYA=
|
||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20220906130339-b9a32e41bffe h1:4OKwfKybR0VsIw2dSM9RtqGWveWPt+JjtiiMIBrg/w0=
|
||||
git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-20220906130339-b9a32e41bffe/go.mod h1:1typNYtO+PQT6KG77vs/PUv0fO60/nbeSGZL2tt1LLg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
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=
|
||||
|
@ -85,6 +95,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/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=
|
||||
|
@ -214,6 +225,8 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
|
|||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
|
@ -222,6 +235,12 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
|||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
|
||||
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
@ -248,14 +267,23 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
|||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mb0/wkt v0.0.0-20170420051526-a30afd545ee1 h1:VCgV+ng800r1/AChRHzHbWCtQI06cPxoZQUljQHTyXc=
|
||||
github.com/mb0/wkt v0.0.0-20170420051526-a30afd545ee1/go.mod h1:IhobDa5AIyiMAsnH/qkytD0NbG0JMOJ2ihQqe1NdXyg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.43 h1:14Q4lwblqTdlAmba05oq5xL0VBLHi06zS4yLnIkz6hI=
|
||||
github.com/minio/minio-go/v7 v7.0.43/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
|
@ -302,12 +330,16 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
|
||||
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
|
@ -542,6 +574,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/cache"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
|
@ -13,10 +13,10 @@ type APIHandler struct {
|
|||
idp *identification.IdentificationProvider
|
||||
config *viper.Viper
|
||||
services *services.ServicesHandler
|
||||
cache *cache.CacheHandler
|
||||
cache cache.CacheHandler
|
||||
}
|
||||
|
||||
func NewAPIHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache *cache.CacheHandler) (*APIHandler, error) {
|
||||
func NewAPIHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache cache.CacheHandler) (*APIHandler, error) {
|
||||
return &APIHandler{
|
||||
idp: idp,
|
||||
config: cfg,
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type FlatMaps []map[string]any
|
||||
|
||||
func (maps FlatMaps) GetHeaders() (res []string) {
|
||||
keys := map[string]bool{}
|
||||
for _, m := range maps {
|
||||
for k, _ := range m {
|
||||
if _, ok := keys[k]; !ok {
|
||||
keys[k] = true
|
||||
res = append(res, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(res)
|
||||
return
|
||||
}
|
||||
|
||||
func (maps FlatMaps) GetValues() (res [][]string) {
|
||||
headers := maps.GetHeaders()
|
||||
for _, m := range maps {
|
||||
line := []string{}
|
||||
for _, k := range headers {
|
||||
if v, ok := m[k]; ok && v != nil {
|
||||
line = append(line, fmt.Sprint(v))
|
||||
} else {
|
||||
line = append(line, "")
|
||||
}
|
||||
}
|
||||
res = append(res, line)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h APIHandler) CacheExport(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
cacheid := vars["cacheid"]
|
||||
|
||||
d, err := h.cache.Get(cacheid)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if data, ok := d.([]any); ok {
|
||||
|
||||
flatmaps := FlatMaps{}
|
||||
//fmt.Println(data)
|
||||
|
||||
for _, v := range data {
|
||||
fmt.Println(v)
|
||||
fm := map[string]any{}
|
||||
flatten("", v.(map[string]any), fm)
|
||||
fmt.Println(fm)
|
||||
flatmaps = append(flatmaps, fm)
|
||||
}
|
||||
|
||||
fmt.Println(flatmaps)
|
||||
|
||||
w.Header().Set("Content-Type", "text/csv")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=export-%s.csv", cacheid))
|
||||
c := csv.NewWriter(w)
|
||||
c.Write(flatmaps.GetHeaders())
|
||||
c.WriteAll(flatmaps.GetValues())
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
||||
}
|
||||
|
||||
func flatten(prefix string, src map[string]any, dest map[string]any) {
|
||||
if len(prefix) > 0 {
|
||||
prefix += "."
|
||||
}
|
||||
for k, v := range src {
|
||||
switch child := v.(type) {
|
||||
case map[string]any:
|
||||
flatten(prefix+k, child, dest)
|
||||
case []any:
|
||||
for i := 0; i < len(child); i++ {
|
||||
dest[prefix+k+"."+strconv.Itoa(i)] = child[i]
|
||||
}
|
||||
default:
|
||||
fmt.Println(prefix+k, " : ", v)
|
||||
dest[prefix+k] = v
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ func (h APIHandler) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
|||
// Extract the ID Token from OAuth2 token.
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
fmt.Println("issue retrieving token")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -40,7 +41,11 @@ func (h APIHandler) OAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
|||
delete(session.Values, "redirect")
|
||||
}
|
||||
|
||||
session.Save(r, w)
|
||||
if err = session.Save(r, w); err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, redirect, http.StatusFound)
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
accounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||
|
@ -19,14 +19,6 @@ import (
|
|||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
type GroupsByName []groupstorage.Group
|
||||
|
||||
func (a GroupsByName) Len() int { return len(a) }
|
||||
func (a GroupsByName) Less(i, j int) bool {
|
||||
return strings.Compare(a[i].Data["name"].(string), a[j].Data["name"].(string)) < 0
|
||||
}
|
||||
func (a GroupsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func (h *ApplicationHandler) Administration(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
request := &groupsmanagement.GetGroupsRequest{
|
||||
|
@ -47,7 +39,7 @@ func (h *ApplicationHandler) Administration(w http.ResponseWriter, r *http.Reque
|
|||
groups = append(groups, g)
|
||||
}
|
||||
|
||||
sort.Sort(GroupsByName(groups))
|
||||
sort.Sort(sorting.GroupsByName(groups))
|
||||
|
||||
h.Renderer.Administration(w, r, groups)
|
||||
}
|
||||
|
@ -229,7 +221,7 @@ func (h *ApplicationHandler) AdministrationGroupInviteAdmin(w http.ResponseWrite
|
|||
}
|
||||
key := base64.RawURLEncoding.EncodeToString(b)
|
||||
|
||||
h.cache.PutWithTTL("onboarding/"+key, onboarding, 72*time.Hour)
|
||||
h.cache.PutWithTTL("onboarding/"+key, onboarding, 168*time.Hour) // 1 week TTL
|
||||
|
||||
data := map[string]any{
|
||||
"group": groupresp.Group.ToStorageType().Data["name"],
|
||||
|
@ -247,6 +239,89 @@ func (h *ApplicationHandler) AdministrationGroupInviteAdmin(w http.ResponseWrite
|
|||
return
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) AdministrationGroupInviteMember(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
groupid := vars["groupid"]
|
||||
|
||||
groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), &groupsmanagement.GetGroupRequest{
|
||||
Id: groupid,
|
||||
Namespace: "parcoursmob_organizations",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
group := groupresp.Group.ToStorageType()
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
accountresp, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &accounts.GetAccountUsernameRequest{
|
||||
Username: r.FormValue("username"),
|
||||
Namespace: "parcoursmob",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
account := accountresp.Account.ToStorageType()
|
||||
account.Data["groups"] = append(account.Data["groups"].([]any), group.ID)
|
||||
|
||||
as, _ := accounts.AccountFromStorageType(&account)
|
||||
|
||||
_, err = h.services.GRPC.MobilityAccounts.UpdateData(
|
||||
context.TODO(),
|
||||
&accounts.UpdateDataRequest{
|
||||
Account: as,
|
||||
},
|
||||
)
|
||||
|
||||
fmt.Println(err)
|
||||
|
||||
data := map[string]any{
|
||||
"group": group.Data["name"],
|
||||
}
|
||||
|
||||
if err := h.emailing.Send("onboarding.existing_member", r.FormValue("username"), data); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/group/settings", http.StatusFound)
|
||||
return
|
||||
} else {
|
||||
// Onboard now administrator
|
||||
onboarding := map[string]any{
|
||||
"username": r.FormValue("username"),
|
||||
"group": group.ID,
|
||||
"admin": false,
|
||||
}
|
||||
|
||||
b := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
key := base64.RawURLEncoding.EncodeToString(b)
|
||||
|
||||
h.cache.PutWithTTL("onboarding/"+key, onboarding, 168*time.Hour) // 1 week TTL
|
||||
|
||||
data := map[string]any{
|
||||
"group": group.Data["name"],
|
||||
"key": key,
|
||||
}
|
||||
|
||||
if err := h.emailing.Send("onboarding.new_member", r.FormValue("username"), data); err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/administration/groups/"+group.ID, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) members() ([]*accounts.Account, error) {
|
||||
resp, err := h.services.GRPC.MobilityAccounts.GetAccounts(context.TODO(), &accounts.GetAccountsRequest{
|
||||
Namespaces: []string{"parcoursmob"},
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/utils/form-validators"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
||||
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||
|
@ -21,12 +22,6 @@ import (
|
|||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type EventsByStartdate []agendastorage.Event
|
||||
|
||||
func (e EventsByStartdate) Len() int { return len(e) }
|
||||
func (e EventsByStartdate) Less(i, j int) bool { return e[i].Startdate.Before(e[j].Startdate) }
|
||||
func (e EventsByStartdate) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
|
||||
type EventsForm struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
|
@ -60,7 +55,7 @@ func (h *ApplicationHandler) AgendaHome(w http.ResponseWriter, r *http.Request)
|
|||
responses = append(responses, e.ToStorageType())
|
||||
}
|
||||
|
||||
sort.Sort(EventsByStartdate(responses))
|
||||
sort.Sort(sorting.EventsByStartdate(responses))
|
||||
|
||||
groupsresp, err := h.services.GRPC.GroupsManagement.GetGroupsBatch(context.TODO(), &groupsmanagement.GetGroupsBatchRequest{
|
||||
Groupids: groupids,
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/cache"
|
||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
||||
"git.coopgo.io/coopgo-platform/emailing"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -14,11 +14,12 @@ type ApplicationHandler struct {
|
|||
config *viper.Viper
|
||||
Renderer *renderer.Renderer
|
||||
services *services.ServicesHandler
|
||||
cache *cache.CacheHandler
|
||||
cache cache.CacheHandler
|
||||
filestorage cache.FileStorage
|
||||
emailing *emailing.Mailer
|
||||
}
|
||||
|
||||
func NewApplicationHandler(cfg *viper.Viper, svc *services.ServicesHandler, cache *cache.CacheHandler, emailing *emailing.Mailer) (*ApplicationHandler, error) {
|
||||
func NewApplicationHandler(cfg *viper.Viper, svc *services.ServicesHandler, cache cache.CacheHandler, filestorage cache.FileStorage, emailing *emailing.Mailer) (*ApplicationHandler, error) {
|
||||
templates_root := cfg.GetString("templates.root")
|
||||
renderer := renderer.NewRenderer(cfg, templates_root)
|
||||
return &ApplicationHandler{
|
||||
|
@ -26,6 +27,7 @@ func NewApplicationHandler(cfg *viper.Viper, svc *services.ServicesHandler, cach
|
|||
Renderer: renderer,
|
||||
services: svc,
|
||||
cache: cache,
|
||||
filestorage: filestorage,
|
||||
emailing: emailing,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -16,10 +18,13 @@ import (
|
|||
formvalidators "git.coopgo.io/coopgo-apps/parcoursmob/utils/form-validators"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
||||
profilepictures "git.coopgo.io/coopgo-apps/parcoursmob/utils/profile-pictures"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
||||
filestorage "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
@ -29,8 +34,9 @@ type BeneficiariesForm struct {
|
|||
FirstName string `json:"first_name" validate:"required"`
|
||||
LastName string `json:"last_name" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Birthdate *time.Time `json:"birthdate"`
|
||||
Birthdate *time.Time `json:"birthdate" validate:"required"`
|
||||
PhoneNumber string `json:"phone_number" validate:"required,phoneNumber"`
|
||||
FileNumber string `json:"file_number" validate:"required"`
|
||||
Address any `json:"address,omitempty"`
|
||||
Gender string `json:"gender"`
|
||||
}
|
||||
|
@ -44,6 +50,8 @@ func (h *ApplicationHandler) BeneficiariesList(w http.ResponseWriter, r *http.Re
|
|||
return
|
||||
}
|
||||
|
||||
sort.Sort(sorting.BeneficiariesByName(accounts))
|
||||
|
||||
cacheid := uuid.NewString()
|
||||
h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour)
|
||||
|
||||
|
@ -115,6 +123,8 @@ func (h *ApplicationHandler) BeneficiaryDisplay(w http.ResponseWriter, r *http.R
|
|||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
documents := h.filestorage.List(filestorage.PREFIX_BENEFICIARIES + "/" + beneficiaryID)
|
||||
|
||||
request := &mobilityaccounts.GetAccountRequest{
|
||||
Id: beneficiaryID,
|
||||
}
|
||||
|
@ -145,7 +155,10 @@ func (h *ApplicationHandler) BeneficiaryDisplay(w http.ResponseWriter, r *http.R
|
|||
bookings = append(bookings, b.ToStorageType())
|
||||
}
|
||||
|
||||
h.Renderer.BeneficiaryDisplay(w, r, resp.Account.ToStorageType(), bookings)
|
||||
beneficiaries_file_types := h.config.GetStringSlice("modules.beneficiaries.documents_types")
|
||||
file_types_map := h.config.GetStringMapString("storage.files.file_types")
|
||||
|
||||
h.Renderer.BeneficiaryDisplay(w, r, resp.Account.ToStorageType(), bookings, beneficiaries_file_types, file_types_map, documents)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) BeneficiaryUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -239,6 +252,63 @@ func (h *ApplicationHandler) BeneficiaryPicture(w http.ResponseWriter, r *http.R
|
|||
}
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) BeneficiaryDocuments(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
|
||||
//r.ParseForm()
|
||||
r.ParseMultipartForm(10 * 1024 * 1024)
|
||||
|
||||
document_type := r.FormValue("type")
|
||||
document_name := r.FormValue("name")
|
||||
|
||||
file, header, err := r.FormFile("file-upload")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fileid := uuid.NewString()
|
||||
|
||||
metadata := map[string]string{
|
||||
"type": document_type,
|
||||
"name": document_name,
|
||||
}
|
||||
|
||||
if err := h.filestorage.Put(file, filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s_%s", beneficiaryID, fileid, header.Filename), header.Size, metadata); err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
||||
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) BeneficiaryDocumentDownload(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
beneficiaryID := vars["beneficiaryid"]
|
||||
document := vars["document"]
|
||||
|
||||
file, info, err := h.filestorage.Get(filestorage.PREFIX_BENEFICIARIES, fmt.Sprintf("%s/%s", beneficiaryID, document))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", info.ContentType)
|
||||
if _, err = io.Copy(w, file); err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/app/beneficiaries/%s", beneficiaryID), http.StatusFound)
|
||||
|
||||
}
|
||||
|
||||
func filterAccount(r *http.Request, a *mobilityaccounts.Account) bool {
|
||||
searchFilter, ok := r.URL.Query()["search"]
|
||||
|
||||
|
@ -252,8 +322,8 @@ func filterAccount(r *http.Request, a *mobilityaccounts.Account) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]any, error) {
|
||||
var accounts = []any{}
|
||||
func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]mobilityaccountsstorage.Account, error) {
|
||||
var accounts = []mobilityaccountsstorage.Account{}
|
||||
g := r.Context().Value(identification.GroupKey)
|
||||
if g == nil {
|
||||
return accounts, errors.New("no group provided")
|
||||
|
@ -301,6 +371,7 @@ func parseBeneficiariesForm(r *http.Request) (map[string]any, error) {
|
|||
Email: r.PostFormValue("email"),
|
||||
Birthdate: date,
|
||||
PhoneNumber: r.PostFormValue("phone_number"),
|
||||
FileNumber: r.PostFormValue("file_number"),
|
||||
Gender: r.PostFormValue("gender"),
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
||||
agenda "git.coopgo.io/coopgo-platform/agenda/grpcapi"
|
||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
|
@ -69,7 +70,7 @@ func (h *ApplicationHandler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
|||
events = append(events, e.ToStorageType())
|
||||
}
|
||||
|
||||
sort.Sort(EventsByStartdate(events))
|
||||
sort.Sort(sorting.EventsByStartdate(events))
|
||||
|
||||
h.Renderer.Dashboard(w, r, accounts, count, count_members, events)
|
||||
|
||||
|
|
|
@ -81,8 +81,8 @@ func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Reque
|
|||
journeys, err = session.Journeys(context.Background(), request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
// w.WriteHeader(http.StatusBadRequest)
|
||||
// return
|
||||
}
|
||||
|
||||
//CARPOOL
|
||||
|
@ -93,8 +93,6 @@ func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Reque
|
|||
// departuredatetime.Format("2006-01-02"), departuredatetime.Add(24*time.Hour).Format("2006-01-02"))
|
||||
carpoolrequest := "https://api.rdex.ridygo.fr/journeys.json"
|
||||
|
||||
fmt.Println(carpoolrequest)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", carpoolrequest, nil)
|
||||
if err != nil {
|
||||
|
@ -113,11 +111,21 @@ func (h *ApplicationHandler) JourneysSearch(w http.ResponseWriter, r *http.Reque
|
|||
fmt.Println(err)
|
||||
}
|
||||
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
err = json.NewDecoder(resp.Body).Decode(&carpoolresults)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
if carpoolresults == nil {
|
||||
carpoolresults = []any{}
|
||||
}
|
||||
} else {
|
||||
carpoolresults = []any{}
|
||||
}
|
||||
|
||||
fmt.Println(carpoolresults)
|
||||
|
||||
// Vehicles
|
||||
|
||||
vehiclerequest := &fleets.GetVehiclesRequest{
|
||||
|
|
|
@ -29,17 +29,12 @@ func (h *ApplicationHandler) SupportSend(w http.ResponseWriter, r *http.Request)
|
|||
"user": current_user_claims["email"],
|
||||
}
|
||||
|
||||
if err := h.emailing.Send("onboarding.Support_email", "support@parcoursmob.fr", data); err != nil {
|
||||
if err := h.emailing.Send("support.request", "support@parcoursmob.fr", data); err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("error")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
fmt.Println("success")
|
||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("comment page!")
|
||||
fmt.Println(comment)
|
||||
fmt.Print(current_user_claims["email"])
|
||||
h.Renderer.SupportSend(w, r, comment, current_user_claims)
|
||||
}
|
||||
|
|
|
@ -5,10 +5,13 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||
|
@ -29,15 +32,58 @@ func (h *ApplicationHandler) VehiclesManagementOverview(w http.ResponseWriter, r
|
|||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
var vehicles = []any{}
|
||||
vehicles := []fleetsstorage.Vehicle{}
|
||||
bookings := []fleetsstorage.Booking{}
|
||||
vehicles_map := map[string]fleetsstorage.Vehicle{}
|
||||
|
||||
for _, vehicle := range resp.Vehicles {
|
||||
if filterVehicle(r, vehicle) {
|
||||
v := vehicle.ToStorageType()
|
||||
vehicles = append(vehicles, v)
|
||||
vehicles_map[v.ID] = v
|
||||
for _, b := range v.Bookings {
|
||||
if b.Status() != fleetsstorage.StatusOld {
|
||||
bookings = append(bookings, b)
|
||||
}
|
||||
}
|
||||
h.Renderer.VehiclesManagementOverview(w, r, vehicles)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sorting.VehiclesByLicencePlate(vehicles))
|
||||
sort.Sort(sorting.BookingsByStartdate(bookings))
|
||||
|
||||
h.Renderer.VehiclesManagementOverview(w, r, vehicles, vehicles_map, bookings)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request) {
|
||||
//Get Vehicles
|
||||
request := &fleets.GetVehiclesRequest{
|
||||
Namespaces: []string{"parcoursmob"},
|
||||
}
|
||||
resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
bookings := []fleetsstorage.Booking{}
|
||||
vehicles_map := map[string]fleetsstorage.Vehicle{}
|
||||
|
||||
for _, vehicle := range resp.Vehicles {
|
||||
if filterVehicle(r, vehicle) {
|
||||
v := vehicle.ToStorageType()
|
||||
vehicles_map[v.ID] = v
|
||||
bookings = append(bookings, v.Bookings...)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sorting.BookingsByStartdate(bookings))
|
||||
|
||||
cacheid := uuid.NewString()
|
||||
h.cache.PutWithTTL(cacheid, bookings, 1*time.Hour)
|
||||
|
||||
h.Renderer.VehiclesManagementBookingsList(w, r, vehicles_map, bookings, cacheid)
|
||||
}
|
||||
|
||||
func (h *ApplicationHandler) VehiclesFleetAdd(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -4,9 +4,11 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/sorting"
|
||||
fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi"
|
||||
groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi"
|
||||
"git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
|
@ -72,6 +74,8 @@ func (h ApplicationHandler) VehiclesSearch(w http.ResponseWriter, r *http.Reques
|
|||
return
|
||||
}
|
||||
|
||||
sort.Sort(sorting.BeneficiariesByName(accounts))
|
||||
|
||||
h.Renderer.VehiclesSearch(w, r, accounts, searched, vehicles, beneficiary, r.FormValue("startdate"), r.FormValue("enddate"))
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@ package auth
|
|||
import (
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/cache"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
||||
"git.coopgo.io/coopgo-platform/emailing"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
|
@ -13,10 +14,11 @@ type AuthHandler struct {
|
|||
config *viper.Viper
|
||||
services *services.ServicesHandler
|
||||
Renderer *renderer.Renderer
|
||||
cache *cache.CacheHandler
|
||||
cache cache.CacheHandler
|
||||
emailing *emailing.Mailer
|
||||
}
|
||||
|
||||
func NewAuthHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache *cache.CacheHandler) (*AuthHandler, error) {
|
||||
func NewAuthHandler(cfg *viper.Viper, idp *identification.IdentificationProvider, svc *services.ServicesHandler, cache cache.CacheHandler, emailing *emailing.Mailer) (*AuthHandler, error) {
|
||||
templates_root := cfg.GetString("templates.root")
|
||||
renderer := renderer.NewRenderer(cfg, templates_root)
|
||||
return &AuthHandler{
|
||||
|
@ -25,5 +27,6 @@ func NewAuthHandler(cfg *viper.Viper, idp *identification.IdentificationProvider
|
|||
services: svc,
|
||||
Renderer: renderer,
|
||||
cache: cache,
|
||||
emailing: emailing,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi"
|
||||
)
|
||||
|
||||
func (h *AuthHandler) LostPasswordInit(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
email := r.FormValue("email")
|
||||
if email != "" {
|
||||
account, err := h.services.GRPC.MobilityAccounts.GetAccountUsername(context.TODO(), &grpcapi.GetAccountUsernameRequest{
|
||||
Username: email,
|
||||
Namespace: "parcoursmob",
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
b := make([]byte, 16)
|
||||
if _, err := io.ReadFull(rand.Reader, b); err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
key := base64.RawURLEncoding.EncodeToString(b)
|
||||
|
||||
passwordretrieval := map[string]any{
|
||||
"username": email,
|
||||
"account_id": account.Account.Id,
|
||||
"key": key,
|
||||
}
|
||||
|
||||
h.cache.PutWithTTL("retrieve-password/"+key, passwordretrieval, 72*time.Hour)
|
||||
|
||||
if err := h.emailing.Send("auth.retrieve_password", email, passwordretrieval); err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
||||
}
|
||||
}
|
||||
h.Renderer.LostPasswordInit(w, r)
|
||||
|
||||
}
|
||||
|
||||
func (h *AuthHandler) LostPasswordRecover(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
|
||||
key := r.FormValue("key")
|
||||
recover, err := h.cache.Get("retrieve-password/" + key)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
h.Renderer.LostPasswordRecoverKO(w, r, key)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
newpassword := r.FormValue("password")
|
||||
if newpassword == "" {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("Password is empty"))
|
||||
return
|
||||
}
|
||||
|
||||
_, err := h.services.GRPC.MobilityAccounts.ChangePassword(context.TODO(), &grpcapi.ChangePasswordRequest{
|
||||
Id: recover.(map[string]any)["account_id"].(string),
|
||||
Password: newpassword,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
err = h.cache.Delete("retrieve-password/" + key)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
||||
|
||||
}
|
||||
h.Renderer.LostPasswordRecover(w, r, recover)
|
||||
}
|
|
@ -16,7 +16,7 @@ func (h *AuthHandler) Onboarding(w http.ResponseWriter, r *http.Request) {
|
|||
onboarding, err := h.cache.Get("onboarding/" + key)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
h.Renderer.AuthOnboardingKO(w, r, key)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,12 @@ func (h *AuthHandler) Onboarding(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.cache.Delete("onboarding/" + key)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/app/", http.StatusFound)
|
||||
}
|
||||
|
||||
|
|
21
main.go
21
main.go
|
@ -11,8 +11,8 @@ import (
|
|||
"git.coopgo.io/coopgo-apps/parcoursmob/handlers/auth"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/renderer"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/cache"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/identification"
|
||||
cache "git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
|
@ -33,12 +33,14 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
idp, err := identification.NewIdentificationProvider(cfg, svc)
|
||||
kv, err := cache.NewKVHandler(cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cache, err := cache.NewCacheHandler(cfg)
|
||||
filestorage, err := cache.NewFileStorage(cfg)
|
||||
|
||||
idp, err := identification.NewIdentificationProvider(cfg, svc, kv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -48,9 +50,9 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
apiHandler, _ := api.NewAPIHandler(cfg, idp, svc, cache)
|
||||
applicationHandler, _ := application.NewApplicationHandler(cfg, svc, cache, emailing)
|
||||
authHandler, _ := auth.NewAuthHandler(cfg, idp, svc, cache)
|
||||
apiHandler, _ := api.NewAPIHandler(cfg, idp, svc, kv)
|
||||
applicationHandler, _ := application.NewApplicationHandler(cfg, svc, kv, filestorage, emailing)
|
||||
authHandler, _ := auth.NewAuthHandler(cfg, idp, svc, kv, emailing)
|
||||
|
||||
fmt.Println("Running", service_name, ":")
|
||||
|
||||
|
@ -59,6 +61,8 @@ func main() {
|
|||
r.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir(templates_public_dir))))
|
||||
|
||||
r.HandleFunc("/auth/onboarding", authHandler.Onboarding)
|
||||
r.HandleFunc("/auth/lost-password", authHandler.LostPasswordInit)
|
||||
r.HandleFunc("/auth/lost-password/recover", authHandler.LostPasswordRecover)
|
||||
r.HandleFunc("/auth/groups/", authHandler.Groups)
|
||||
r.HandleFunc("/auth/groups/switch", authHandler.GroupSwitch)
|
||||
r.HandleFunc("/", redirectApp)
|
||||
|
@ -67,6 +71,7 @@ func main() {
|
|||
api_router.HandleFunc("/", apiHandler.NotFound)
|
||||
api_router.HandleFunc("/geo/autocomplete", apiHandler.GeoAutocomplete)
|
||||
api_router.HandleFunc("/cache/{cacheid}", apiHandler.GetCache)
|
||||
api_router.HandleFunc("/cache/{cacheid}/export", apiHandler.CacheExport)
|
||||
api_router.HandleFunc("/oauth2/callback", apiHandler.OAuth2Callback)
|
||||
|
||||
application := r.PathPrefix("/app").Subrouter()
|
||||
|
@ -75,6 +80,8 @@ func main() {
|
|||
application.HandleFunc("/beneficiaries/create", applicationHandler.BeneficiaryCreate)
|
||||
application.HandleFunc("/beneficiaries/{beneficiaryid}", applicationHandler.BeneficiaryDisplay)
|
||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/update", applicationHandler.BeneficiaryUpdate)
|
||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/documents", applicationHandler.BeneficiaryDocuments)
|
||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/documents/{document}", applicationHandler.BeneficiaryDocumentDownload)
|
||||
application.HandleFunc("/beneficiaries/{beneficiaryid}/picture", applicationHandler.BeneficiaryPicture)
|
||||
application.HandleFunc("/members/{beneficiaryid}/picture", applicationHandler.BeneficiaryPicture)
|
||||
application.HandleFunc("/journeys/", applicationHandler.JourneysSearch)
|
||||
|
@ -86,6 +93,7 @@ func main() {
|
|||
application.HandleFunc("/vehicles-management/fleet/add", applicationHandler.VehiclesFleetAdd)
|
||||
application.HandleFunc("/vehicles-management/fleet/{vehicleid}", applicationHandler.VehiclesFleetDisplay)
|
||||
application.HandleFunc("/vehicles-management/fleet/{vehicleid}/update", applicationHandler.VehiclesFleetUpdate)
|
||||
application.HandleFunc("/vehicles-management/bookings/", applicationHandler.VehiclesManagementBookingsList)
|
||||
application.HandleFunc("/vehicles-management/bookings/{bookingid}", applicationHandler.VehicleManagementBookingDisplay)
|
||||
application.HandleFunc("/agenda/", applicationHandler.AgendaHome)
|
||||
application.HandleFunc("/agenda/create-event", applicationHandler.AgendaCreateEvent)
|
||||
|
@ -109,6 +117,7 @@ func main() {
|
|||
appAdmin.HandleFunc("/groups/", applicationHandler.AdministrationCreateGroup)
|
||||
appAdmin.HandleFunc("/groups/{groupid}", applicationHandler.AdministrationGroupDisplay)
|
||||
appAdmin.HandleFunc("/groups/{groupid}/invite-admin", applicationHandler.AdministrationGroupInviteAdmin)
|
||||
appAdmin.HandleFunc("/groups/{groupid}/invite-member", applicationHandler.AdministrationGroupInviteMember)
|
||||
//TODO Secure with Middleware checking for modules
|
||||
|
||||
fmt.Println("-> HTTP server listening on", address)
|
||||
|
|
|
@ -13,7 +13,7 @@ func (renderer *Renderer) AuthGroups(w http.ResponseWriter, r *http.Request, gro
|
|||
}
|
||||
|
||||
func (renderer *Renderer) AuthOnboarding(w http.ResponseWriter, r *http.Request, key string, onboarding any) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.auth.onboarding.files")
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.auth.onboarding.form.files")
|
||||
state := NewState(r, renderer.ThemeConfig, "")
|
||||
state.ViewState = map[string]any{
|
||||
"key": key,
|
||||
|
@ -22,3 +22,41 @@ func (renderer *Renderer) AuthOnboarding(w http.ResponseWriter, r *http.Request,
|
|||
|
||||
renderer.RenderNoLayout("onboarding", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) AuthOnboardingKO(w http.ResponseWriter, r *http.Request, key string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.auth.onboarding.ko.files")
|
||||
state := NewState(r, renderer.ThemeConfig, "")
|
||||
state.ViewState = map[string]any{
|
||||
"key": key,
|
||||
}
|
||||
|
||||
renderer.RenderNoLayout("onboarding", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) LostPasswordInit(w http.ResponseWriter, r *http.Request) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.auth.lost_password.init.files")
|
||||
state := NewState(r, renderer.ThemeConfig, "")
|
||||
state.ViewState = map[string]any{}
|
||||
|
||||
renderer.RenderNoLayout("lost_password_init", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) LostPasswordRecover(w http.ResponseWriter, r *http.Request, recover any) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.auth.lost_password.recover.form.files")
|
||||
state := NewState(r, renderer.ThemeConfig, "")
|
||||
state.ViewState = map[string]any{
|
||||
"recover": recover,
|
||||
}
|
||||
|
||||
renderer.RenderNoLayout("lost_password_recover", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) LostPasswordRecoverKO(w http.ResponseWriter, r *http.Request, key string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.auth.lost_password.recover.ko.files")
|
||||
state := NewState(r, renderer.ThemeConfig, "")
|
||||
state.ViewState = map[string]any{
|
||||
"key": key,
|
||||
}
|
||||
|
||||
renderer.RenderNoLayout("lost_password_recover_ko", w, r, files, state)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
)
|
||||
|
||||
const beneficiariesMenu = "beneficiaries"
|
||||
|
@ -11,7 +13,7 @@ const beneficiariesMenu = "beneficiaries"
|
|||
type BeneficiariesListState struct {
|
||||
Count int `json:"count"`
|
||||
CacheId string `json:"cache_id"`
|
||||
Beneficiaries []any `json:"beneficiaries"`
|
||||
Beneficiaries []mobilityaccountsstorage.Account `json:"beneficiaries"`
|
||||
}
|
||||
|
||||
func (s BeneficiariesListState) JSON() template.JS {
|
||||
|
@ -26,7 +28,7 @@ func (s BeneficiariesListState) JSONWithLimits(a int, b int) template.JS {
|
|||
return s.JSON()
|
||||
}
|
||||
|
||||
func (renderer *Renderer) BeneficiariesList(w http.ResponseWriter, r *http.Request, accounts []any, cacheid string) {
|
||||
func (renderer *Renderer) BeneficiariesList(w http.ResponseWriter, r *http.Request, accounts []mobilityaccountsstorage.Account, cacheid string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.list.files")
|
||||
|
||||
state := NewState(r, renderer.ThemeConfig, beneficiariesMenu)
|
||||
|
@ -50,12 +52,15 @@ type BeneficiariesDisplayState struct {
|
|||
Beneficiary any
|
||||
}
|
||||
|
||||
func (renderer *Renderer) BeneficiaryDisplay(w http.ResponseWriter, r *http.Request, beneficiary any, bookings []any) {
|
||||
func (renderer *Renderer) BeneficiaryDisplay(w http.ResponseWriter, r *http.Request, beneficiary any, bookings []any, beneficiaries_file_types []string, file_types_map map[string]string, documents any) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.beneficiaries.display.files")
|
||||
state := NewState(r, renderer.ThemeConfig, beneficiariesMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"beneficiary": beneficiary,
|
||||
"bookings": bookings,
|
||||
"beneficiaries_file_types": beneficiaries_file_types,
|
||||
"file_types_map": file_types_map,
|
||||
"documents": documents,
|
||||
}
|
||||
|
||||
renderer.Render("beneficiaries_display", w, r, files, state)
|
||||
|
|
|
@ -5,11 +5,12 @@ import (
|
|||
)
|
||||
|
||||
const commentMenu = "comment"
|
||||
const commentsend = "sendComment"
|
||||
|
||||
//const commentsend = "sendComment"
|
||||
|
||||
func (renderer *Renderer) SupportSend(w http.ResponseWriter, r *http.Request, comment any, admins any) {
|
||||
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.support.search.files")
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.support.request.files")
|
||||
state := NewState(r, renderer.ThemeConfig, commentMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"comment": comment,
|
||||
|
|
|
@ -1,14 +1,32 @@
|
|||
package renderer
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||
)
|
||||
|
||||
const vehiclesmanagementMenu = "vehicles_management"
|
||||
|
||||
func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request, vehicles []any) {
|
||||
func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request, vehicles []fleetsstorage.Vehicle, vehicles_map map[string]fleetsstorage.Vehicle, bookings []fleetsstorage.Booking) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.overview.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"vehicles": vehicles,
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehicles_map,
|
||||
}
|
||||
|
||||
renderer.Render("fleet overview", w, r, files, state)
|
||||
}
|
||||
|
||||
func (renderer *Renderer) VehiclesManagementBookingsList(w http.ResponseWriter, r *http.Request, vehicles_map map[string]fleetsstorage.Vehicle, bookings []fleetsstorage.Booking, cacheid string) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.bookings_list.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu)
|
||||
state.ViewState = map[string]any{
|
||||
"bookings": bookings,
|
||||
"vehicles_map": vehicles_map,
|
||||
"cacheid": cacheid,
|
||||
}
|
||||
|
||||
renderer.Render("fleet overview", w, r, files, state)
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package renderer
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
)
|
||||
|
||||
const vehiclesMenu = "vehicles"
|
||||
|
||||
func (renderer *Renderer) VehiclesSearch(w http.ResponseWriter, r *http.Request, beneficiaries []any, searched bool, vehicles []any, beneficiary any, startdate any, enddate any) {
|
||||
func (renderer *Renderer) VehiclesSearch(w http.ResponseWriter, r *http.Request, beneficiaries []mobilityaccountsstorage.Account, searched bool, vehicles []any, beneficiary any, startdate any, enddate any) {
|
||||
files := renderer.ThemeConfig.GetStringSlice("views.vehicles.search.files")
|
||||
state := NewState(r, renderer.ThemeConfig, vehiclesMenu)
|
||||
viewstate := map[string]any{
|
||||
|
|
|
@ -52,6 +52,10 @@ views:
|
|||
- web/layouts/vehicles_management/_partials/bookings-list.html
|
||||
- web/layouts/vehicles_management/_partials/vehicles-list.html
|
||||
- web/layouts/vehicles_management/overview.html
|
||||
bookings_list:
|
||||
files:
|
||||
- web/layouts/vehicles_management/_partials/bookings-list.html
|
||||
- web/layouts/vehicles_management/bookings-list.html
|
||||
fleet_add:
|
||||
files:
|
||||
- web/layouts/_partials/address_autocomplete.html
|
||||
|
@ -94,7 +98,7 @@ views:
|
|||
- web/layouts/journeys/_partials/journeys-public-transit.html
|
||||
- web/layouts/journeys/search.html
|
||||
support:
|
||||
search:
|
||||
request:
|
||||
files:
|
||||
- web/layouts/support/support.html
|
||||
administration:
|
||||
|
@ -119,9 +123,24 @@ views:
|
|||
groups:
|
||||
files:
|
||||
- web/layouts/auth/groups.html
|
||||
lost_password:
|
||||
init:
|
||||
files:
|
||||
- web/layouts/auth/lost-password-init.html
|
||||
recover:
|
||||
form:
|
||||
files:
|
||||
- web/layouts/auth/lost-password-recover.html
|
||||
ko:
|
||||
files:
|
||||
- web/layouts/auth/lost-password-recover-ko.html
|
||||
onboarding:
|
||||
form:
|
||||
files:
|
||||
- web/layouts/auth/onboarding.html
|
||||
ko:
|
||||
files:
|
||||
- web/layouts/auth/onboarding-ko.html
|
||||
|
||||
icons:
|
||||
svg:
|
||||
|
@ -131,12 +150,16 @@ icons:
|
|||
hero:outline/calendar: <svg xmlns="http://www.w3.org/2000/svg" class="%s" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
hero:outline/chevron-right: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="%s"><path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /></svg>
|
||||
hero:outline/cog: <svg xmlns="http://www.w3.org/2000/svg" class="%s" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
||||
hero:outline/document-arrow-down: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="%s"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m.75 12l3 3m0 0l3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
|
||||
hero:outline/document-text: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="%s"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
|
||||
hero:outline/folder-plus: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="%s"><path stroke-linecap="round" stroke-linejoin="round" d="M12 10.5v6m3-3H9m4.06-7.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" /></svg>
|
||||
hero:outline/home: <svg xmlns="http://www.w3.org/2000/svg" class="%s" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /></svg>
|
||||
hero:outline/map: <svg xmlns="http://www.w3.org/2000/svg" class="%s" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7" /></svg>
|
||||
hero:outline/office-building: <svg xmlns="http://www.w3.org/2000/svg" class="%s" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" /></svg>
|
||||
hero:outline/paper-clip: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="%s"><path stroke-linecap="round" stroke-linejoin="round" d="M18.375 12.739l-7.693 7.693a4.5 4.5 0 01-6.364-6.364l10.94-10.94A3 3 0 1119.5 7.372L8.552 18.32m.009-.01l-.01.01m5.699-9.941l-7.81 7.81a1.5 1.5 0 002.112 2.13" /></svg>
|
||||
hero:outline/plus-circle: <svg xmlns="http://www.w3.org/2000/svg" class="%s" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
hero:outline/shield-check: <svg xmlns="http://www.w3.org/2000/svg" class="%s" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
hero:outline/table-cells: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="%s"><path stroke-linecap="round" stroke-linejoin="round" d="M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 01-1.125-1.125M3.375 19.5h7.5c.621 0 1.125-.504 1.125-1.125m-9.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-7.5A1.125 1.125 0 0112 18.375m9.75-12.75c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125m19.5 0v1.5c0 .621-.504 1.125-1.125 1.125M2.25 5.625v1.5c0 .621.504 1.125 1.125 1.125m0 0h17.25m-17.25 0h7.5c.621 0 1.125.504 1.125 1.125M3.375 8.25c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m17.25-3.75h-7.5c-.621 0-1.125.504-1.125 1.125m8.625-1.125c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M12 10.875v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125M13.125 12h7.5m-7.5 0c-.621 0-1.125.504-1.125 1.125M20.625 12c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h7.5M12 14.625v-1.5m0 1.5c0 .621-.504 1.125-1.125 1.125M12 14.625c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m0 1.5v-1.5m0 0c0-.621.504-1.125 1.125-1.125m0 0h7.5" /></svg>
|
||||
hero:outline/user-group: <svg xmlns="http://www.w3.org/2000/svg" class="%s" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
hero:outline/x: <svg class="%s text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
hero:solid/chevron-right: <svg class="%s text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" /></svg>
|
||||
|
@ -170,8 +193,15 @@ emails:
|
|||
files:
|
||||
- emails/layout.html
|
||||
- emails/onboarding/existing-member.html
|
||||
support_email:
|
||||
subject: PARCOURMOB - Vous avez reçu un commentaire
|
||||
auth:
|
||||
retrieve_password:
|
||||
subject: PAROURSMOB - RĂ©initialisez votre mot de passe
|
||||
files:
|
||||
- emails/layout.html
|
||||
- emails/auth/retrieve-password.html
|
||||
support:
|
||||
request:
|
||||
subject: PARCOURMOB - Demande de support
|
||||
files:
|
||||
- emails/layout.html
|
||||
- emails/onboarding/support_emailing.html
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{{define "content"}}
|
||||
<p>Bonjour,</p>
|
||||
<p>Vous avez demandé à réinitialiser votre mot de passe pour <b>{{.username}}</b></p>
|
||||
<p>Pour créer votre nouveau mot de passe, cliquez sur le lien suivant : <a href="http://localhost:9000/auth/lost-password/recover?key={{.key}}">http://localhost:9000/auth/lost-password/recover?key={{.key}}</a></p>
|
||||
{{end}}
|
|
@ -1,5 +1,5 @@
|
|||
{{define "content"}}
|
||||
<p>Vous avez été ajouté comme administrateur de l'organisation {{.group}} sur PARCOURSMOB.</p>
|
||||
<p>Vous devez créer votre compte pour y accéder.</p>
|
||||
<p>Pour créer votre compte PARCOURSMOB, cliquez sur : <a href="http://localhost:9000/auth/onboarding?key={{.key}}">http://localhost:9000/onboarding?key={{.key}}</a></p>
|
||||
<p>Pour créer votre compte PARCOURSMOB, cliquez sur : <a href="http://localhost:9000/auth/onboarding?key={{.key}}">http://localhost:9000/auth/onboarding?key={{.key}}</a></p>
|
||||
{{end}}
|
|
@ -1,5 +1,5 @@
|
|||
{{define "content"}}
|
||||
<p>Vous avez été ajouté à l'organisation {{.group}} sur PARCOURSMOB.</p>
|
||||
<p>Vous devez créer votre compte pour y accéder.</p>
|
||||
<p>Pour créer votre compte PARCOURSMOB, cliquez sur : <a href="http://localhost:9000/auth/onboarding?key={{.key}}">http://localhost:9000/onboarding?key={{.key}}</a></p>
|
||||
<p>Pour créer votre compte PARCOURSMOB, cliquez sur : <a href="http://localhost:9000/auth/onboarding?key={{.key}}">http://localhost:9000/auth/onboarding?key={{.key}}</a></p>
|
||||
{{end}}
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<div class="col-span-6 relative" x-data="{
|
||||
input: {{if .Address}}'{{.Address.Properties.label}}'{{else}}null{{end}},
|
||||
address: null,
|
||||
address: {{if .Address}}JSON.stringify({{printf "%s" .Address.MarshalJSON}}){{else}}null{{end}},
|
||||
addressObject: {{if .Address}}{{printf "%s" .Address.MarshalJSON}}{{else}}null{{end}},
|
||||
responselength: 0,
|
||||
async autocomplete() {
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
{{define "documents_list"}}
|
||||
<div class="-mx-4 mt-10 ring-1 ring-gray-300 sm:-mx-6 md:mx-0 md:rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Type</th>
|
||||
<th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">Nom du document</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="relative py-4 pl-4 sm:pl-6 pr-3 text-sm">
|
||||
<div class="font-medium text-gray-900">
|
||||
<span class="bg-co-blue text-xs text-white rounded-xl p-1 mr-2">{{index $.ViewState.file_types_map .Metadata.Type}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-3.5 text-sm text-gray-500 lg:table-cell">{{.Metadata.Name}}</td>
|
||||
<td class="relative py-3.5 pl-3 pr-4 sm:pr-6 text-right text-sm font-medium">
|
||||
<button type="button" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium leading-4 text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-30">Télécharger<span class="sr-only"> le fichier</span></button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- More plans... -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
|
@ -42,6 +42,20 @@
|
|||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{{template "groups_members" .}}
|
||||
<div class="px-2 py-4">
|
||||
<form class="flex" method="POST" action="/app/administration/groups/{{.ViewState.group.ID}}/invite-member">
|
||||
<div class="pr-2 flex-1">
|
||||
<input type="text" name="username" id="username"
|
||||
class="mt-1 border-gray-300 focus:ring-co-blue focus:border-co-blue block w-full shadow-sm sm:text-sm rounded-2xl"
|
||||
placeholder="Email">
|
||||
</div>
|
||||
<button class="px-1 py-1 border border-transparent text-sm font-medium rounded-2xl shadow-sm text-white bg-co-blue hover:bg-co-blue focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-co-blue">Ajouter un membre</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
{{define "main"}}
|
||||
<html class="h-full bg-gray-50">
|
||||
<head>
|
||||
<title>PARCOURSMOB - Identification</title>
|
||||
<link rel="stylesheet" href="http://localhost:9000/public/css/main.css" />
|
||||
</head>
|
||||
<body class="h-full">
|
||||
<form method="post">
|
||||
|
||||
|
||||
|
||||
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<!-- <img class="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company"> -->
|
||||
<h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">RĂ©initialiser votre mot de passe PARCOURSMOB</h2>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Votre email</label>
|
||||
<div class="mt-1">
|
||||
<input id="email" name="email" type="text" autocomplete="email" required class="block w-full appearance-none rounded-2xl border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-co-blue focus:outline-none focus:ring-co-blue sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="p-4 text-gray-500 text-xs">Si votre compte existe, vous allez recevoir un mot de passe par email contenant un lien pour réinitialiser votre mot de passe. Celui-ci sera actif pendant 72h.</p>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="mt-2 flex w-full justify-center rounded-2xl border border-transparent bg-co-blue py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-co-blue focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2">Recevoir un lien de réinitialisation</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
|
@ -0,0 +1,26 @@
|
|||
{{define "main"}}
|
||||
<html class="h-full bg-gray-50">
|
||||
<head>
|
||||
<title>PARCOURSMOB - Identification</title>
|
||||
<link rel="stylesheet" href="http://localhost:9000/public/css/main.css" />
|
||||
</head>
|
||||
<body class="h-full">
|
||||
|
||||
|
||||
|
||||
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<!-- <img class="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company"> -->
|
||||
<h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">RĂ©initialiser votre mot de passe PARCOURSMOB</h2>
|
||||
<p class="p-12 text-gray-500 text-center text-md">Ce lien de réinitialisation n'est plus actif. Vous l'avez déjà utilisé ou il a expiré. Vous pouvez redemander un nouveau mot de passe ou réessayer de vous connecter directement à PARCOURSMOB.</p>
|
||||
<div>
|
||||
<a href="/">
|
||||
<button class="mt-2 flex w-full justify-center rounded-2xl border border-transparent bg-co-blue py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-co-blue focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2">Se connecter Ă PARCOURSMOB</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
|
@ -0,0 +1,41 @@
|
|||
{{define "main"}}
|
||||
<html class="h-full bg-gray-50">
|
||||
<head>
|
||||
<title>PARCOURSMOB - Identification</title>
|
||||
<link rel="stylesheet" href="http://localhost:9000/public/css/main.css" />
|
||||
</head>
|
||||
<body class="h-full">
|
||||
<form method="post">
|
||||
|
||||
|
||||
|
||||
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<!-- <img class="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company"> -->
|
||||
<h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">RĂ©initialisez votre mot de passe PARCOURSMOB</h2>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
|
||||
<p class="p-4 text-gray-500 text-xs">Vous avez demandé à réinitialiser votre mot de passe pour <b>{{.ViewState.recover.username}}</b></p>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700">Votre nouveau mot de passe</label>
|
||||
<div class="mt-1">
|
||||
<input id="password" name="password" type="password" required class="block w-full appearance-none rounded-2xl border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-co-blue focus:outline-none focus:ring-co-blue sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="mt-2 flex w-full justify-center rounded-2xl border border-transparent bg-co-blue py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-co-blue focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2">RĂ©initialiser</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
|
@ -0,0 +1,26 @@
|
|||
{{define "main"}}
|
||||
<html class="h-full bg-gray-50">
|
||||
<head>
|
||||
<title>PARCOURSMOB - Identification</title>
|
||||
<link rel="stylesheet" href="http://localhost:9000/public/css/main.css" />
|
||||
</head>
|
||||
<body class="h-full">
|
||||
|
||||
|
||||
|
||||
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<!-- <img class="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company"> -->
|
||||
<h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">Inscription Ă PARCOURSMOB</h2>
|
||||
<p class="p-12 text-gray-500 text-center text-md">Ce lien d'inscription n'est plus actif. Vous avez peut être déjà créé votre compte. Si ce n'est pas le cas, le lien a pu expirer : veuillez en demander un nouveau à l'administrateur PARCOURSMOB de votre structure.</p>
|
||||
<div>
|
||||
<a href="/">
|
||||
<button class="mt-2 flex w-full justify-center rounded-2xl border border-transparent bg-co-blue py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-co-blue focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2">Se connecter Ă PARCOURSMOB</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
|
@ -1,5 +1,135 @@
|
|||
{{define "beneficiary_files"}}
|
||||
<div class="px-4 py-6 sm:px-6">
|
||||
TODO Fichiers
|
||||
<div class="px-4 py-6 sm:px-6"
|
||||
x-data="{
|
||||
fields: {
|
||||
name: null,
|
||||
type: null,
|
||||
file: null,
|
||||
},
|
||||
rules: {
|
||||
name: ['required'],
|
||||
type: ['required'],
|
||||
file: ['required'],
|
||||
},
|
||||
formValidation: {
|
||||
valid: false,
|
||||
fields: {
|
||||
name: {valid: null},
|
||||
type: {valid: null},
|
||||
file: {valid: null},
|
||||
}
|
||||
},
|
||||
isFormValid: true,
|
||||
validate() {
|
||||
this.formValidation = Iodine.assert(this.fields, this.rules)
|
||||
},
|
||||
validateField(field) {
|
||||
this.formValidation.fields[field] = Iodine.assert(this.fields[field], this.rules[field])
|
||||
},
|
||||
submit(event) {
|
||||
this.validate()
|
||||
if(!this.formValidation.valid) {
|
||||
this.isFormValid = false
|
||||
event.preventDefault()
|
||||
}
|
||||
return this.formValidation.valid
|
||||
}
|
||||
}">
|
||||
{{if eq (len .ViewState.documents) 0}}
|
||||
<p class="p-12 text-gray-500 text-center text-md">Aucun document</p>
|
||||
{{end}}
|
||||
|
||||
|
||||
{{if gt (len .ViewState.documents) 0}}
|
||||
|
||||
<div class="-mx-4 mb-10 ring-1 ring-gray-300 sm:-mx-6 md:mx-0 md:rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Type</th>
|
||||
<th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">Nom du document</th>
|
||||
<th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">Ajouté le</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
|
||||
<span class="sr-only">Actions</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .ViewState.documents}}
|
||||
<tr>
|
||||
<td class="relative py-4 pl-4 sm:pl-6 pr-3 text-sm">
|
||||
<div class="font-medium text-gray-900">
|
||||
<span class="bg-co-blue text-xs text-white rounded-xl p-1 mr-2">{{index $.ViewState.file_types_map .Metadata.Type}}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-3.5 text-sm text-gray-900 lg:table-cell">{{.Metadata.Name}}</td>
|
||||
<td class="px-3 py-3.5 text-sm text-gray-500 lg:table-cell">{{.LastModified.Format "02/01/2006"}}</td>
|
||||
<td class="relative py-3.5 pl-3 pr-4 sm:pr-6 text-right text-sm font-medium">
|
||||
<a href="/app/beneficiaries/{{$.ViewState.beneficiary.ID}}/documents/{{.FileName}}" target="_blank">
|
||||
<button type="button" class="inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium leading-4 text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-30">Voir<span class="sr-only"> le document</span></button>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
|
||||
<!-- More plans... -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{end}}
|
||||
|
||||
<h3 class="text-lg">Ajouter un document</h3>
|
||||
<form method="POST" action="/app/beneficiaries/e7616eac-4a87-4396-a505-23e0421b9c4c/documents" @submit="submit" enctype="multipart/form-data">
|
||||
<div class="md:grid md:grid-cols-6 p-2">
|
||||
<div class="sm:col-span-2">
|
||||
<label for="type" class="block text-sm font-medium text-gray-700">Type</label>
|
||||
<select id="type" name="type" class="mt-1 block w-full rounded-l-2xl border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
|
||||
x-model="fields.type" @blur="validateField('type')"
|
||||
:class="formValidation.fields.type.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
<option value="" selected>SĂ©lectionner un type</option>
|
||||
{{range .ViewState.beneficiaries_file_types}}
|
||||
<option value="{{.}}">{{index $.ViewState.file_types_map .}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-4">
|
||||
<label for="name" class="block text-sm font-medium text-gray-700">Nom</label>
|
||||
<input type="text" name="name" id="name"
|
||||
placeholder="Nom du fichier"
|
||||
class="mt-1 focus:ring-co-blue focus:border-co-blue block w-full shadow-sm sm:text-sm rounded-r-2xl"
|
||||
x-model="fields.name" @blur="validateField('name')"
|
||||
:class="formValidation.fields.name.valid == false ? 'border-co-red border-2' : 'border-gray-300'" />
|
||||
</div>
|
||||
<div class="sm:col-span-6 mt-4">
|
||||
<label for="cover-photo" class="block text-sm font-medium text-gray-700">Téléchargement</label>
|
||||
<div class="mt-1 flex justify-center rounded-md border-2 border-dashed px-6 pt-5 pb-6"
|
||||
x-on:drop="console.log('toto')"
|
||||
:class="formValidation.fields.file.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
<div class="space-y-1 text-center">
|
||||
{{.IconSet.Icon "hero:outline/folder-plus" "mx-auto h-12 w-12 text-gray-400"}}
|
||||
<div class="flex text-sm text-gray-600">
|
||||
<label for="file-upload" class="relative cursor-pointer rounded-md bg-white font-medium text-co-blue focus-within:outline-none focus-within:ring-2 focus-within:ring-co-blue focus-within:ring-offset-2 hover:text-co-blue">
|
||||
<span>SĂ©lectionnez un fichier </span>
|
||||
<input id="file-upload" name="file-upload" type="file" class="sr-only"
|
||||
x-model="fields.file" @blur="validateField('file')">
|
||||
</label>
|
||||
<!-- <p class="pl-1">ou glissez-déposez</p> -->
|
||||
|
||||
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">Jusqu'Ă 10MB</p>
|
||||
<p class="text-co-blue p-2" x-text="fields.file" x-if="fields.file"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="rounded-2xl border border-transparent bg-co-blue px-4 py-2 my-4 mt-8 w-full text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2 sm:w-auto">
|
||||
Ajouter le document
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
{{end}}
|
|
@ -11,14 +11,16 @@
|
|||
last_name: null,
|
||||
email: null,
|
||||
phone_number: null,
|
||||
birthdate: null
|
||||
birthdate: null,
|
||||
file_number: null
|
||||
},
|
||||
rules: {
|
||||
first_name: ['required'],
|
||||
last_name: ['required'],
|
||||
email: ['required', 'email'],
|
||||
phone_number: ['required', 'regexMatch:^((\\+)33|0)[1-9](\\d{2}){4}$'],
|
||||
birthdate: ['optional'],
|
||||
birthdate: ['required'],
|
||||
file_number: ['optional'],
|
||||
},
|
||||
formValidation: {
|
||||
valid: false,
|
||||
|
@ -28,6 +30,7 @@
|
|||
email: {valid: null},
|
||||
phone_number: {valid: null},
|
||||
birthdate: {valid: null},
|
||||
file_number: {valid: null},
|
||||
}
|
||||
},
|
||||
isFormValid: true,
|
||||
|
@ -72,7 +75,7 @@
|
|||
:class="formValidation.fields.last_name.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input type="text" name="email" id="email" autocomplete="email"
|
||||
class="mt-1 focus:ring-co-blue focus:border-co-blue block w-full shadow-sm sm:text-sm rounded-2xl"
|
||||
|
@ -80,7 +83,7 @@
|
|||
:class="formValidation.fields.email.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="phone_number" class="block text-sm font-medium text-gray-700">Numéro de
|
||||
téléphone</label>
|
||||
<input type="text" name="phone_number" id="phone_number" autocomplete="phone" placeholder="+33612345678"
|
||||
|
@ -88,6 +91,15 @@
|
|||
x-model="fields.phone_number" @blur="validateField('phone_number')"
|
||||
:class="formValidation.fields.phone_number.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="birthdate" class="block text-sm font-medium text-gray-700">Date de
|
||||
naissance</label>
|
||||
<input type="date" name="birthdate" id="birthdate" autocomplete="birthdate" placeholder="JJ/MM/AAAA"
|
||||
class="mt-1 focus:ring-co-blue focus:border-co-blue block w-full shadow-sm sm:text-sm rounded-2xl"
|
||||
x-model="fields.birthdate" @blur="validateField('birthdate')"
|
||||
:class="formValidation.fields.birthdate.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -103,12 +115,11 @@
|
|||
<div class="grid grid-cols-6 gap-6">
|
||||
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="birthdate" class="block text-sm font-medium text-gray-700">Date de
|
||||
naissance</label>
|
||||
<input type="date" name="birthdate" id="birthdate" autocomplete="birthdate" placeholder="JJ/MM/AAAA"
|
||||
<label for="file_number" class="block text-sm font-medium text-gray-700">Numéro de dossier (allocataire, ...)</label>
|
||||
<input type="text" name="file_number" id="file_number" placeholder=""
|
||||
class="mt-1 focus:ring-co-blue focus:border-co-blue block w-full shadow-sm sm:text-sm rounded-2xl"
|
||||
x-model="fields.birthdate" @blur="validateField('birthdate')"
|
||||
:class="formValidation.fields.birthdate.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
x-model="fields.file_number" @blur="validateField('file_number')"
|
||||
:class="formValidation.fields.file_number.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
</section>
|
||||
|
||||
<section aria-labelledby="functionalities-title" x-data="{
|
||||
tab: 'vehicles',
|
||||
tab: 'documents',
|
||||
to(event) {
|
||||
this.tab = event.target.value
|
||||
}
|
||||
|
@ -91,9 +91,11 @@
|
|||
|
||||
<option value="journeys">DĂ©placements</option>
|
||||
|
||||
<option value="vehicles">VĂ©hicules</option>
|
||||
|
||||
<option value="events">Dispositifs</option>
|
||||
|
||||
<option value="files">Documents</option>
|
||||
<option value="documents">Documents</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="hidden sm:block">
|
||||
|
@ -120,10 +122,10 @@
|
|||
:class="tab == 'events' ? 'border-co-blue text-co-blue' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'">
|
||||
Dispositifs </a>
|
||||
|
||||
<!-- <a href="#" @click="tab = 'files'"
|
||||
<a href="#" @click="tab = 'documents'"
|
||||
class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
|
||||
:class="tab == 'files' ? 'border-co-blue text-co-blue' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'">
|
||||
Documents </a> -->
|
||||
:class="tab == 'documents' ? 'border-co-blue text-co-blue' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'">
|
||||
Documents </a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -132,7 +134,7 @@
|
|||
<div x-show="tab == 'journeys'">{{template "beneficiary_journeys" .}}</div>
|
||||
<div x-show="tab == 'vehicles'">{{template "beneficiary_vehicles" .}}</div>
|
||||
<div x-show="tab == 'events'">{{template "beneficiary_events" .}}</div>
|
||||
<div x-show="tab == 'files'">{{template "beneficiary_files" .}}</div>
|
||||
<div x-show="tab == 'documents'">{{template "beneficiary_files" .}}</div>
|
||||
<div x-show="tab == 'notes'">{{template "beneficiary_notes" .}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
<p class="mt-2 text-sm text-gray-700"></p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<a href="/api/cache/{{.ViewState.CacheId}}/export">
|
||||
<button type="button"
|
||||
class="inline-flex items-center justify-center bg-white hover:bg-gray-50 border-gray-300 border px-4 py-2 text-gray-700 flex items-center text-sm rounded-2xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-co-blue">
|
||||
{{$.IconSet.Icon "hero:outline/document-arrow-down" "h-5 w-5 mr-3"}}
|
||||
Exporter
|
||||
</button>
|
||||
</a>
|
||||
<a href="/app/beneficiaries/create">
|
||||
<button type="button"
|
||||
class="inline-flex items-center justify-center rounded-2xl border border-transparent bg-co-blue px-4 py-2 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-ci-blue focus:ring-offset-2 sm:w-auto">
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
email: '{{ .ViewState.Data.email }}',
|
||||
phone_number: '{{ .ViewState.Data.phone_number }}',
|
||||
birthdate: {{if .ViewState.Data.birthdate}}'{{ (timeFrom .ViewState.Data.birthdate).Format "2006-01-02" }}'{{else}}null{{end}},
|
||||
file_number: '{{ .ViewState.Data.file_number }}',
|
||||
gender: {{.ViewState.Data.gender}}
|
||||
},
|
||||
rules: {
|
||||
|
@ -19,7 +20,8 @@
|
|||
last_name: ['required'],
|
||||
email: ['required', 'email'],
|
||||
phone_number: ['required', 'regexMatch:^((\\+)33|0)[1-9](\\d{2}){4}$'],
|
||||
birthdate: ['optional'],
|
||||
birthdate: ['required'],
|
||||
file_number: ['optional'],
|
||||
},
|
||||
formValidation: {
|
||||
valid: false,
|
||||
|
@ -29,6 +31,7 @@
|
|||
email: {valid: null},
|
||||
phone_number: {valid: null},
|
||||
birthdate: {valid: null},
|
||||
file_number: {valid: null},
|
||||
}
|
||||
},
|
||||
isFormValid: true,
|
||||
|
@ -73,7 +76,7 @@
|
|||
:class="formValidation.fields.last_name.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
||||
<input type="text" name="email" id="email" autocomplete="email"
|
||||
class="mt-1 focus:ring-co-blue focus:border-co-blue block w-full shadow-sm sm:text-sm rounded-2xl"
|
||||
|
@ -81,7 +84,7 @@
|
|||
:class="formValidation.fields.email.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="phone_number" class="block text-sm font-medium text-gray-700">Numéro de
|
||||
téléphone</label>
|
||||
<input type="text" name="phone_number" id="phone_number" autocomplete="phone" placeholder="+33612345678"
|
||||
|
@ -89,6 +92,15 @@
|
|||
x-model="fields.phone_number" @blur="validateField('phone_number')"
|
||||
:class="formValidation.fields.phone_number.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="birthdate" class="block text-sm font-medium text-gray-700">Date de
|
||||
naissance</label>
|
||||
<input type="date" name="birthdate" id="birthdate" autocomplete="birthdate" placeholder="JJ/MM/AAAA"
|
||||
class="mt-1 focus:ring-co-blue focus:border-co-blue block w-full shadow-sm sm:text-sm rounded-2xl"
|
||||
x-model="fields.birthdate" @blur="validateField('birthdate')"
|
||||
:class="formValidation.fields.birthdate.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -104,12 +116,11 @@
|
|||
<div class="grid grid-cols-6 gap-6">
|
||||
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="birthdate" class="block text-sm font-medium text-gray-700">Date de
|
||||
naissance</label>
|
||||
<input type="date" name="birthdate" id="birthdate" autocomplete="birthdate" placeholder="JJ/MM/AAAA"
|
||||
<label for="file_number" class="block text-sm font-medium text-gray-700">Numéro de dossier (allocataire, ...)</label>
|
||||
<input type="text" name="file_number" id="file_number"
|
||||
class="mt-1 focus:ring-co-blue focus:border-co-blue block w-full shadow-sm sm:text-sm rounded-2xl"
|
||||
x-model="fields.birthdate" @blur="validateField('birthdate')"
|
||||
:class="formValidation.fields.birthdate.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
x-model="fields.file_number" @blur="validateField('file_number')"
|
||||
:class="formValidation.fields.file_number.valid == false ? 'border-co-red border-2' : 'border-gray-300'">
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
{{end}}
|
||||
|
||||
|
||||
{{ if gt (len .ViewState.journeys.Journeys) 0}}
|
||||
<div class="px-4 pt-4 flex text-sm text-grey-900 font-bold border-t-2">
|
||||
<div class="flex-1">
|
||||
{{.IconSet.Icon "tabler-icons:bus" "h-6 w-6 inline-flex mr-4"}}
|
||||
|
@ -76,7 +76,7 @@
|
|||
{{end}}
|
||||
{{if eq .Type "public_transport"}}
|
||||
<span class="ml-2 rounded-xl px-2 py-1 bg-co-blue flex items-center justify-center ring-8 ring-white text-sm text-white whitespace-nowrap">
|
||||
{{if eq .Display.Network "Antibes"}}Envibus{{else}}{{.Display.Network}}{{end}} Ligne {{.Display.Label}}
|
||||
{{if eq .Display.Network "Antibes - Envibus"}}Envibus{{else}}{{.Display.Network}}{{end}} Ligne {{.Display.Label}}
|
||||
</span>
|
||||
{{$.IconSet.Icon "hero:outline/chevron-right" "h-3 w-3 stroke-gray-800 m-2"}}
|
||||
{{end}}
|
||||
|
@ -88,6 +88,8 @@
|
|||
<button class="rounded-xl text-md px-4 py-1 bg-gray-200 text-co-blue" @click="tab = 'public-transit'">{{ len .ViewState.journeys.Journeys}} solutions en transports en commun : les voir toutes</button>
|
||||
</div>
|
||||
|
||||
{{end}}
|
||||
|
||||
<!--VEHICLES-->
|
||||
<div class="px-4 pt-16 flex text-sm text-grey-900 border-t-2">
|
||||
<div class="flex-1">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
{{define "journeys_carpool"}}
|
||||
|
||||
{{ if eq (len .ViewState.carpools) 0}}
|
||||
<p class="p-12 text-gray-500 text-center text-md">Aucun covoiturage disponible pour ce trajet.</p>
|
||||
{{end}}
|
||||
|
||||
{{$first := true}}
|
||||
{{range .ViewState.carpools}}
|
||||
{{if $first}}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
{{define "journeys_public_transit"}}
|
||||
|
||||
{{ if eq (len .ViewState.journeys.Journeys) 0}}
|
||||
<p class="p-12 text-gray-500 text-center text-md">Aucun transport en commun pour ce trajet.</p>
|
||||
{{end}}
|
||||
|
||||
{{$first := true}}
|
||||
{{range .ViewState.journeys.Journeys}}
|
||||
{{if $first}}
|
||||
|
@ -51,7 +55,7 @@
|
|||
</div>
|
||||
<div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
|
||||
<div>
|
||||
<p class="text-md text-gray-500">{{if eq .Display.Network "Antibes"}}Envibus{{else}}{{.Display.Network}}{{end}} <a href="#" class="font-medium text-gray-900">Ligne {{.Display.Label}}</a></p>
|
||||
<p class="text-md text-gray-500">{{if eq .Display.Network "Antibes - Envibus"}}Envibus{{else}}{{.Display.Network}}{{end}} <a href="#" class="font-medium text-gray-900">Ligne {{.Display.Label}}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</div>
|
||||
|
||||
<div class="flex-shrink-0 flex items-center px-4">
|
||||
<img class="h-8 w-auto" src="/public/images/parcoursmob_logo_whitered.svg" alt="PARCOURSMOB">
|
||||
<img class="h-8 w-auto" src="/public/images/main_logo.svg" alt="PARCOURSMOB">
|
||||
</div>
|
||||
<div class="mt-5 flex-1 h-0 overflow-y-auto">
|
||||
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
{{define "content"}}
|
||||
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 space-y-6">
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Contact</h1>
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Demande de support</h1>
|
||||
<div class="bg-white py-2 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<p class="text-sm text-gray-600 p-4">
|
||||
Le support PARCOURSMOB est ouvert les jours ouvrés de 9h à 18h. Vous pouvez également nous joindre par email à <b class="text-co-blue"><a href="mailto:support@parcoursmob.fr">support@parcoursmob.fr</a></b>, par exemple pour nous envoyez des copies d'écran du problème que vous rencontrez.
|
||||
</p>
|
||||
<form action="" method="POST">
|
||||
|
||||
<div class="mb-4 w-full bg-gray-50 rounded-lg border border-gray-200 dark:bg-gray-700 dark:border-gray-600">
|
||||
<div class="py-2 px-4 bg-white rounded-t-lg dark:bg-gray-800">
|
||||
<div class="mb-4 w-full bg-gray-50 rounded-3xl border border-gray-200 dark:bg-gray-700 dark:border-gray-600">
|
||||
<div class="py-2 px-4 rounded-3xl bg-white dark:bg-gray-800">
|
||||
<label class="sr-only">Votre message</label>
|
||||
|
||||
<textarea name="comment" rows="4" class="block w-full resize-none border-0 border-b border-transparent p-0 pb-2 focus:border-indigo-600 focus:ring-0 sm:text-sm" placeholder="Votre message..." required></textarea>
|
||||
<textarea name="comment" rows="4" class="block w-full resize-none border-0 border-b border-transparent p-0 pb-2 focus:border-co-blue focus:ring-0 sm:text-sm" placeholder="Votre message..." required></textarea>
|
||||
</div>
|
||||
<div class="flex justify-center items-center py-2 px-3 border-t dark:border-gray-600">
|
||||
<button type="submit" value="send message" class="px-2 py-2 border border-transparent text-sm font-medium rounded-2xl shadow-sm text-white bg-co-blue hover:bg-co-blue focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-co-blue">
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<a href="/app/vehicles/bookings/">
|
||||
<button type="button"
|
||||
class="inline-flex items-center justify-center rounded-2xl border border-transparent bg-co-blue px-4 py-2 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2 sm:w-auto">
|
||||
Voir les prêts de véhicules
|
||||
Voir les mises Ă disposition
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -23,14 +23,15 @@
|
|||
<div class="border-t border-gray-200 px-4 py-5 sm:px-6">
|
||||
<form method="GET">
|
||||
|
||||
|
||||
<div x-data="{
|
||||
text: '{{if .ViewState.search}}{{.ViewState.search.beneficiary.Data.first_name}} {{.ViewState.search.beneficiary.Data.last_name}}{{end}}',
|
||||
beneficiariesListOpen: false,
|
||||
beneficiaries: {{json .ViewState.beneficiaries}},
|
||||
filteredBeneficiaries: (text) => {
|
||||
if(text=='') return beneficiaries
|
||||
return this.beneficiaries.filter(b => b['data']['first_name'].includes(text) || b['data']['last_name'].includes(text))
|
||||
filteredBeneficiaries: (beneficiaries, text) => {
|
||||
if(text=='') {
|
||||
return beneficiaries
|
||||
}
|
||||
return beneficiaries.filter(b => b['data']['first_name'].toLowerCase().includes(text.toLowerCase()) || b['data']['last_name'].toLowerCase().includes(text.toLowerCase()))
|
||||
},
|
||||
fields: {
|
||||
beneficiaryid: {{if .ViewState.search}}'{{.ViewState.search.beneficiary.ID}}'{{else}}null{{end}},
|
||||
|
@ -45,7 +46,7 @@
|
|||
<input type="hidden" name="beneficiaryid" x-model="fields.beneficiaryid">
|
||||
<label for="combobox" class="block text-sm font-medium text-gray-700">Bénéficiaire</label>
|
||||
<div class="relative mt-1 mb-4">
|
||||
<input @focus="beneficiariesListOpen = true" x-model="text" id="combobox" type="text" class="w-full rounded-2xl border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-co-blue focus:outline-none focus:ring-1 focus:ring-co-blue sm:text-sm" role="combobox" aria-controls="options" aria-expanded="false">
|
||||
<input autocomplete="off" @focus="beneficiariesListOpen = true" x-model="text" id="combobox" type="text" class="w-full rounded-2xl border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-co-blue focus:outline-none focus:ring-1 focus:ring-co-blue sm:text-sm" role="combobox" aria-controls="options" aria-expanded="false">
|
||||
|
||||
<button @click="beneficiariesListOpen = ! beneficiariesListOpen" type="button" class="absolute inset-y-0 right-0 flex items-center rounded-r-2xl px-2 focus:outline-none">
|
||||
<!-- Heroicon name: solid/selector -->
|
||||
|
@ -60,7 +61,7 @@
|
|||
|
||||
Active: "text-white bg-indigo-600", Not Active: "text-gray-900"
|
||||
-->
|
||||
<template x-for="beneficiary in beneficiaries">
|
||||
<template x-for="beneficiary in filteredBeneficiaries(beneficiaries, text)">
|
||||
<li @click="selectbeneficiary(beneficiary)" class="relative cursor-default hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900" id="option-0" role="option" tabindex="-1">
|
||||
<!-- Selected: "font-semibold" -->
|
||||
<span class="truncate" x-text="beneficiary.data.first_name"></span> <span class="truncate" x-text="beneficiary.data.last_name"></span>
|
||||
|
|
|
@ -7,22 +7,19 @@
|
|||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<!-- <th scope="col"
|
||||
class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
<th scope="col"
|
||||
class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 text-center">
|
||||
Statut
|
||||
</th> -->
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
Type
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
Immatriculation
|
||||
Numéro (Immat / Bicycode)
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
Beneficiaire
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
Gestionnaire
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
Dates
|
||||
</th>
|
||||
|
@ -32,29 +29,35 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
{{range .ViewState.vehicles}}
|
||||
{{$vehicle := .}}
|
||||
{{range .Bookings}}
|
||||
{{range .ViewState.bookings}}
|
||||
<tr>
|
||||
<!-- <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
|
||||
<div class="text-gray-900" >aa</div>
|
||||
</td> -->
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
|
||||
<div class="text-gray-900" >Voiture</div>
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6 text-center">
|
||||
{{if eq .Status 1 }}
|
||||
<span class="p-1 bg-co-blue text-white text-xs font-bold rounded-xl" >
|
||||
A venir
|
||||
</span>
|
||||
{{end}}
|
||||
{{if eq .Status 0 }}
|
||||
<span class="p-1 bg-co-green text-white text-xs font-bold rounded-xl" >
|
||||
En cours
|
||||
</span>
|
||||
{{end}}
|
||||
{{if eq .Status -1 }}
|
||||
<span class="p-1 bg-co-red text-white text-xs font-bold rounded-xl" >
|
||||
Terminé
|
||||
</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
|
||||
<div class="text-gray-900" >{{$vehicle.Data.licence_plate}}</div>
|
||||
<div class="text-gray-900" >{{ (index $.ViewState.vehicles_map .Vehicleid).Type }}</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
|
||||
<div class="text-gray-900" >{{ (index $.ViewState.vehicles_map .Vehicleid).Data.licence_plate }}</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
|
||||
<div class="text-gray-900" ><img class="h-6 w-6 rounded-co"
|
||||
src="/app/beneficiaries/{{.Driver}}/picture" alt=""></div>
|
||||
</td>
|
||||
<!-- <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
|
||||
<div class="text-gray-900" >aa</div>
|
||||
</td> -->
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
|
||||
<div class="text-gray-900" >COOPGO</div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
|
||||
<div class="text-gray-900" >Du {{(timeFrom .Startdate).Format "02/01/2006"}} au {{(timeFrom .Enddate).Format "02/01/2006"}}</div>
|
||||
</td>
|
||||
|
@ -64,7 +67,6 @@
|
|||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{{define "content"}}
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Historique des réservations de véhicules</h1>
|
||||
|
||||
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
|
||||
<a href="/api/cache/{{.ViewState.cacheid}}/export">
|
||||
<button type="button"
|
||||
class="inline-flex items-center justify-center bg-white hover:bg-gray-50 border-gray-300 border px-4 py-2 text-gray-700 flex items-center text-sm rounded-2xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-co-blue">
|
||||
{{$.IconSet.Icon "hero:outline/document-arrow-down" "h-5 w-5 mr-3"}}
|
||||
Exporter
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "bookings_list" .}}
|
||||
|
||||
{{end}}
|
|
@ -2,17 +2,31 @@
|
|||
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
|
||||
<h1 class="text-2xl font-semibold text-gray-900">Gestion des véhicules et réservations</h1>
|
||||
|
||||
<h2 class="text-xl font-semibold text-gray-600 pt-8">RĂ©servations</h2>
|
||||
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h2 class="text-xl font-semibold text-gray-600 pt-8">RĂ©servations en cours et Ă venir</h2>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<a href="/app/vehicles-management/bookings/">
|
||||
<button type="button"
|
||||
class="inline-flex items-center justify-center rounded-2xl border border-transparent bg-co-blue px-4 py-2 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-co-blue focus:ring-offset-2 sm:w-auto">
|
||||
{{$.IconSet.Icon "hero:outline/table-cells" "h-5 w-5 mr-3"}}
|
||||
Historique
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "bookings_list" .}}
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
|
||||
<h2 class="text-xl font-semibold text-gray-600 pt-8">VĂ©hicules</h2>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 md:px-8 mt-8">
|
||||
|
||||
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<p class="mt-2 text-sm text-gray-700"></p>
|
||||
<h2 class="text-xl font-semibold text-gray-600 pt-8">VĂ©hicules</h2>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<a href="/app/vehicles-management/fleet/add">
|
||||
|
|
|
@ -806,6 +806,18 @@ html {
|
|||
left: 1rem;
|
||||
}
|
||||
|
||||
.left-6 {
|
||||
left: 1.5rem;
|
||||
}
|
||||
|
||||
.-top-px {
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.right-6 {
|
||||
right: 1.5rem;
|
||||
}
|
||||
|
||||
.z-40 {
|
||||
z-index: 40;
|
||||
}
|
||||
|
@ -993,6 +1005,10 @@ html {
|
|||
margin-right: -0.25rem;
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
@ -1069,6 +1085,10 @@ html {
|
|||
height: 0.75rem;
|
||||
}
|
||||
|
||||
.h-px {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.max-h-60 {
|
||||
max-height: 15rem;
|
||||
}
|
||||
|
@ -1239,6 +1259,10 @@ html {
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.resize-none {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.appearance-none {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
|
@ -1507,9 +1531,9 @@ html {
|
|||
border-bottom-left-radius: 1rem;
|
||||
}
|
||||
|
||||
.rounded-l {
|
||||
border-top-left-radius: 0.25rem;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
.rounded-t-lg {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-top-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.border {
|
||||
|
@ -1520,6 +1544,10 @@ html {
|
|||
border-width: 2px;
|
||||
}
|
||||
|
||||
.border-0 {
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.border-r {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
@ -1544,6 +1572,10 @@ html {
|
|||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
.border-dashed {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.border-gray-200 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(229 231 235 / var(--tw-border-opacity));
|
||||
|
@ -1670,8 +1702,12 @@ html {
|
|||
padding: 0.375rem;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 0.75rem;
|
||||
.p-8 {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.p-0 {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.px-4 {
|
||||
|
@ -1714,6 +1750,11 @@ html {
|
|||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.px-1 {
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.px-3 {
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
|
@ -1734,11 +1775,6 @@ html {
|
|||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.px-1 {
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.py-8 {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
|
@ -1777,6 +1813,10 @@ html {
|
|||
padding-right: 2.25rem;
|
||||
}
|
||||
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.pr-12 {
|
||||
padding-right: 3rem;
|
||||
}
|
||||
|
@ -1801,10 +1841,6 @@ html {
|
|||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.pt-4 {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
@ -1829,6 +1865,14 @@ html {
|
|||
padding-left: 0.375rem;
|
||||
}
|
||||
|
||||
.pb-6 {
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
@ -2084,6 +2128,11 @@ html {
|
|||
--tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.ring-gray-300 {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.ring-opacity-5 {
|
||||
--tw-ring-opacity: 0.05;
|
||||
}
|
||||
|
@ -2170,6 +2219,11 @@ html {
|
|||
color: rgb(75 85 99 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.focus-within\:outline-none:focus-within {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.focus-within\:ring-2:focus-within {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
|
@ -2181,6 +2235,11 @@ html {
|
|||
--tw-ring-color: rgb(36 56 135 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus-within\:ring-indigo-500:focus-within {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus-within\:ring-offset-2:focus-within {
|
||||
--tw-ring-offset-width: 2px;
|
||||
}
|
||||
|
@ -2258,6 +2317,11 @@ html {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
.hover\:text-indigo-500:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(99 102 241 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.focus\:border-transparent:focus {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
@ -2277,6 +2341,11 @@ html {
|
|||
border-color: rgb(59 130 246 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.focus\:border-indigo-600:focus {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(79 70 229 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.focus\:placeholder-gray-400:focus::-moz-placeholder {
|
||||
--tw-placeholder-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
|
||||
|
@ -2352,11 +2421,36 @@ html {
|
|||
--tw-ring-offset-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.disabled\:cursor-not-allowed:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disabled\:opacity-30:disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.group:hover .group-hover\:text-gray-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:border-gray-600 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(75 85 99 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-gray-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-gray-800 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sm\:col-span-3 {
|
||||
grid-column: span 3 / span 3;
|
||||
|
@ -2404,6 +2498,10 @@ html {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.sm\:inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.sm\:flex {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -2532,16 +2630,16 @@ html {
|
|||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.sm\:py-5 {
|
||||
padding-top: 1.25rem;
|
||||
padding-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.sm\:px-10 {
|
||||
padding-left: 2.5rem;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.sm\:py-5 {
|
||||
padding-top: 1.25rem;
|
||||
padding-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.sm\:pl-6 {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
@ -2578,6 +2676,19 @@ html {
|
|||
grid-column: span 2 / span 2;
|
||||
}
|
||||
|
||||
.md\:col-span-3 {
|
||||
grid-column: span 3 / span 3;
|
||||
}
|
||||
|
||||
.md\:col-span-6 {
|
||||
grid-column: span 6 / span 6;
|
||||
}
|
||||
|
||||
.md\:mx-0 {
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.md\:ml-0 {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
@ -2711,6 +2822,14 @@ html {
|
|||
margin-right: -2rem;
|
||||
}
|
||||
|
||||
.lg\:table-cell {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.lg\:hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lg\:max-w-7xl {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 374.2 33.2"><defs><style>.cls-1{fill:#ff1300;}.cls-2{fill:#fff;}</style></defs><g id="Calque_2" data-name="Calque 2"><g id="Calque_1-2" data-name="Calque 1"><path class="cls-1" d="M300.9.6a4.7,4.7,0,0,1,4.9,4.9V32.6h-8.6a4.7,4.7,0,0,1-4.9-4.9V14.1h-.9c-.6,0-1,.6-1,1.5v17H282a4.7,4.7,0,0,1-4.9-4.9V14.1h-1a1.4,1.4,0,0,0-.9,1.5v17H263a1.2,1.2,0,0,1-1.2-1.2V5.5A4.7,4.7,0,0,1,266.7.6h17.9l-.7,5a.9.9,0,0,0,.7,1.1.9.9,0,0,0,1-.8l.8-4.6a.9.9,0,0,1,.9-.7Z"/><path class="cls-1" d="M341.1,16.4c0,9.1-1.9,16.8-16.5,16.8S307.8,25,307.8,16.4,310.6,0,324.6,0,341.1,7.8,341.1,16.4Z"/><path class="cls-1" d="M344.3,32.6a1.2,1.2,0,0,1-1.2-1.2V1.8A1.2,1.2,0,0,1,344.3.6h18.8c6.9,0,10.4,2.9,10.4,8.1s-1.6,6.9-4.5,7.5h0c2.9.6,5.2,3.2,5.2,7.7s-3.5,8.6-10.4,8.6Z"/><path class="cls-2" d="M16.2,11.6c0-.6-.4-.9-1-.9h-.8V31.4a1.2,1.2,0,0,1-1.2,1.2H1.2A1.2,1.2,0,0,1,0,31.4V1.8A1.2,1.2,0,0,1,1.2.6H16.8C25.7.6,30.9,4.2,31,10.7s-4.1,9.9-9.4,10H16.1Z"/><path class="cls-2" d="M44.9,23.1H43.2L40,31.9a1.1,1.1,0,0,1-1.2.7H26.9a1.3,1.3,0,0,1-1.3-1.9L37,3.4A4.5,4.5,0,0,1,41.3.6h5.8a4.1,4.1,0,0,1,4,2.6L62.6,30.8c.5,1-.2,1.8-1.3,1.8H41.7Z"/><path class="cls-2" d="M66.1,32.6a1.2,1.2,0,0,1-1.2-1.2V1.8A1.2,1.2,0,0,1,66.1.6H83.2C92.7.6,95.5,7,95.6,12.3c0,3.3-.8,6.9-3.4,8.3l3.4,12H84.4c-2.3,0-5.2-.7-5.9-2.7L71.9,10.7a1,1,0,0,0-1.2-.7,1,1,0,0,0-.5,1.3l7.2,21.3Z"/><path class="cls-2" d="M129.6,17.1c.8,0,1.3.5,1.2,1.2-.2,8.3-2.9,14.9-16.4,14.9-15.8.1-16.8-8.2-16.8-16.8S100.5,0,114.4,0c15.7,0,16.5,8,16.5,15.4H115.3V11.3c0-.8-.4-1.2-1-1.2s-.9.4-.9,1.2V21.9c0,.9.4,1.2.9,1.2s1-.3,1-1.2V17.1Z"/><path class="cls-2" d="M166.2,16.4c0,9.1-2,16.8-16.6,16.8S132.8,25,132.8,16.4,135.7,0,149.6,0,166.2,7.8,166.2,16.4Z"/><path class="cls-2" d="M200.5,1.8V12.2c0,12.7-1.4,21-16.3,21s-16-8.4-16-20.9V1.8A1.2,1.2,0,0,1,169.4.6h13a.9.9,0,0,1,.9.9V21.8c0,.9.4,1.2,1,1.2s.9-.3.9-1.2V1.5a.9.9,0,0,1,.9-.9h13.1A1.2,1.2,0,0,1,200.5,1.8Z"/><path class="cls-2" d="M203.9,32.6a1.2,1.2,0,0,1-1.3-1.2V1.8A1.2,1.2,0,0,1,203.8.6h17.1c9.5,0,12.3,6.4,12.4,11.7,0,3.3-.8,6.9-3.4,8.3l3.4,12H222.1c-2.3,0-5.2-.7-5.9-2.7l-6.6-19.2a1,1,0,0,0-1.2-.7,1,1,0,0,0-.5,1.3l7.2,21.3Z"/><path class="cls-2" d="M235.8,29.2v-10a1,1,0,0,1,1.5-1l11.1,5.1a1,1,0,0,0,1.2-.4.9.9,0,0,0-.6-1.2l-9.3-4.4c-4.5-2.2-4.4-5.5-4.4-8.2,0-7.6,7.4-9.1,14.6-9.1,5.4,0,12.1,1.8,14.3,4.4v9.2a1,1,0,0,1-1.5,1L252.3,9.8a1,1,0,0,0-1.3.4q-.3.8.6,1.2l8.5,4.1c5.3,2.6,4.7,4.9,4.7,9.2s-6.3,8.5-14.9,8.5C244.7,33.2,238,31.6,235.8,29.2Z"/></g></g></svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,104 +0,0 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"go.etcd.io/etcd/client/v3/namespace"
|
||||
)
|
||||
|
||||
type CacheHandler struct {
|
||||
*clientv3.Client
|
||||
}
|
||||
|
||||
func NewCacheHandler(cfg *viper.Viper) (*CacheHandler, error) {
|
||||
var (
|
||||
endpoints = cfg.GetStringSlice("cache.storage.etcd.endpoints")
|
||||
prefix = cfg.GetString("cache.storage.etcd.prefix")
|
||||
)
|
||||
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: 5 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cli.KV = namespace.NewKV(cli.KV, prefix)
|
||||
cli.Watcher = namespace.NewWatcher(cli.Watcher, prefix)
|
||||
cli.Lease = namespace.NewLease(cli.Lease, prefix)
|
||||
|
||||
return &CacheHandler{
|
||||
Client: cli,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *CacheHandler) Put(k string, v any) error {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// _, err = s.Client.KV.Put(context.TODO(), k, data.String())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err = s.Client.KV.Put(ctx, k, string(data))
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CacheHandler) PutWithTTL(k string, v any, duration time.Duration) error {
|
||||
lease, err := s.Client.Lease.Grant(context.TODO(), int64(duration.Seconds()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// _, err = s.Client.KV.Put(context.TODO(), k, data.String(), clientv3.WithLease(lease.ID))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err = s.Client.KV.Put(ctx, k, string(data), clientv3.WithLease(lease.ID))
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CacheHandler) Get(k string) (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
resp, err := s.Client.KV.Get(ctx, k)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range resp.Kvs {
|
||||
var data any
|
||||
err := json.Unmarshal([]byte(v.Value), &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We return directly as we want to last revision of value
|
||||
return data, nil
|
||||
}
|
||||
return nil, errors.New(fmt.Sprintf("no value %v", k))
|
||||
}
|
||||
|
||||
func (s *CacheHandler) Delete(k string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err := s.Client.KV.Delete(ctx, k)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -19,6 +19,7 @@ func (p *IdentificationProvider) GroupsMiddleware(next http.Handler) http.Handle
|
|||
|
||||
o, ok := session.Values["organization"]
|
||||
if !ok || o == nil {
|
||||
fmt.Println("no organization")
|
||||
http.Redirect(w, r, "/auth/groups/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/services"
|
||||
"git.coopgo.io/coopgo-apps/parcoursmob/utils/storage"
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -28,7 +29,7 @@ type IdentificationProvider struct {
|
|||
Services *services.ServicesHandler
|
||||
}
|
||||
|
||||
func NewIdentificationProvider(cfg *viper.Viper, services *services.ServicesHandler) (*IdentificationProvider, error) {
|
||||
func NewIdentificationProvider(cfg *viper.Viper, services *services.ServicesHandler, kv storage.KVHandler) (*IdentificationProvider, error) {
|
||||
var (
|
||||
providerURL = cfg.GetString("identification.oidc.provider")
|
||||
clientID = cfg.GetString("identification.oidc.client_id")
|
||||
|
@ -54,7 +55,7 @@ func NewIdentificationProvider(cfg *viper.Viper, services *services.ServicesHand
|
|||
Scopes: []string{oidc.ScopeOpenID, "groups", "first_name", "last_name", "display_name", "email"},
|
||||
}
|
||||
|
||||
var store = sessions.NewCookieStore([]byte(sessionsSecret))
|
||||
store := storage.NewSessionStore(kv, []byte(sessionsSecret))
|
||||
verifier := provider.Verifier(&oidc.Config{ClientID: oauth2Config.ClientID})
|
||||
|
||||
return &IdentificationProvider{
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package sorting
|
||||
|
||||
import (
|
||||
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
|
||||
)
|
||||
|
||||
type BeneficiariesByName []mobilityaccountsstorage.Account
|
||||
|
||||
func (e BeneficiariesByName) Len() int { return len(e) }
|
||||
func (e BeneficiariesByName) Less(i, j int) bool {
|
||||
return e[i].Data["first_name"].(string) < e[j].Data["first_name"].(string)
|
||||
}
|
||||
func (e BeneficiariesByName) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
|
@ -0,0 +1,11 @@
|
|||
package sorting
|
||||
|
||||
import (
|
||||
agendastorage "git.coopgo.io/coopgo-platform/agenda/storage"
|
||||
)
|
||||
|
||||
type EventsByStartdate []agendastorage.Event
|
||||
|
||||
func (e EventsByStartdate) Len() int { return len(e) }
|
||||
func (e EventsByStartdate) Less(i, j int) bool { return e[i].Startdate.Before(e[j].Startdate) }
|
||||
func (e EventsByStartdate) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
|
@ -0,0 +1,23 @@
|
|||
package sorting
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
fleetsstorage "git.coopgo.io/coopgo-platform/fleets/storage"
|
||||
)
|
||||
|
||||
type VehiclesByLicencePlate []fleetsstorage.Vehicle
|
||||
|
||||
func (a VehiclesByLicencePlate) Len() int { return len(a) }
|
||||
func (a VehiclesByLicencePlate) Less(i, j int) bool {
|
||||
return strings.Compare(a[i].Data["licence_plate"].(string), a[j].Data["licence_plate"].(string)) < 0
|
||||
}
|
||||
func (a VehiclesByLicencePlate) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type BookingsByStartdate []fleetsstorage.Booking
|
||||
|
||||
func (a BookingsByStartdate) Len() int { return len(a) }
|
||||
func (a BookingsByStartdate) Less(i, j int) bool {
|
||||
return a[i].Startdate.Before(a[j].Startdate)
|
||||
}
|
||||
func (a BookingsByStartdate) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
@ -0,0 +1,15 @@
|
|||
package sorting
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
|
||||
)
|
||||
|
||||
type GroupsByName []groupstorage.Group
|
||||
|
||||
func (a GroupsByName) Len() int { return len(a) }
|
||||
func (a GroupsByName) Less(i, j int) bool {
|
||||
return strings.Compare(a[i].Data["name"].(string), a[j].Data["name"].(string)) < 0
|
||||
}
|
||||
func (a GroupsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
@ -0,0 +1 @@
|
|||
package sorting
|
|
@ -0,0 +1,11 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type CacheHandler KVHandler
|
||||
|
||||
func NewCacheHandler(cfg *viper.Viper) (CacheHandler, error) {
|
||||
return NewKVHandler(cfg)
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"go.etcd.io/etcd/client/v3/namespace"
|
||||
)
|
||||
|
||||
type EtcdSerializer interface {
|
||||
Deserialize(d []byte, m *any) error
|
||||
Serialize(m any) ([]byte, error)
|
||||
}
|
||||
|
||||
type JSONEtcdSerializer struct{}
|
||||
|
||||
// Serialize to JSON. Will err if there are unmarshalable key values
|
||||
func (s JSONEtcdSerializer) Serialize(m any) ([]byte, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
// Deserialize back to map[string]interface{}
|
||||
func (s JSONEtcdSerializer) Deserialize(d []byte, m *any) (err error) {
|
||||
err = json.Unmarshal(d, &m)
|
||||
if err != nil {
|
||||
fmt.Printf("JSONSerializer.deserialize() Error: %v", err)
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GobEtcdSerializer uses gob package to encode the session map
|
||||
type GobEtcdSerializer struct{}
|
||||
|
||||
// Serialize using gob
|
||||
func (s GobEtcdSerializer) Serialize(m any) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
err := enc.Encode(m)
|
||||
if err == nil {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Deserialize back to map[interface{}]interface{}
|
||||
func (s GobEtcdSerializer) Deserialize(d []byte, m any) error {
|
||||
dec := gob.NewDecoder(bytes.NewBuffer(d))
|
||||
return dec.Decode(&m)
|
||||
}
|
||||
|
||||
type EtcdHandler struct {
|
||||
*clientv3.Client
|
||||
serializer EtcdSerializer
|
||||
}
|
||||
|
||||
func NewEtcdHandler(cfg *viper.Viper) (*EtcdHandler, error) {
|
||||
var (
|
||||
endpoints = cfg.GetStringSlice("storage.kv.etcd.endpoints")
|
||||
prefix = cfg.GetString("storage.kv.etcd.prefix")
|
||||
)
|
||||
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
DialTimeout: 5 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cli.KV = namespace.NewKV(cli.KV, prefix)
|
||||
cli.Watcher = namespace.NewWatcher(cli.Watcher, prefix)
|
||||
cli.Lease = namespace.NewLease(cli.Lease, prefix)
|
||||
|
||||
return &EtcdHandler{
|
||||
Client: cli,
|
||||
serializer: JSONEtcdSerializer{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *EtcdHandler) Put(k string, v any) error {
|
||||
data, err := s.serializer.Serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// _, err = s.Client.KV.Put(context.TODO(), k, data.String())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err = s.Client.KV.Put(ctx, k, string(data))
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EtcdHandler) PutWithTTL(k string, v any, duration time.Duration) error {
|
||||
lease, err := s.Client.Lease.Grant(context.TODO(), int64(duration.Seconds()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := s.serializer.Serialize(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// _, err = s.Client.KV.Put(context.TODO(), k, data.String(), clientv3.WithLease(lease.ID))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err = s.Client.KV.Put(ctx, k, string(data), clientv3.WithLease(lease.ID))
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EtcdHandler) Get(k string) (any, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
resp, err := s.Client.KV.Get(ctx, k)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range resp.Kvs {
|
||||
var data any
|
||||
err := s.serializer.Deserialize([]byte(v.Value), &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We return directly as we want to last revision of value
|
||||
return data, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no value %v", k)
|
||||
}
|
||||
|
||||
func (s *EtcdHandler) Delete(k string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
_, err := s.Client.KV.Delete(ctx, k)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
PREFIX_BENEFICIARIES = "beneficiaries"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
Key string
|
||||
FileName string
|
||||
LastModified time.Time
|
||||
ContentType string
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
type FileStorage interface {
|
||||
Put(reader io.Reader, prefix string, filename string, size int64, metadata map[string]string) error
|
||||
List(prefix string) []FileInfo
|
||||
Get(prefix string, file string) (io.Reader, *FileInfo, error)
|
||||
}
|
||||
|
||||
func NewFileStorage(cfg *viper.Viper) (FileStorage, error) {
|
||||
return NewMinioStorageHandler(cfg)
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type KVHandler interface {
|
||||
Put(k string, v any) error
|
||||
PutWithTTL(k string, v any, duration time.Duration) error
|
||||
Get(k string) (any, error)
|
||||
Delete(k string) error
|
||||
}
|
||||
|
||||
func NewKVHandler(cfg *viper.Viper) (KVHandler, error) {
|
||||
return NewEtcdHandler(cfg)
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type MinioStorageHandler struct {
|
||||
*minio.Client
|
||||
|
||||
BucketName string
|
||||
}
|
||||
|
||||
func NewMinioStorageHandler(cfg *viper.Viper) (*MinioStorageHandler, error) {
|
||||
minioClient, err := minio.New(cfg.GetString("storage.files.minio.endpoint"), &minio.Options{
|
||||
Creds: credentials.NewStaticV4(cfg.GetString("storage.files.minio.access_key"), cfg.GetString("storage.files.minio.secret_key"), ""),
|
||||
Secure: cfg.GetBool("storage.files.minio.use_ssl"),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &MinioStorageHandler{
|
||||
Client: minioClient,
|
||||
BucketName: cfg.GetString("storage.files.minio.bucket_name"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *MinioStorageHandler) Put(reader io.Reader, prefix string, filename string, size int64, metadata map[string]string) error {
|
||||
s.Client.PutObject(context.TODO(), s.BucketName, prefix+"/"+filename, reader, size, minio.PutObjectOptions{
|
||||
|
||||
UserMetadata: metadata,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MinioStorageHandler) List(prefix string) []FileInfo {
|
||||
res := []FileInfo{}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
objectCh := s.Client.ListObjects(ctx, s.BucketName, minio.ListObjectsOptions{
|
||||
Prefix: prefix,
|
||||
Recursive: true,
|
||||
})
|
||||
|
||||
for object := range objectCh {
|
||||
if object.Err != nil {
|
||||
fmt.Println("Error : ", object.Err)
|
||||
continue
|
||||
}
|
||||
|
||||
objinfo, err := s.Client.StatObject(context.Background(), s.BucketName, object.Key, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
path := strings.Split(object.Key, "/")
|
||||
|
||||
fileinfo := FileInfo{
|
||||
Key: object.Key,
|
||||
FileName: path[len(path)-1],
|
||||
LastModified: object.LastModified,
|
||||
ContentType: object.ContentType,
|
||||
Metadata: objinfo.UserMetadata,
|
||||
}
|
||||
|
||||
res = append(res, fileinfo)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *MinioStorageHandler) Get(prefix string, file string) (io.Reader, *FileInfo, error) {
|
||||
object, err := s.Client.GetObject(context.Background(), s.BucketName, prefix+"/"+file, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
objinfo, err := s.Client.StatObject(context.Background(), s.BucketName, prefix+"/"+file, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
path := strings.Split(objinfo.Key, "/")
|
||||
fileinfo := &FileInfo{
|
||||
Key: objinfo.Key,
|
||||
FileName: path[len(path)-1],
|
||||
LastModified: objinfo.LastModified,
|
||||
ContentType: objinfo.ContentType,
|
||||
Metadata: objinfo.UserMetadata,
|
||||
}
|
||||
|
||||
return object, fileinfo, nil
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base32"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
// Amount of time for cookies/kv keys to expire.
|
||||
var sessionExpire = 86400 * 30
|
||||
|
||||
type SessionStore struct {
|
||||
KV KVHandler
|
||||
Codecs []securecookie.Codec
|
||||
options *sessions.Options // default configuration
|
||||
DefaultMaxAge int // default TiKV TTL for a MaxAge == 0 session
|
||||
//maxLength int
|
||||
keyPrefix string
|
||||
//serializer SessionSerializer
|
||||
}
|
||||
|
||||
func NewSessionStore(client KVHandler, keyPairs ...[]byte) *SessionStore {
|
||||
es := &SessionStore{
|
||||
KV: client,
|
||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||
options: &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: sessionExpire,
|
||||
},
|
||||
DefaultMaxAge: sessionExpire, // 20 minutes seems like a reasonable default
|
||||
//maxLength: 4096,
|
||||
keyPrefix: "session/",
|
||||
}
|
||||
|
||||
return es
|
||||
}
|
||||
|
||||
func (s *SessionStore) Get(r *http.Request, name string) (*sessions.Session, error) {
|
||||
// session := sessions.NewSession(s, name)
|
||||
// ok, err := s.load(r.Context(), session)
|
||||
// if !(err == nil && ok) {
|
||||
// if err == nil {
|
||||
// err = errors.New("key does not exist")
|
||||
// }
|
||||
// }
|
||||
// return session, err
|
||||
return sessions.GetRegistry(r).Get(s, name)
|
||||
}
|
||||
|
||||
func (s *SessionStore) New(r *http.Request, name string) (*sessions.Session, error) {
|
||||
session := sessions.NewSession(s, name)
|
||||
options := *s.options
|
||||
session.Options = &options
|
||||
session.IsNew = true
|
||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
||||
err := securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
|
||||
if err != nil {
|
||||
return session, err
|
||||
}
|
||||
ok, err := s.load(r.Context(), session)
|
||||
session.IsNew = !(err == nil && ok) // not new if no error and data available
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Save adds a single session to the response.
|
||||
func (s *SessionStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
|
||||
// Marked for deletion.
|
||||
if session.Options.MaxAge <= 0 {
|
||||
if err := s.delete(r.Context(), session); err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
|
||||
} else {
|
||||
// Build an alphanumeric key for the kv store.
|
||||
if session.ID == "" {
|
||||
session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
|
||||
}
|
||||
if err := s.save(r.Context(), session); err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// save stores the session in kv.
|
||||
func (s *SessionStore) save(ctx context.Context, session *sessions.Session) error {
|
||||
m := make(map[string]interface{}, len(session.Values))
|
||||
for k, v := range session.Values {
|
||||
ks, ok := k.(string)
|
||||
if !ok {
|
||||
err := fmt.Errorf("non-string key value, cannot serialize session: %v", k)
|
||||
return err
|
||||
}
|
||||
m[ks] = v
|
||||
}
|
||||
|
||||
age := session.Options.MaxAge
|
||||
if age == 0 {
|
||||
age = s.DefaultMaxAge
|
||||
}
|
||||
|
||||
return s.KV.PutWithTTL(s.keyPrefix+session.ID, m, time.Duration(age)*time.Second)
|
||||
}
|
||||
|
||||
// load reads the session from kv store.
|
||||
// returns true if there is a sessoin data in DB
|
||||
func (s *SessionStore) load(ctx context.Context, session *sessions.Session) (bool, error) {
|
||||
|
||||
data, err := s.KV.Get(s.keyPrefix + session.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if data == nil && err == nil {
|
||||
return false, errors.New("key does not exist")
|
||||
}
|
||||
|
||||
for k, v := range data.(map[string]any) {
|
||||
session.Values[k] = v
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// delete removes keys from tikv if MaxAge<0
|
||||
func (s *SessionStore) delete(ctx context.Context, session *sessions.Session) error {
|
||||
if err := s.KV.Delete(s.keyPrefix + session.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue