commit 5f63e312c9f5740c635b084083cec011a14315ee Author: Arnaud Delcasse Date: Tue Apr 29 06:31:39 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9df9aa5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +config.yaml +.env +.vscode +__debug_bin diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6623929 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM git.coopgo.io/coopgo-platform/libvalhalla-go:main AS builder + +# Install Golang +RUN curl -LO https://go.dev/dl/go1.23.4.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.23.4.linux-amd64.tar.gz + +ARG ACCESS_TOKEN_USR="nothing" +ARG ACCESS_TOKEN_PWD="nothing" + +RUN printf "machine git.coopgo.io\n\ + login ${ACCESS_TOKEN_USR}\n\ + password ${ACCESS_TOKEN_PWD}\n"\ + >> ~/.netrc +RUN chmod 600 ~/.netrc + +RUN ls . + +COPY . . + +ENV PATH=$PATH:/usr/local/go/bin + +RUN go mod download & CGO_ENABLED=1 go build -o /server +#RUN go build -a -ldflags "-linkmode external -extldflags '-static' -s -w" -o /server +#RUN go mod download & CGO_ENABLED=1 GOOS=linux go build -x -ldflags '-extldflags "-L/usr/local/lib/"' -o /server + +FROM alpine:latest + +RUN apk add --no-cache ca-certificates tzdata +COPY --from=builder /usr/local/lib/libvalhalla-go.so /usr/local/lib +COPY --from=builder /server / + +EXPOSE 8080 +ENTRYPOINT ["/server"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ca5512 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# COOPGO multimodal routing service + +COOPGO Multimodal routing service handles routing through different mobility modes : + +- [X] Public transit +- [X] Carpool +- [X] Walking +- [X] Driving + +## Notes on public transit + +Public transit routing is built with [Valhalla](https://github.com/valhalla/valhalla) routing engine. In the future, we may implement our own. + +Valhalla C++ library (libvalhalla.so) is embedded within Go using the CGO. + +Bindings are available in [libs/transit/valhalla/bindings](libs/transit/valhalla/bindings). This is largely inspired by [https://github.com/pufferffish/valhalla-go](https://github.com/pufferffish/valhalla-go), except that we build with Docker and integrate it directly within the project. + diff --git a/config.go b/config.go new file mode 100644 index 0000000..9c8e25d --- /dev/null +++ b/config.go @@ -0,0 +1,41 @@ +package main + +import ( + "strings" + + "github.com/spf13/viper" +) + +func ReadConfig() (*viper.Viper, error) { + defaults := map[string]any{ + "name": "COOPGO Multimodal Routing", + "dev_env": false, + "modes": map[string]any{ + "carpool": map[string]any{ + "enabled": true, + "operators": []map[string]any{}, + }, + }, + "services": map[string]any{ + "grpc": map[string]any{ + "enable": true, + "port": 8080, + }, + "http": map[string]any{ + "enable": false, + "port": 80, + }, + }, + } + + v := viper.New() + for key, value := range defaults { + v.SetDefault(key, value) + } + v.SetConfigName("config") + v.AddConfigPath(".") + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.AutomaticEnv() + err := v.ReadInConfig() + return v, err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..836cbe2 --- /dev/null +++ b/go.mod @@ -0,0 +1,43 @@ +module git.coopgo.io/coopgo-platform/multimodal-routing + +go 1.23.3 + +require ( + git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20240919052743-201e803c6a4e // indirect + git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230164352-d46c349d51d7 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/schema v1.2.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/paulmach/orb v0.9.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rs/zerolog v1.33.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.mongodb.org/mongo-driver v1.11.1 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.20.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/grpc v1.68.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6defe54 --- /dev/null +++ b/go.sum @@ -0,0 +1,165 @@ +git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20240919052743-201e803c6a4e h1:grG21iRInke5CjRyHs9I+38ZVeDXRG82xB654+vqeVc= +git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0-20240919052743-201e803c6a4e/go.mod h1:c9aJwNtY4PJuqAFYZ9afnx46UAZtWJ3P8ICZM02/DBA= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230105518-1d3838339ae6 h1:8vCeARJDo7GUhN4wLiQQeKETssGpjhewMnPuzhZfFE4= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230105518-1d3838339ae6/go.mod h1:VH5hNBKEZQlr1cgFd7sDMh9iGtoaINbyDmT++sUkTx0= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230113846-3c63ee618ed8 h1:DlPSwmrt805bn506sL7BVH382QNSuJIl1SCsGE3i754= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230113846-3c63ee618ed8/go.mod h1:VH5hNBKEZQlr1cgFd7sDMh9iGtoaINbyDmT++sUkTx0= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230132247-cad8e7ac3e3f h1:PPQmjthw6QrPjCHifbNj+ePC1nWkKUVulzK1+Nqofa4= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230132247-cad8e7ac3e3f/go.mod h1:VH5hNBKEZQlr1cgFd7sDMh9iGtoaINbyDmT++sUkTx0= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230143710-0139ebdf72ce h1:XTsveayxA6DPtDWkTYdSCCYaCEBxi9rbdclZgfyPSrw= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230143710-0139ebdf72ce/go.mod h1:VH5hNBKEZQlr1cgFd7sDMh9iGtoaINbyDmT++sUkTx0= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230161647-11405e2a9959 h1:GmWcNby1OTaKUnmayrBtvFHkQIvAbgNYdlYtLBsU/ck= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230161647-11405e2a9959/go.mod h1:VH5hNBKEZQlr1cgFd7sDMh9iGtoaINbyDmT++sUkTx0= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230163259-b858b0fb1932 h1:W644/JN5JdS2HZWX1wFsR2PsFc2zlaB5ET1x80/l4ds= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230163259-b858b0fb1932/go.mod h1:VH5hNBKEZQlr1cgFd7sDMh9iGtoaINbyDmT++sUkTx0= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230164352-d46c349d51d7 h1:yPiQrN0BxU+tLsDKojZv3sI/A3RtnrSd7hOJhBHZyqg= +git.coopgo.io/coopgo-platform/libvalhalla-go v0.0.0-20241230164352-d46c349d51d7/go.mod h1:VH5hNBKEZQlr1cgFd7sDMh9iGtoaINbyDmT++sUkTx0= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/paulmach/orb v0.9.0 h1:MwA1DqOKtvCgm7u9RZ/pnYejTeDJPnr0+0oFajBbJqk= +github.com/paulmach/orb v0.9.0/go.mod h1:SudmOk85SXtmXAB3sLGyJ6tZy/8pdfrV0o6ef98Xc30= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8= +go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handlers/carpool.go b/handlers/carpool.go new file mode 100644 index 0000000..b094677 --- /dev/null +++ b/handlers/carpool.go @@ -0,0 +1,103 @@ +package handlers + +import ( + "errors" + "fmt" + "sync" + "time" + + "git.coopgo.io/coopgo-platform/multimodal-routing/libs/carpool" + "github.com/paulmach/orb" + "github.com/paulmach/orb/geojson" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +type CarpoolRoutingHandler struct { + OperatorAPIs []carpool.CarpoolOperatorAPI +} + +func NewCarpoolRoutingHandler(cfg *viper.Viper) (*CarpoolRoutingHandler, error) { + operatorApis := []carpool.CarpoolOperatorAPI{} + operators := cfg.Get("operators").([]any) + for _, operator := range operators { + o := operator.(map[string]any) + operatorType, ok := o["type"].(string) + if !ok { + return nil, errors.New("missing operator type") + } + if operatorType == "blablacardaily" { + operatorId, ok := o["operator_id"].(string) + if !ok { + return nil, errors.New("missing operator_id") + } + apiKey, ok := o["api_key"].(string) + if !ok { + return nil, errors.New("missing api_key") + } + baseUrl, ok := o["base_url"].(string) + if !ok { + return nil, errors.New("missing base_url") + } + operatorapi, err := carpool.NewBBCDailyCarpoolAPI(operatorId, apiKey, baseUrl) + if err != nil { + return nil, fmt.Errorf("could not create Blablacar Daily API: %w", err) + } + operatorApis = append(operatorApis, operatorapi) + } + } + return &CarpoolRoutingHandler{ + OperatorAPIs: operatorApis, + }, nil +} + +// TODO add options (WithTimeDelta, etc...) +func (h CarpoolRoutingHandler) Search(results chan *geojson.FeatureCollection, wg *sync.WaitGroup, departure geojson.Feature, destination geojson.Feature, departureDate time.Time) error { + defer wg.Done() + + defaultTimeDelta := 3600 * time.Second + defaultDepartureRadius := int64(10) + defaultDestinationRadius := int64(10) + defaultCount := int64(10) + + var wg2 sync.WaitGroup + + for _, api := range h.OperatorAPIs { + wg2.Add(1) + + go func(results chan *geojson.FeatureCollection, wg2 *sync.WaitGroup) { + defer wg2.Done() + r, err := api.GetDriverJourneys(departure.Point().Lat(), departure.Point().Lon(), destination.Point().Lat(), destination.Point().Lon(), departureDate, &defaultTimeDelta, defaultDepartureRadius, defaultDestinationRadius, defaultCount) + if err != nil { + log.Error().Err(err).Str("operator", api.GetOperatorId()).Msg("error in carpool api request") + } + for _, journey := range r { + // Departure + geo := geojson.NewFeatureCollection() + dep := geojson.NewFeature(orb.Point{journey.PassengerPickupLng, journey.PassengerPickupLat}) + dep.Properties["type"] = "departure" + dep.Properties["label"] = journey.PassengerPickupAddress + + // TODO Polyline to GeoJSON LineString + + // Destination + dest := geojson.NewFeature(orb.Point{journey.PassengerDropLng, journey.PassengerDropLat}) + dest.Properties["type"] = "destination" + dest.Properties["label"] = journey.PassengerDropAddress + + // Carpool GeoJSON definition + geo.Features = append(geo.Features, dep) + geo.Features = append(geo.Features, dest) + geo.ExtraMembers = geojson.Properties{ + "journey_type": "carpool", + "ocss": journey, + } + results <- geo + } + }(results, &wg2) + } + + wg2.Wait() + + return nil +} diff --git a/handlers/handlers.go b/handlers/handlers.go new file mode 100644 index 0000000..5ce704b --- /dev/null +++ b/handlers/handlers.go @@ -0,0 +1,47 @@ +package handlers + +import ( + "fmt" + "sync" + "time" + + "github.com/paulmach/orb/geojson" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" +) + +type MultimodalRoutingHandler struct { + Carpool *CarpoolRoutingHandler +} + +func NewMultimodalRoutingHandler(cfg *viper.Viper) (*MultimodalRoutingHandler, error) { + carpoolHandler, err := NewCarpoolRoutingHandler(cfg.Sub("carpool")) + if err != nil { + return nil, fmt.Errorf("could not initialize carpool handler : %w", err) + } + return &MultimodalRoutingHandler{ + Carpool: carpoolHandler, + }, nil +} + +func (h *MultimodalRoutingHandler) Search(departure geojson.Feature, destination geojson.Feature, departureDate time.Time) ([]*geojson.FeatureCollection, error) { + ch := make(chan *geojson.FeatureCollection) + journeys := []*geojson.FeatureCollection{} + + var wg sync.WaitGroup + + // Carpool + wg.Add(1) + go h.Carpool.Search(ch, &wg, departure, destination, departureDate) + + go func() { + wg.Wait() + close(ch) + }() + + for journey := range ch { + log.Debug().Any("journey", journey).Msg("Received from channel") + journeys = append(journeys, journey) + } + return journeys, nil +} diff --git a/handlers/transit.go b/handlers/transit.go new file mode 100644 index 0000000..0181d1b --- /dev/null +++ b/handlers/transit.go @@ -0,0 +1,29 @@ +package handlers + +// import ( +// "sync" +// "time" +// +// "git.coopgo.io/coopgo-platform/multimodal-routing/libs/transit" +// "github.com/paulmach/orb/geojson" +// "github.com/spf13/viper" +// ) +// +// type TransitHandler struct { +// Routing *transit.TransitRouting +// } +// +// func NewTransitHandler(cfg *viper.Viper) (*TransitHandler, error) { +// routing, err := transit.NewTransitRouting(cfg) +// if err != nil { +// return nil, err +// } +// return &TransitHandler{ +// Routing: routing, +// }, nil +// } +// +// func (h TransitHandler) Search(results chan *geojson.FeatureCollection, wg *sync.WaitGroup, departure geojson.Feature, destination geojson.Feature, departureDate time.Time) error { +// defer wg.Done() +// return nil +// } diff --git a/libs/carpool/blablacardaily.go b/libs/carpool/blablacardaily.go new file mode 100644 index 0000000..fdb152c --- /dev/null +++ b/libs/carpool/blablacardaily.go @@ -0,0 +1,192 @@ +package carpool + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "time" + + "git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss" + "github.com/google/uuid" + "github.com/rs/zerolog/log" +) + +type BBCDailyCarpoolAPI struct { + OperatorId string + APIKey string + BaseURL string +} + +func NewBBCDailyCarpoolAPI(operatorId string, api_key string, baseURL string) (*BBCDailyCarpoolAPI, error) { + return &BBCDailyCarpoolAPI{ + OperatorId: operatorId, + APIKey: api_key, + BaseURL: baseURL, + }, nil +} + +type BBCDailyResult struct { + ID *string `json:"id"` + Duration *time.Duration `json:"duration"` + Distance *int64 `json:"distance"` + PickupLatitude *float64 `json:"pickup_latitude"` + PickupLongitude *float64 `json:"pickup_longitude"` + PickupDatetime time.Time `json:"pickup_datetime"` + DropoffLatitude *float64 `json:"dropoff_latitude"` + DropoffLongitude *float64 `json:"dropoff_longitude"` + DropoffDatetime *string `json:"dropoff_datetime"` + WebURL *string `json:"web_url"` + DepartureToPickupWalkingTime *time.Duration `json:"departure_to_pickup_walking_time"` + DropoffToArrivalWalkingTime *time.Duration `json:"dropoff_to_arrival_walking_time"` + JourneyPolyline *string `json:"journey_polyline"` + Price *struct { + Currency string `json:"currency"` + Amount float64 `json:"amount"` + } `json:"price"` + AvailableSeats *int64 `json:"available_seats"` +} + +func (api *BBCDailyCarpoolAPI) GetOperatorId() string { + return api.OperatorId +} + +func (api *BBCDailyCarpoolAPI) GetDriverJourneys( + departureLat float64, + departureLng float64, + arrivalLat float64, + arrivalLng float64, + departureDate time.Time, + timeDelta *time.Duration, + departureRadius int64, + arrivalRadius int64, + count int64, +) ([]ocss.DriverJourney, error) { + td := 1 * time.Hour + if timeDelta != nil { + td = *timeDelta + } + results, err := blablacarDailySearch( + api.BaseURL+"/search", + api.APIKey, + departureLat, + departureLng, + arrivalLat, + arrivalLng, + departureDate, + td, + ) + if err != nil { + log.Error().Err(err).Msg("error in blablacarDailySearch") + return nil, err + } + + journeys := []ocss.DriverJourney{} + + for _, r := range results { + var price *ocss.Price + if r.Price != nil { + paying := ocss.Paying + price = &ocss.Price{ + Type: &paying, + Amount: &r.Price.Amount, + Currency: &r.Price.Currency, + } + } + driverJourney := ocss.DriverJourney{ + DriverTrip: ocss.DriverTrip{ + Driver: ocss.User{ + ID: uuid.NewString(), + Operator: api.OperatorId, + Alias: "Utilisateur BlablacarDaily", + }, + DepartureToPickupWalkingDuration: r.DepartureToPickupWalkingTime, + DropoffToArrivalWalkingDuration: r.DropoffToArrivalWalkingTime, + Trip: ocss.Trip{ + Operator: api.OperatorId, + PassengerPickupLat: nilCheck(r.PickupLatitude), + PassengerPickupLng: nilCheck(r.PickupLongitude), + PassengerDropLat: nilCheck(r.DropoffLatitude), + PassengerDropLng: nilCheck(r.DropoffLongitude), + Duration: nilCheck(r.Duration), + Distance: r.Distance, + JourneyPolyline: r.JourneyPolyline, + }, + }, + JourneySchedule: ocss.JourneySchedule{ + ID: r.ID, + PassengerPickupDate: ocss.OCSSTime(r.PickupDatetime), + WebUrl: r.WebURL, + }, + AvailableSteats: r.AvailableSeats, + Price: price, + } + journeys = append(journeys, driverJourney) + } + return journeys, nil +} + +func (api *BBCDailyCarpoolAPI) GetPassengerJourneys( + departureLat float64, + departureLng float64, + arrivalLat float64, + arrivalLng float64, + departureDate time.Time, + timeDelta *time.Duration, + departureRadius int64, + arrivalRadius int64, + count int64, +) ([]ocss.PassengerJourney, error) { + return nil, errors.New("not implemented") +} + +func blablacarDailySearch(url string, access_token string, departure_latitude float64, departure_longitude float64, arrival_latitude float64, arrival_longitude float64, departure_time time.Time, departure_timedelta time.Duration) ([]BBCDailyResult, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Error().Err(err).Msg("new request issue") + return nil, err + } + req.Header.Set("Content-Type", "application/json") + + q := req.URL.Query() + q.Add("access_token", access_token) + q.Add("departure_latitude", fmt.Sprintf("%f", departure_latitude)) + q.Add("departure_longitude", fmt.Sprintf("%f", departure_longitude)) + q.Add("arrival_latitude", fmt.Sprintf("%f", arrival_latitude)) + q.Add("arrival_longitude", fmt.Sprintf("%f", arrival_longitude)) + q.Add("departure_epoch", strconv.FormatInt(departure_time.Unix(), 10)) + q.Add("departure_timedelta", fmt.Sprintf("%v", departure_timedelta.Abs().Seconds())) + req.URL.RawQuery = q.Encode() + + log.Debug().Str("url", req.URL.String()).Msg("Request to BBCDaily") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Error().Err(err).Msg("error in BBCDaily request") + return nil, err + } + + response := []BBCDailyResult{} + + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + body, err2 := io.ReadAll(resp.Body) + if err2 != nil { + log.Error().Err(err2).Msg("error reading json string") + } + log.Error().Err(err).Any("resp body", body).Msg("cannot read json response to blablacardaily API") + return nil, err + } + + return response, nil +} + +func nilCheck[T any](a *T) T { + var t T + if a == nil { + return t + } + return *a +} diff --git a/libs/carpool/carpool.go b/libs/carpool/carpool.go new file mode 100644 index 0000000..4753bee --- /dev/null +++ b/libs/carpool/carpool.go @@ -0,0 +1,13 @@ +package carpool + +import ( + "time" + + "git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss" +) + +type CarpoolOperatorAPI interface { + GetOperatorId() string + GetDriverJourneys(departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureDate time.Time, timeDelta *time.Duration, departureRadius int64, arrivalRadius int64, count int64) ([]ocss.DriverJourney, error) + GetPassengerJourneys(departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureDate time.Time, timeDelta *time.Duration, departureRadius int64, arrivalRadius int64, count int64) ([]ocss.PassengerJourney, error) +} diff --git a/libs/transit/transit.go b/libs/transit/transit.go new file mode 100644 index 0000000..2146b68 --- /dev/null +++ b/libs/transit/transit.go @@ -0,0 +1,23 @@ +package transit + +import ( + "fmt" + + valhalla "git.coopgo.io/coopgo-platform/libvalhalla-go" + "github.com/spf13/viper" +) + +type TransitRouting struct { + Valhalla *valhalla.Actor +} + +func NewTransitRouting(cfg *viper.Viper) (*TransitRouting, error) { + config := valhalla.DefaultConfig() + actor, err := valhalla.NewActorFromConfig(config) + if err != nil { + return nil, fmt.Errorf("could not initiate valhalla library : %w", err) + } + return &TransitRouting{ + Valhalla: actor, + }, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..2886414 --- /dev/null +++ b/main.go @@ -0,0 +1,47 @@ +//go:build cgo + +package main + +import ( + "C" + "os" + + "git.coopgo.io/coopgo-platform/multimodal-routing/handlers" + grpcserver "git.coopgo.io/coopgo-platform/multimodal-routing/servers/grpc/server" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + cfg, err := ReadConfig() + if err != nil { + log.Error().Err(err).Msg("issue reading config file") + } + + var ( + service_name = cfg.GetString("name") + dev_env = cfg.GetBool("dev_env") + grpc_enable = cfg.GetBool("services.grpc.enable") + ) + + if dev_env { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + } + + handler, err := handlers.NewMultimodalRoutingHandler(cfg.Sub("modes")) + if err != nil { + log.Fatal().Err(err).Msg("error creating handlers") + return + } + + log.Info().Str("service_name", service_name).Msg("Running service") + + failed := make(chan error) + if grpc_enable { + grpcserver.Run(failed, cfg, handler) + } + + err = <-failed + log.Fatal().Err(err).Str("service_name", service_name).Msg("Terminating service") +} diff --git a/servers/grpc/proto/geojson.pb.go b/servers/grpc/proto/geojson.pb.go new file mode 100644 index 0000000..5ab796c --- /dev/null +++ b/servers/grpc/proto/geojson.pb.go @@ -0,0 +1,208 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.1 +// protoc v4.24.4 +// source: geojson.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type FeatureCollection struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Serialized string `protobuf:"bytes,1,opt,name=serialized,proto3" json:"serialized,omitempty"` +} + +func (x *FeatureCollection) Reset() { + *x = FeatureCollection{} + if protoimpl.UnsafeEnabled { + mi := &file_geojson_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FeatureCollection) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FeatureCollection) ProtoMessage() {} + +func (x *FeatureCollection) ProtoReflect() protoreflect.Message { + mi := &file_geojson_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FeatureCollection.ProtoReflect.Descriptor instead. +func (*FeatureCollection) Descriptor() ([]byte, []int) { + return file_geojson_proto_rawDescGZIP(), []int{0} +} + +func (x *FeatureCollection) GetSerialized() string { + if x != nil { + return x.Serialized + } + return "" +} + +type Feature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Serialized string `protobuf:"bytes,1,opt,name=serialized,proto3" json:"serialized,omitempty"` +} + +func (x *Feature) Reset() { + *x = Feature{} + if protoimpl.UnsafeEnabled { + mi := &file_geojson_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Feature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Feature) ProtoMessage() {} + +func (x *Feature) ProtoReflect() protoreflect.Message { + mi := &file_geojson_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Feature.ProtoReflect.Descriptor instead. +func (*Feature) Descriptor() ([]byte, []int) { + return file_geojson_proto_rawDescGZIP(), []int{1} +} + +func (x *Feature) GetSerialized() string { + if x != nil { + return x.Serialized + } + return "" +} + +var File_geojson_proto protoreflect.FileDescriptor + +var file_geojson_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x67, 0x65, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x33, 0x0a, 0x11, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x07, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x42, + 0x45, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x2e, 0x63, 0x6f, 0x6f, 0x70, 0x67, 0x6f, 0x2e, 0x69, 0x6f, + 0x2f, 0x63, 0x6f, 0x6f, 0x70, 0x67, 0x6f, 0x2d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x2f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x2d, 0x72, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x2f, 0x67, 0x72, 0x70, 0x63, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_geojson_proto_rawDescOnce sync.Once + file_geojson_proto_rawDescData = file_geojson_proto_rawDesc +) + +func file_geojson_proto_rawDescGZIP() []byte { + file_geojson_proto_rawDescOnce.Do(func() { + file_geojson_proto_rawDescData = protoimpl.X.CompressGZIP(file_geojson_proto_rawDescData) + }) + return file_geojson_proto_rawDescData +} + +var file_geojson_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_geojson_proto_goTypes = []interface{}{ + (*FeatureCollection)(nil), // 0: FeatureCollection + (*Feature)(nil), // 1: Feature +} +var file_geojson_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_geojson_proto_init() } +func file_geojson_proto_init() { + if File_geojson_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_geojson_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FeatureCollection); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_geojson_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Feature); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_geojson_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_geojson_proto_goTypes, + DependencyIndexes: file_geojson_proto_depIdxs, + MessageInfos: file_geojson_proto_msgTypes, + }.Build() + File_geojson_proto = out.File + file_geojson_proto_rawDesc = nil + file_geojson_proto_goTypes = nil + file_geojson_proto_depIdxs = nil +} diff --git a/servers/grpc/proto/geojson.proto b/servers/grpc/proto/geojson.proto new file mode 100644 index 0000000..7f67c3b --- /dev/null +++ b/servers/grpc/proto/geojson.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +option go_package = "git.coopgo.io/coopgo-platform/multimodal-routing/servers/grpc/proto"; + +// import "google/protobuf/timestamp.proto"; + +message FeatureCollection { + string serialized = 1; + } + +message Feature { + string serialized = 1; + } diff --git a/servers/grpc/proto/multimodal-routing.pb.go b/servers/grpc/proto/multimodal-routing.pb.go new file mode 100644 index 0000000..5462123 --- /dev/null +++ b/servers/grpc/proto/multimodal-routing.pb.go @@ -0,0 +1,264 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.1 +// protoc v4.24.4 +// source: multimodal-routing.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SearchRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Departure *Feature `protobuf:"bytes,1,opt,name=departure,proto3" json:"departure,omitempty"` + Destination *Feature `protobuf:"bytes,2,opt,name=destination,proto3" json:"destination,omitempty"` + DepartureDate *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=departure_date,json=departureDate,proto3" json:"departure_date,omitempty"` + TimeDelta *int64 `protobuf:"varint,4,opt,name=time_delta,json=timeDelta,proto3,oneof" json:"time_delta,omitempty"` +} + +func (x *SearchRequest) Reset() { + *x = SearchRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_multimodal_routing_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchRequest) ProtoMessage() {} + +func (x *SearchRequest) ProtoReflect() protoreflect.Message { + mi := &file_multimodal_routing_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchRequest.ProtoReflect.Descriptor instead. +func (*SearchRequest) Descriptor() ([]byte, []int) { + return file_multimodal_routing_proto_rawDescGZIP(), []int{0} +} + +func (x *SearchRequest) GetDeparture() *Feature { + if x != nil { + return x.Departure + } + return nil +} + +func (x *SearchRequest) GetDestination() *Feature { + if x != nil { + return x.Destination + } + return nil +} + +func (x *SearchRequest) GetDepartureDate() *timestamppb.Timestamp { + if x != nil { + return x.DepartureDate + } + return nil +} + +func (x *SearchRequest) GetTimeDelta() int64 { + if x != nil && x.TimeDelta != nil { + return *x.TimeDelta + } + return 0 +} + +type SearchResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Results []*FeatureCollection `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` +} + +func (x *SearchResponse) Reset() { + *x = SearchResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_multimodal_routing_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SearchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchResponse) ProtoMessage() {} + +func (x *SearchResponse) ProtoReflect() protoreflect.Message { + mi := &file_multimodal_routing_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SearchResponse.ProtoReflect.Descriptor instead. +func (*SearchResponse) Descriptor() ([]byte, []int) { + return file_multimodal_routing_proto_rawDescGZIP(), []int{1} +} + +func (x *SearchResponse) GetResults() []*FeatureCollection { + if x != nil { + return x.Results + } + return nil +} + +var File_multimodal_routing_proto protoreflect.FileDescriptor + +var file_multimodal_routing_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x2d, 0x72, 0x6f, 0x75, + 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0d, 0x67, 0x65, 0x6f, + 0x6a, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd9, 0x01, 0x0a, 0x0d, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x09, + 0x64, 0x65, 0x70, 0x61, 0x72, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x08, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x09, 0x64, 0x65, 0x70, 0x61, 0x72, + 0x74, 0x75, 0x72, 0x65, 0x12, 0x2a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x41, 0x0a, 0x0e, 0x64, 0x65, 0x70, 0x61, 0x72, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x64, 0x65, 0x70, 0x61, 0x72, 0x74, 0x75, 0x72, 0x65, 0x44, + 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, + 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x44, + 0x65, 0x6c, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x22, 0x3e, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x46, 0x65, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x32, 0x40, 0x0a, 0x11, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x6d, + 0x6f, 0x64, 0x61, 0x6c, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x2b, 0x0a, 0x06, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x0e, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x45, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x2e, + 0x63, 0x6f, 0x6f, 0x70, 0x67, 0x6f, 0x2e, 0x69, 0x6f, 0x2f, 0x63, 0x6f, 0x6f, 0x70, 0x67, 0x6f, + 0x2d, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x6d, + 0x6f, 0x64, 0x61, 0x6c, 0x2d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x73, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_multimodal_routing_proto_rawDescOnce sync.Once + file_multimodal_routing_proto_rawDescData = file_multimodal_routing_proto_rawDesc +) + +func file_multimodal_routing_proto_rawDescGZIP() []byte { + file_multimodal_routing_proto_rawDescOnce.Do(func() { + file_multimodal_routing_proto_rawDescData = protoimpl.X.CompressGZIP(file_multimodal_routing_proto_rawDescData) + }) + return file_multimodal_routing_proto_rawDescData +} + +var file_multimodal_routing_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_multimodal_routing_proto_goTypes = []interface{}{ + (*SearchRequest)(nil), // 0: SearchRequest + (*SearchResponse)(nil), // 1: SearchResponse + (*Feature)(nil), // 2: Feature + (*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp + (*FeatureCollection)(nil), // 4: FeatureCollection +} +var file_multimodal_routing_proto_depIdxs = []int32{ + 2, // 0: SearchRequest.departure:type_name -> Feature + 2, // 1: SearchRequest.destination:type_name -> Feature + 3, // 2: SearchRequest.departure_date:type_name -> google.protobuf.Timestamp + 4, // 3: SearchResponse.results:type_name -> FeatureCollection + 0, // 4: MultimodalRouting.Search:input_type -> SearchRequest + 1, // 5: MultimodalRouting.Search:output_type -> SearchResponse + 5, // [5:6] is the sub-list for method output_type + 4, // [4:5] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_multimodal_routing_proto_init() } +func file_multimodal_routing_proto_init() { + if File_multimodal_routing_proto != nil { + return + } + file_geojson_proto_init() + if !protoimpl.UnsafeEnabled { + file_multimodal_routing_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SearchRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_multimodal_routing_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SearchResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_multimodal_routing_proto_msgTypes[0].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_multimodal_routing_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_multimodal_routing_proto_goTypes, + DependencyIndexes: file_multimodal_routing_proto_depIdxs, + MessageInfos: file_multimodal_routing_proto_msgTypes, + }.Build() + File_multimodal_routing_proto = out.File + file_multimodal_routing_proto_rawDesc = nil + file_multimodal_routing_proto_goTypes = nil + file_multimodal_routing_proto_depIdxs = nil +} diff --git a/servers/grpc/proto/multimodal-routing.proto b/servers/grpc/proto/multimodal-routing.proto new file mode 100644 index 0000000..6ae7970 --- /dev/null +++ b/servers/grpc/proto/multimodal-routing.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option go_package = "git.coopgo.io/coopgo-platform/multimodal-routing/servers/grpc/proto"; + +import "google/protobuf/timestamp.proto"; +import "geojson.proto"; + +service MultimodalRouting { + rpc Search(SearchRequest) returns (SearchResponse) {} + } + +message SearchRequest { + Feature departure = 1; + Feature destination = 2; + google.protobuf.Timestamp departure_date = 3; + optional int64 time_delta = 4; +} + +message SearchResponse { + repeated FeatureCollection results = 1; +} diff --git a/servers/grpc/proto/multimodal-routing_grpc.pb.go b/servers/grpc/proto/multimodal-routing_grpc.pb.go new file mode 100644 index 0000000..c15094d --- /dev/null +++ b/servers/grpc/proto/multimodal-routing_grpc.pb.go @@ -0,0 +1,109 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.24.4 +// source: multimodal-routing.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + MultimodalRouting_Search_FullMethodName = "/MultimodalRouting/Search" +) + +// MultimodalRoutingClient is the client API for MultimodalRouting service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type MultimodalRoutingClient interface { + Search(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*SearchResponse, error) +} + +type multimodalRoutingClient struct { + cc grpc.ClientConnInterface +} + +func NewMultimodalRoutingClient(cc grpc.ClientConnInterface) MultimodalRoutingClient { + return &multimodalRoutingClient{cc} +} + +func (c *multimodalRoutingClient) Search(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*SearchResponse, error) { + out := new(SearchResponse) + err := c.cc.Invoke(ctx, MultimodalRouting_Search_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MultimodalRoutingServer is the server API for MultimodalRouting service. +// All implementations must embed UnimplementedMultimodalRoutingServer +// for forward compatibility +type MultimodalRoutingServer interface { + Search(context.Context, *SearchRequest) (*SearchResponse, error) + mustEmbedUnimplementedMultimodalRoutingServer() +} + +// UnimplementedMultimodalRoutingServer must be embedded to have forward compatible implementations. +type UnimplementedMultimodalRoutingServer struct { +} + +func (UnimplementedMultimodalRoutingServer) Search(context.Context, *SearchRequest) (*SearchResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Search not implemented") +} +func (UnimplementedMultimodalRoutingServer) mustEmbedUnimplementedMultimodalRoutingServer() {} + +// UnsafeMultimodalRoutingServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to MultimodalRoutingServer will +// result in compilation errors. +type UnsafeMultimodalRoutingServer interface { + mustEmbedUnimplementedMultimodalRoutingServer() +} + +func RegisterMultimodalRoutingServer(s grpc.ServiceRegistrar, srv MultimodalRoutingServer) { + s.RegisterService(&MultimodalRouting_ServiceDesc, srv) +} + +func _MultimodalRouting_Search_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SearchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MultimodalRoutingServer).Search(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MultimodalRouting_Search_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MultimodalRoutingServer).Search(ctx, req.(*SearchRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// MultimodalRouting_ServiceDesc is the grpc.ServiceDesc for MultimodalRouting service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var MultimodalRouting_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "MultimodalRouting", + HandlerType: (*MultimodalRoutingServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Search", + Handler: _MultimodalRouting_Search_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "multimodal-routing.proto", +} diff --git a/servers/grpc/server/grpc.go b/servers/grpc/server/grpc.go new file mode 100644 index 0000000..10b2400 --- /dev/null +++ b/servers/grpc/server/grpc.go @@ -0,0 +1,87 @@ +package grpcserver + +import ( + "context" + "fmt" + "net" + + "git.coopgo.io/coopgo-platform/multimodal-routing/handlers" + "git.coopgo.io/coopgo-platform/multimodal-routing/servers/grpc/proto" + "github.com/paulmach/orb/geojson" + "github.com/rs/zerolog/log" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/reflection" + "google.golang.org/grpc/status" +) + +type MultimodalRoutingServer struct { + Handler *handlers.MultimodalRoutingHandler + + proto.UnimplementedMultimodalRoutingServer +} + +func NewMultimodalRoutingServer(handler *handlers.MultimodalRoutingHandler) *MultimodalRoutingServer { + return &MultimodalRoutingServer{ + Handler: handler, + } +} + +func (s *MultimodalRoutingServer) Search(ctx context.Context, req *proto.SearchRequest) (*proto.SearchResponse, error) { + departure, err := geojson.UnmarshalFeature([]byte(req.Departure.Serialized)) + if err != nil { + log.Error().Err(err).Msg("error decoding departure") + return nil, status.Errorf(codes.Internal, "could not read departure: %w", err) + } + destination, err := geojson.UnmarshalFeature([]byte(req.Destination.Serialized)) + if err != nil { + log.Error().Err(err).Msg("error decoding destination") + return nil, status.Errorf(codes.Internal, "could not read destination: %w", err) + } + departureDate := req.DepartureDate.AsTime() + journeys, err := s.Handler.Search(*departure, *destination, departureDate) + if err != nil { + log.Error().Err(err).Any("departure", departure).Any("destination", destination).Time("departure date", departureDate).Msg("error retrieving carpools") + } + + results := []*proto.FeatureCollection{} + for _, c := range journeys { + serialized, err := c.MarshalJSON() + if err != nil { + log.Error().Err(err).Any("carpool object", c).Msg("could not serialize carpool response") + continue + } + results = append(results, &proto.FeatureCollection{Serialized: string(serialized)}) + } + + return &proto.SearchResponse{ + Results: results, + }, nil +} + +func Run(failed chan error, cfg *viper.Viper, handler *handlers.MultimodalRoutingHandler) { + var ( + dev_env = cfg.GetBool("dev_env") + address = fmt.Sprintf(":%s", cfg.GetString("services.grpc.port")) + ) + + server := grpc.NewServer() + + proto.RegisterMultimodalRoutingServer(server, NewMultimodalRoutingServer(handler)) + l, err := net.Listen("tcp", address) + if err != nil { + failed <- fmt.Errorf("error trying to listen to network port : %w", err) + return + } + + if dev_env { + log.Info().Msg("gRPC server reflexion activated") + reflection.Register(server) + } + + log.Info().Str("address", address).Msg("running gRPC server") + if err := server.Serve(l); err != nil { + failed <- fmt.Errorf("error in grpc server : %w", err) + } +}