diff --git a/.env.dev b/.env.dev index 652f4154..ec3702c4 100644 --- a/.env.dev +++ b/.env.dev @@ -8,5 +8,7 @@ MONGO_SYNCS_COLLECTION_NAME=syncs MONGO_DATABASE_NAME=chabo-api MONGO_DSN='mongodb://root:my_password@mongodb:27017/?authSource=admin&ssl=false' +POSTGRES_DSN='postgres://root:my_password@postgresql:5432/chabo-api?sslmode=disable' + SYNC_COOL_DOWN_SECONDS='300' -OPENDATA_API_URL='https://opendata.bordeaux-metropole.fr/api/records/1.0/search?dataset=previsions_pont_chaban&rows=1000&sort=-date_passage&start=0&timezone=Europe%2FParis' +OPENDATA_API_URL='https://opendata.bordeaux-metropole.fr/api/records/1.0/search?dataset=previsions_pont_chaban&rows=1000&sort=-date_passage,fermeture_a_la_circulation&start=0&timezone=Europe%2FParis' diff --git a/.gitignore b/.gitignore index e128a9d9..6b52b7ee 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,8 @@ go.work !.env.dev # IntelliJ -.idea/ \ No newline at end of file +.idea/ + +# Docker compose, ignore all data directories (except .gitkeep) +dev/**/data/* +!dev/**/data/.gitkeep \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 1dd5e9f6..988d59fd 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -10,6 +10,7 @@ "github.vscode-github-actions", "PKief.material-icon-theme", "redhat.vscode-yaml", - "ms-azuretools.vscode-docker" + "ms-azuretools.vscode-docker", + "inferrinizzard.prettier-sql-vscode" ] } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..608d3c69 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e4dc9d9..f59d5fc7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,4 +17,13 @@ "workbench.preferredDarkColorTheme": "Default Dark Modern", "workbench.iconTheme": "vs-seti", "files.autoSave": "onFocusChange", + "cSpell.words": [ + "Approximative", + "chabo", + "healthcheck", + "logrus", + "OPENDATA", + "usecases", + "vareversat" + ], } \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index 601036a7..df9272df 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -6,5 +6,6 @@ WORKDIR /app COPY go.mod go.sum ./ RUN go mod download RUN go install github.com/swaggo/swag/cmd/swag@latest +RUN go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.2 COPY . . RUN swag init -d ./internal/api/routers,./ -g main_router.go \ No newline at end of file diff --git a/compose.yml b/compose.yml index 5638f3ef..77b117b5 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,11 @@ services: app: container_name: chabo-api + depends_on: + - postgresql + - mongodb + command: > + sh -c "go run ." build: context: . dockerfile: Dockerfile.dev @@ -10,7 +15,7 @@ services: - "8080:8080" - "6060:6060" env_file: - - /.env.dev + - .env.dev mongodb: container_name: mongodb image: mongo:8.0.8 @@ -23,5 +28,26 @@ services: postgresql: container_name: postgresql image: postgres:17.4 + ports: + - "5432:5432" + environment: + POSTGRES_USER: root + POSTGRES_PASSWORD: my_password + POSTGRES_DB: chabo-api + volumes: + - postgresqldata:/var/lib/postgresql/data + pgadmin: + container_name: pgadmin + image: dpage/pgadmin4:9.2.0 + ports: + - "5050:80" environment: - POSTGRES_PASSWORD: my_password \ No newline at end of file + PGADMIN_DEFAULT_EMAIL: test@test.com + PGADMIN_DEFAULT_PASSWORD: my_password + volumes: + - pgadmindata:/var/lib/pgadmin + +volumes: + mongodata: + postgresqldata: + pgadmindata: \ No newline at end of file diff --git a/db/migrations/000001_create_syncs_table.down.sql b/db/migrations/000001_create_syncs_table.down.sql new file mode 100644 index 00000000..537e8496 --- /dev/null +++ b/db/migrations/000001_create_syncs_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS syncs; \ No newline at end of file diff --git a/db/migrations/000001_create_syncs_table.up.sql b/db/migrations/000001_create_syncs_table.up.sql new file mode 100644 index 00000000..f8cfbb68 --- /dev/null +++ b/db/migrations/000001_create_syncs_table.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE + IF NOT EXISTS syncs ( + sync_id INTEGER GENERATED ALWAYS AS IDENTITY, + item_count INTEGER NOT NULL, + duration INTEGER NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + + PRIMARY KEY(sync_id) + ); \ No newline at end of file diff --git a/db/migrations/000002_create_boats_table.down.sql b/db/migrations/000002_create_boats_table.down.sql new file mode 100644 index 00000000..3e4223b5 --- /dev/null +++ b/db/migrations/000002_create_boats_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS boats; \ No newline at end of file diff --git a/db/migrations/000002_create_boats_table.up.sql b/db/migrations/000002_create_boats_table.up.sql new file mode 100644 index 00000000..9f1452cf --- /dev/null +++ b/db/migrations/000002_create_boats_table.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE + IF NOT EXISTS boats ( + boat_id INTEGER GENERATED ALWAYS AS IDENTITY, + name TEXT UNIQUE NOT NULL, + + PRIMARY KEY(boat_id) + ); diff --git a/db/migrations/000003_create_forecasts_table.down.sql b/db/migrations/000003_create_forecasts_table.down.sql new file mode 100644 index 00000000..a7b453c1 --- /dev/null +++ b/db/migrations/000003_create_forecasts_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS forecasts; \ No newline at end of file diff --git a/db/migrations/000003_create_forecasts_table.up.sql b/db/migrations/000003_create_forecasts_table.up.sql new file mode 100644 index 00000000..9b92fd2b --- /dev/null +++ b/db/migrations/000003_create_forecasts_table.up.sql @@ -0,0 +1,11 @@ +CREATE TABLE + IF NOT EXISTS forecasts ( + forecast_id TEXT, + closing_event_name TEXT, + closing_duration_min INTEGER NOT NULL, + circulation_closing_date TIMESTAMPTZ NOT NULL, + circulation_reopening_date TIMESTAMPTZ NOT NULL, + is_traffic_fully_closed BOOLEAN NOT NULL, + + PRIMARY KEY(forecast_id) + ); \ No newline at end of file diff --git a/db/migrations/000004_create_forecasts_boats_table.down.sql b/db/migrations/000004_create_forecasts_boats_table.down.sql new file mode 100644 index 00000000..3e4223b5 --- /dev/null +++ b/db/migrations/000004_create_forecasts_boats_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS boats; \ No newline at end of file diff --git a/db/migrations/000004_create_forecasts_boats_table.up.sql b/db/migrations/000004_create_forecasts_boats_table.up.sql new file mode 100644 index 00000000..1c55d273 --- /dev/null +++ b/db/migrations/000004_create_forecasts_boats_table.up.sql @@ -0,0 +1,15 @@ +CREATE TABLE + IF NOT EXISTS forecasts_boats ( + forecast_id TEXT, + boat_id INTEGER, + is_leaving_dock BOOLEAN NOT NULL, + approximative_crossing_date TIMESTAMPTZ NOT NULL, + + PRIMARY KEY(forecast_id, boat_id), + CONSTRAINT fk_forecast + FOREIGN KEY(forecast_id) + REFERENCES forecasts(forecast_id), + CONSTRAINT fk_boat + FOREIGN KEY(boat_id) + REFERENCES boats(boat_id) + ); diff --git a/db/migrations/000005_create_static_boat_data_table.down.sql b/db/migrations/000005_create_static_boat_data_table.down.sql new file mode 100644 index 00000000..2bc58110 --- /dev/null +++ b/db/migrations/000005_create_static_boat_data_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS static_boat_data; \ No newline at end of file diff --git a/db/migrations/000005_create_static_boat_data_table.up.sql b/db/migrations/000005_create_static_boat_data_table.up.sql new file mode 100644 index 00000000..4403dd0e --- /dev/null +++ b/db/migrations/000005_create_static_boat_data_table.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE + IF NOT EXISTS static_boat_data ( + boat_id INTEGER GENERATED ALWAYS AS IDENTITY, + name TEXT UNIQUE NOT NULL, + imo INTEGER, + mmsi INTEGER, + + PRIMARY KEY(boat_id) + ); diff --git a/db/migrations/000006_populate_boat_static_data.up.sql b/db/migrations/000006_populate_boat_static_data.up.sql new file mode 100644 index 00000000..c5f20767 --- /dev/null +++ b/db/migrations/000006_populate_boat_static_data.up.sql @@ -0,0 +1,42 @@ +INSERT INTO static_boat_data (name, imo, mmsi) VALUES + ('FRIDJTOF NANSEN', 9813084, 257088070), + ('AZAMARA QUEST', 9210218, 256216000), + ('MARINA', 9438066, 538003668), + ('CRYSTAL SERENITY', 9243667, 311536000), + ('LE LYRIAL', 9704130, 578000800), + ('SPIRIT OF DISCOVERY', 9802683, 232021171), + ('BALMORAL', 8506294, 308785000), + ('EUROPA', 9183855, 215767000), + ('SILVER DAWN', 9857937, 311001044), + ('STAR PRIDE', 8707343, 311084000), + ('LE BELLOT', 9852418, 578001500), + ('PANTHERE', 8021713, NULL), + ('SILVER SPIRIT', 9437866, 311022500), + ('SPIRIT OF ADVENTURE', 9818084, 232026551), + ('HANSEATIC SPRIT', 9857640, 215973000), + ('GLYCINE', 4545838, NULL), + ('OCEANIA VISTA', 9876957, 538009952), + ('EGLANTINE', NULL, 227576220), + ('WORLD VOYAGER', 9871529, 255806150), + ('AIDASOL', 9490040, 247302900), + ('SEADREAM II', 8203440, 308311000), + ('EUROPA 2', 9616230, 229378000), + ('AZAMARA ONWARD', 9187887, 229765000), + ('JAGUAR', 1007756, 319295000), + ('ILMA', 9967586, 256343000), + ('AZAMARA JOURNEY', 9200940, 256204000), + ('ALTAIR', 9806550, 319185200), + ('SEVEN SEAS VOYAGEUR', 9247144, 311513000), + ('CHACAL', 8944355, 227801800), + ('LE BOREAL', 9502506, 578000500), + ('HANSEATIC SPIRIT', 9857640, 215973000), + ('SEVEN SEAS MARINER', 9210139, 311622000), + ('SEVEN SEAS GRANDEUR', 9877444, 538010706), + ('SEABOURN SOJOURN', 9417098, 311027100), + ('SILVER WIND', 8903935, 308814000), + ('SEUDRE DF32', NULL, 228074600), + ('AMADEA', 8913162, 308445000), + ('STAR LEGEND', 9008598, 311085000), + ('SIRENA', 9187899, 538006842) +ON CONFLICT (name) +DO NOTHING; diff --git a/db/queries/001_select_all.sql b/db/queries/001_select_all.sql new file mode 100644 index 00000000..f7b4a394 --- /dev/null +++ b/db/queries/001_select_all.sql @@ -0,0 +1,5 @@ +SELECT b.name, f.circulation_closing_date AT TIME ZONE 'Europe/Paris', f.circulation_reopening_date, fb.is_leaving +FROM boats AS b +INNER JOIN forecasts_boats AS fb ON b.boat_id = fb.boat_id +INNER JOIN forecasts AS f ON f.forecast_id = fb.forecast_id +ORDER BY f.circulation_closing_date; \ No newline at end of file diff --git a/go.mod b/go.mod index 7c75416d..7028a273 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/vareversat/chabo-api -go 1.22 +go 1.23.0 toolchain go1.24.2 @@ -8,60 +8,64 @@ require ( github.com/getsentry/sentry-go v0.32.0 github.com/getsentry/sentry-go/gin v0.32.0 github.com/gin-gonic/gin v1.10.0 + github.com/jackc/pgx/v5 v5.7.4 github.com/stretchr/testify v1.10.0 github.com/swaggo/files v1.0.1 go.mongodb.org/mongo-driver v1.17.3 ) require ( - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - golang.org/x/sync v0.10.0 // indirect + golang.org/x/sync v0.13.0 // indirect ) require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/bytedance/sonic v1.11.9 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/bytedance/sonic v1.13.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.22.0 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/sirupsen/logrus v1.9.3 github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.4 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.22.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/arch v0.16.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/tools v0.32.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 21650955..ef464499 100644 --- a/go.sum +++ b/go.sum @@ -1,68 +1,70 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= -github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= +github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= +github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= -github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= -github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= -github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= -github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4= -github.com/getsentry/sentry-go v0.31.1/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= -github.com/getsentry/sentry-go/gin v0.31.1 h1:lvOOO5j0o0IhYIXoHCmQ+D4ExhXWRCnDusV176dXWDA= -github.com/getsentry/sentry-go/gin v0.31.1/go.mod h1:iMF6gA5uO2t3KVMj4QpjLi9B0U+oMidAiHAdPcJMMdQ= github.com/getsentry/sentry-go/gin v0.32.0 h1:9wDh8sLkRNhjBra99kVFPdsTHs/9ADqMMHaW9huyIVc= github.com/getsentry/sentry-go/gin v0.32.0/go.mod h1:4iRO82BCDcQzMCBq/phv4Px22p6pvNod3OqGLTopQe4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= +github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -70,8 +72,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -81,38 +83,32 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= -github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= -github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= @@ -125,47 +121,32 @@ github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 h1:tBiBTKHnIjovYoLX/TPkcf+OjqqKGQrPtGT3Foz+Pgo= -github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76/go.mod h1:SQliXeA7Dhkt//vS29v3zpbEwoa+zb2Cn5xj5uO4K5U= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4= -go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= -go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= -go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= +golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -174,12 +155,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.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/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -188,20 +165,16 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -209,4 +182,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/api/routers/forecasts_router.go b/internal/api/routers/forecasts_router.go index cc75b945..28a1d653 100644 --- a/internal/api/routers/forecasts_router.go +++ b/internal/api/routers/forecasts_router.go @@ -4,18 +4,40 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v5/pgxpool" "github.com/vareversat/chabo-api/internal/api/controllers" "github.com/vareversat/chabo-api/internal/domains" - "github.com/vareversat/chabo-api/internal/repositories" + "github.com/vareversat/chabo-api/internal/repositories/mongodb" + "github.com/vareversat/chabo-api/internal/repositories/postgresql" "github.com/vareversat/chabo-api/internal/usecases" "go.mongodb.org/mongo-driver/mongo" ) -func ForecastsRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - forecastRepository := repositories.NewForecastRepository( +func ForecastsRouter(timeout time.Duration, db *mongo.Database, group *gin.RouterGroup) { + forecastRepository := mongodb.NewForecastRepository( db.Collection(domains.ForecastCollection), ) - syncRepository := repositories.NewSyncRepository(db.Collection(domains.SyncCollection)) + syncRepository := mongodb.NewSyncRepository(db.Collection(domains.SyncCollection)) + forecastController := &controllers.ForecastController{ + ForecastUseCase: usecases.NewForecastUseCase( + forecastRepository, + syncRepository, + timeout, + ), + } + + forecastGroup := group.Group("/forecasts") + forecastGroup.GET("", forecastController.GetAllForecasts()) + forecastGroup.GET(":id", forecastController.GetForecastByID()) + forecastGroup.GET("/today", forecastController.GetTodayForecasts()) + forecastGroup.POST("/sync", forecastController.SyncForecasts()) + forecastGroup.GET("/next", forecastController.GetNextForecast()) + forecastGroup.GET("/current", forecastController.GetCurrentForecast()) +} + +func PostgresForecastsRouter(timeout time.Duration, pool *pgxpool.Pool, group *gin.RouterGroup) { + forecastRepository := postgresql.NewForecastRepository(pool) + syncRepository := postgresql.NewSyncRepository(pool) forecastController := &controllers.ForecastController{ ForecastUseCase: usecases.NewForecastUseCase( forecastRepository, diff --git a/internal/api/routers/main_router.go b/internal/api/routers/main_router.go index ffae83c8..005cfc9e 100644 --- a/internal/api/routers/main_router.go +++ b/internal/api/routers/main_router.go @@ -8,6 +8,7 @@ import ( sentrygin "github.com/getsentry/sentry-go/gin" "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v5/pgxpool" swaggerfiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "github.com/vareversat/chabo-api/docs" @@ -29,7 +30,7 @@ var version = "undefined" // @License.name MIT // @License.url https://github.com/vareversat/chabo-api/blob/main/LICENSE.md -func MainRouter(mongoDatabase mongo.Database) { +func MainRouter(mongoDatabase *mongo.Database, postgresClient *pgxpool.Pool) { // Configure Gin web server router := gin.New() @@ -54,8 +55,10 @@ func MainRouter(mongoDatabase mongo.Database) { rootRouterGroup := router.Group(docs.SwaggerInfo.BasePath) timeout := time.Duration(30) * time.Second - ForecastsRouter(timeout, mongoDatabase, rootRouterGroup) - SyncRouter(timeout, mongoDatabase, rootRouterGroup) + //ForecastsRouter(timeout, mongoDatabase, rootRouterGroup) + PostgresForecastsRouter(timeout, postgresClient, rootRouterGroup) + //SyncRouter(timeout, mongoDatabase, rootRouterGroup) + PostgresSyncRouter(timeout, postgresClient, rootRouterGroup) SystemRouter(timeout, mongoDatabase.Client(), rootRouterGroup) // Compute the app address diff --git a/internal/api/routers/sync_router.go b/internal/api/routers/sync_router.go index 281cdd1c..fd24593f 100644 --- a/internal/api/routers/sync_router.go +++ b/internal/api/routers/sync_router.go @@ -4,15 +4,30 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v5/pgxpool" "github.com/vareversat/chabo-api/internal/api/controllers" "github.com/vareversat/chabo-api/internal/domains" - "github.com/vareversat/chabo-api/internal/repositories" + "github.com/vareversat/chabo-api/internal/repositories/mongodb" + "github.com/vareversat/chabo-api/internal/repositories/postgresql" "github.com/vareversat/chabo-api/internal/usecases" "go.mongodb.org/mongo-driver/mongo" ) -func SyncRouter(timeout time.Duration, db mongo.Database, group *gin.RouterGroup) { - syncRepository := repositories.NewSyncRepository(db.Collection(domains.SyncCollection)) +func SyncRouter(timeout time.Duration, db *mongo.Database, group *gin.RouterGroup) { + syncRepository := mongodb.NewSyncRepository(db.Collection(domains.SyncCollection)) + syncController := &controllers.SyncController{ + SyncUseCase: usecases.NewSyncUseCase( + syncRepository, + timeout, + ), + } + + syncGroup := group.Group("/syncs") + syncGroup.GET("/last", syncController.GetLastSyncAction()) +} + +func PostgresSyncRouter(timeout time.Duration, pool *pgxpool.Pool, group *gin.RouterGroup) { + syncRepository := postgresql.NewSyncRepository(pool) syncController := &controllers.SyncController{ SyncUseCase: usecases.NewSyncUseCase( syncRepository, diff --git a/internal/api/routers/system_router.go b/internal/api/routers/system_router.go index bf68ebe0..009c16a6 100644 --- a/internal/api/routers/system_router.go +++ b/internal/api/routers/system_router.go @@ -5,13 +5,13 @@ import ( "github.com/gin-gonic/gin" "github.com/vareversat/chabo-api/internal/api/controllers" - "github.com/vareversat/chabo-api/internal/repositories" + "github.com/vareversat/chabo-api/internal/repositories/mongodb" "github.com/vareversat/chabo-api/internal/usecases" "go.mongodb.org/mongo-driver/mongo" ) func SystemRouter(timeout time.Duration, mongoClient *mongo.Client, group *gin.RouterGroup) { - healthcheckRepository := repositories.NewHealthCheckRepository(mongoClient) + healthcheckRepository := mongodb.NewHealthCheckRepository(mongoClient) systemController := &controllers.SystemController{ HealthCheckUseCase: usecases.NewHealthCheckUseCase( healthcheckRepository, diff --git a/internal/domains/boat_domain.go b/internal/domains/boat_domain.go index d48b9f4a..0362b2b9 100644 --- a/internal/domains/boat_domain.go +++ b/internal/domains/boat_domain.go @@ -1,7 +1,10 @@ package domains import ( + "context" "time" + + "github.com/vareversat/chabo-api/internal/errors" ) type BoatManeuver string @@ -12,17 +15,19 @@ const ( ) type Boat struct { - Name string `json:"name" bson:"name" example:"EUROPA 2"` - Maneuver BoatManeuver `json:"maneuver" bson:"maneuver"` - CrossingDateApproximation time.Time `json:"crossing_date_approximation" bson:"crossing_date_approximation" example:"2021-05-25T00:53:16.535668Z" format:"date-time"` + Name string `json:"name" bson:"name" example:"EUROPA 2"` + IsLeavingDock bool `json:"is_leaving_dock" bson:"is_leaving_dock"` + ApproximativeCrossingDate time.Time `json:"approximative_crossing_date" bson:"approximative_crossing_date" example:"2021-05-25T00:53:16.535668Z" format:"date-time"` + IMO int64 `json:"imo" bson:"imo" example:"9616230"` + MMSI int64 `json:"mmsi" bson:"mmsi" example:"229378000"` } type Boats []Boat func (b Boat) IsEqual(other Boat) bool { return b.Name == other.Name && - b.Maneuver == other.Maneuver && - b.CrossingDateApproximation.Equal(other.CrossingDateApproximation) + b.IsLeavingDock == other.IsLeavingDock && + b.ApproximativeCrossingDate.Equal(other.ApproximativeCrossingDate) } func (boats Boats) AreEqual(other Boats) bool { @@ -37,3 +42,12 @@ func (boats Boats) AreEqual(other Boats) bool { return true } + +type BoatRepository interface { + InsertOne(ctx context.Context, boat Boat) (error, int) + GetByForecastId(ctx context.Context, forecastId string, boats *Boats) (err error) +} + +type BoatUseCase interface { + InsertOne(ctx context.Context, boat Boat) errors.CustomError +} diff --git a/internal/domains/boat_domain_test.go b/internal/domains/boat_domain_test.go index cc28aef3..3e17eb59 100644 --- a/internal/domains/boat_domain_test.go +++ b/internal/domains/boat_domain_test.go @@ -11,13 +11,13 @@ func TestBoatIsEqualOK(t *testing.T) { crossingDateApproximation, _ := time.Parse(time.RFC3339, "2023-02-26T21:00:00+01:00") boat := Boat{ Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, } otherBoat := Boat{ Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, } result := boat.IsEqual(otherBoat) @@ -28,13 +28,13 @@ func TestBoatIsEqualNOK(t *testing.T) { crossingDateApproximation, _ := time.Parse(time.RFC3339, "2023-02-26T21:00:00+01:00") boat := Boat{ Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, } otherBoat := Boat{ Name: "MY_BOAT", - Maneuver: Leaving, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, } result := boat.IsEqual(otherBoat) @@ -46,15 +46,15 @@ func TestBoatsAreEqualOK(t *testing.T) { boats := Boats{ Boat{ Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, } otherBoats := Boats{ Boat{ Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, } result := boats.AreEqual(otherBoats) @@ -67,15 +67,15 @@ func TestBoatsAreEqualNOK(t *testing.T) { boats := Boats{ Boat{ Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, } otherBoats := Boats{ Boat{ Name: "MY_BOAT2", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, } result := boats.AreEqual(otherBoats) diff --git a/internal/domains/forecast_boats_domain.go b/internal/domains/forecast_boats_domain.go new file mode 100644 index 00000000..4daf1d2e --- /dev/null +++ b/internal/domains/forecast_boats_domain.go @@ -0,0 +1,23 @@ +package domains + +import ( + "context" + "time" + + "github.com/vareversat/chabo-api/internal/errors" +) + +type ForecastsBoats struct { + ForecastID string + BoatID int + IsLeavingDock bool + ApproximativeCrossingDate time.Time +} + +type ForecastsBoatsRepository interface { + InsertOne(ctx context.Context, forecast Forecast, boat Boat, boatId int) (err error, id string) +} + +type ForecastsUseCase interface { + InsertOne(ctx context.Context, fB ForecastsBoats) (err errors.CustomError) +} diff --git a/internal/domains/forecast_domain.go b/internal/domains/forecast_domain.go index 591c40e6..6bf47dcf 100644 --- a/internal/domains/forecast_domain.go +++ b/internal/domains/forecast_domain.go @@ -13,16 +13,10 @@ var ( ForecastCollection = os.Getenv("MONGO_FORECASTS_COLLECTION_NAME") ) -type ClosingType string type ClosingReason string const ( - TwoWay ClosingType = "two_way" - OneWay ClosingType = "one_way" -) - -const ( - BoatReason ClosingReason = "boat" + BoatPassage ClosingReason = "boat_passage" Maintenance ClosingReason = "maintenance" WineFestivalBoats ClosingReason = "wine_festival_boats" SpecialEvent ClosingReason = "special_event" @@ -32,7 +26,7 @@ type Forecasts []Forecast type Forecast struct { ID string `json:"id" bson:"_id" example:"63a6430fc07ff1d895c9555ef2ef6e41c1e3b1f5"` - ClosingType ClosingType `json:"closing_type" bson:"closing_type"` + IsTrafficFullyClosed bool `json:"is_traffic_fully_closed" bson:"is_traffic_fully_closed"` ClosingDuration time.Duration `json:"closing_duration_min" bson:"closing_duration_min" example:"83" swaggertype:"primitive,integer"` CirculationClosingDate time.Time `json:"circulation_closing_date" bson:"circulation_closing_date" example:"2021-05-25T00:53:16.535668Z" format:"date-time"` CirculationReopeningDate time.Time `json:"circulation_reopening_date" bson:"circulation_reopening_date" example:"2021-05-25T00:53:16.535668Z" format:"date-time"` @@ -67,7 +61,7 @@ type ForecastMongoCountResponse struct { func (f *Forecast) IsEqual(other Forecast) bool { return f.ID == other.ID && - f.ClosingType == other.ClosingType && + f.IsTrafficFullyClosed == other.IsTrafficFullyClosed && f.ClosingDuration == other.ClosingDuration && f.CirculationClosingDate.Equal(other.CirculationClosingDate) && f.CirculationReopeningDate.Equal(other.CirculationReopeningDate) && @@ -93,7 +87,7 @@ func (f *Forecast) ChangeLocation(location *time.Location) { f.CirculationClosingDate = f.CirculationClosingDate.In(location) f.CirculationReopeningDate = f.CirculationReopeningDate.In(location) for index, boat := range f.Boats { - f.Boats[index].CrossingDateApproximation = boat.CrossingDateApproximation.In( + f.Boats[index].ApproximativeCrossingDate = boat.ApproximativeCrossingDate.In( location, ) } diff --git a/internal/domains/forecast_domain_test.go b/internal/domains/forecast_domain_test.go index 44b14ee2..2b10e8f6 100644 --- a/internal/domains/forecast_domain_test.go +++ b/internal/domains/forecast_domain_test.go @@ -13,32 +13,32 @@ func TestForecastIsEqualOK(t *testing.T) { circulationReopeningDate, _ := time.Parse(time.RFC3339, "2023-02-26T23:00:00Z") forecast := Forecast{ ID: "recordId", - ClosingType: TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 7200000000000, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: BoatReason, + ClosingReason: BoatPassage, Boats: []Boat{ { Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, }, Link: APIResponseSelfLink{Self: APIResponseLink{Link: "/v1/forecasts/recordId"}}, } otherForecast := Forecast{ ID: "recordId", - ClosingType: TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 7200000000000, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: BoatReason, + ClosingReason: BoatPassage, Boats: []Boat{ { Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, }, Link: APIResponseSelfLink{Self: APIResponseLink{Link: "/v1/forecasts/recordId"}}, @@ -54,32 +54,32 @@ func TestForecastIsEqualNOK(t *testing.T) { circulationReopeningDate, _ := time.Parse(time.RFC3339, "2023-02-26T23:00:00Z") forecast := Forecast{ ID: "recordId", - ClosingType: TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 7200000000000, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: BoatReason, + ClosingReason: BoatPassage, Boats: []Boat{ { Name: "MY_BOAT2", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, }, Link: APIResponseSelfLink{Self: APIResponseLink{Link: "/v1/forecasts/recordId"}}, } otherForecast := Forecast{ ID: "recordId", - ClosingType: TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 7200000000000, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: BoatReason, + ClosingReason: BoatPassage, Boats: []Boat{ { Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, }, Link: APIResponseSelfLink{Self: APIResponseLink{Link: "/v1/forecasts/recordId"}}, @@ -95,32 +95,32 @@ func TestForecastsAreEqualOK(t *testing.T) { circulationReopeningDate, _ := time.Parse(time.RFC3339, "2023-02-26T23:00:00Z") forecasts := Forecasts{Forecast{ ID: "recordId", - ClosingType: TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 7200000000000, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: BoatReason, + ClosingReason: BoatPassage, Boats: []Boat{ { Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, }, Link: APIResponseSelfLink{Self: APIResponseLink{Link: "/v1/forecasts/recordId"}}, }} otherForecasts := Forecasts{Forecast{ ID: "recordId", - ClosingType: TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 7200000000000, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: BoatReason, + ClosingReason: BoatPassage, Boats: []Boat{ { Name: "MY_BOAT", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, }, Link: APIResponseSelfLink{Self: APIResponseLink{Link: "/v1/forecasts/recordId"}}, @@ -136,32 +136,32 @@ func TestForecastsAreEqualNOK(t *testing.T) { circulationReopeningDate, _ := time.Parse(time.RFC3339, "2023-02-26T23:00:00Z") forecasts := Forecasts{Forecast{ ID: "recordId", - ClosingType: TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 7200000000000, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: BoatReason, + ClosingReason: BoatPassage, Boats: []Boat{ { Name: "MY_BOAT2", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, }, Link: APIResponseSelfLink{Self: APIResponseLink{Link: "/v1/forecasts/recordId"}}, }} otherForecasts := Forecasts{Forecast{ ID: "recordId2", - ClosingType: TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 7200000000000, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: BoatReason, + ClosingReason: BoatPassage, Boats: []Boat{ { Name: "MY_BOAT2", - Maneuver: Entering, - CrossingDateApproximation: crossingDateApproximation, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingDateApproximation, }, }, Link: APIResponseSelfLink{Self: APIResponseLink{Link: "/v1/forecasts/recordId"}}, diff --git a/internal/domains/sync_domain.go b/internal/domains/sync_domain.go index b23e8016..52c655f0 100644 --- a/internal/domains/sync_domain.go +++ b/internal/domains/sync_domain.go @@ -6,7 +6,6 @@ import ( "time" "github.com/vareversat/chabo-api/internal/errors" - "go.mongodb.org/mongo-driver/bson/primitive" ) var ( @@ -14,10 +13,10 @@ var ( ) type Sync struct { - ID primitive.ObjectID `json:"-" bson:"_id,omitempty"` - ItemCount int `json:"item_count" bson:"item_count" example:"10"` - Duration time.Duration `json:"duration_ms" bson:"duration_ms" example:"130" swaggertype:"primitive,integer"` - Timestamp time.Time `json:"timestamp" bson:"timestamp" example:"2021-05-25T00:53:16.535668Z" format:"date-time"` + ID string `json:"-" bson:"_id,omitempty"` + ItemCount int `json:"item_count" bson:"item_count" example:"10"` + Duration time.Duration `json:"duration_ms" bson:"duration_ms" example:"130" swaggertype:"primitive,integer"` + CreatedAt time.Time `json:"created_at" bson:"created_at" example:"2021-05-25T00:53:16.535668Z" format:"date-time"` } type SyncRepository interface { diff --git a/internal/repositories/forecast_repository.go b/internal/repositories/mongodb/forecast_repository.go similarity index 99% rename from internal/repositories/forecast_repository.go rename to internal/repositories/mongodb/forecast_repository.go index cf6daa81..9430000c 100644 --- a/internal/repositories/forecast_repository.go +++ b/internal/repositories/mongodb/forecast_repository.go @@ -1,4 +1,4 @@ -package repositories +package mongodb import ( "context" diff --git a/internal/repositories/healthcheck_repository.go b/internal/repositories/mongodb/healthcheck_repository.go similarity index 95% rename from internal/repositories/healthcheck_repository.go rename to internal/repositories/mongodb/healthcheck_repository.go index 3d9cb5eb..97413b04 100644 --- a/internal/repositories/healthcheck_repository.go +++ b/internal/repositories/mongodb/healthcheck_repository.go @@ -1,4 +1,4 @@ -package repositories +package mongodb import ( "context" diff --git a/internal/repositories/sync_repository.go b/internal/repositories/mongodb/sync_repository.go similarity index 97% rename from internal/repositories/sync_repository.go rename to internal/repositories/mongodb/sync_repository.go index 4a46fbcb..f4e42ef8 100644 --- a/internal/repositories/sync_repository.go +++ b/internal/repositories/mongodb/sync_repository.go @@ -1,4 +1,4 @@ -package repositories +package mongodb import ( "context" diff --git a/internal/repositories/postgresql/boat_repository.go b/internal/repositories/postgresql/boat_repository.go new file mode 100644 index 00000000..f9b635b4 --- /dev/null +++ b/internal/repositories/postgresql/boat_repository.go @@ -0,0 +1,83 @@ +package postgresql + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/vareversat/chabo-api/internal/domains" +) + +var TABLE_NAME = "boats" + +type boatRepository struct { + connectionPool *pgxpool.Pool +} + +func NewBoatRepository(connectionPool *pgxpool.Pool) domains.BoatRepository { + return &boatRepository{ + connectionPool: connectionPool, + } +} + +func (bR *boatRepository) InsertOne(ctx context.Context, boat domains.Boat) (err error, id int) { + query := ` + INSERT INTO ` + TABLE_NAME + `(name) + VALUES (@name) + ON CONFLICT (name) + DO UPDATE + SET name = EXCLUDED.name + RETURNING boat_id + ` + + args := pgx.NamedArgs{ + "name": boat.Name, + } + err = bR.connectionPool.QueryRow(ctx, query, args).Scan(&id) + + return err, id +} + +func (bR *boatRepository) GetByForecastId(ctx context.Context, forecastId string, boats *domains.Boats) error { + query := ` + SELECT b.name, + fb.is_leaving_dock, + fb.approximative_crossing_date, + sbd.imo, + sbd.mmsi + FROM ` + TABLE_NAME + ` AS b + INNER JOIN forecasts_boats AS fb + ON b.boat_id = fb.boat_id + AND fb.forecast_id = '@forecast_id' + LEFT JOIN static_boat_data AS sbd + ON b.name = sbd.name + ` + + args := pgx.NamedArgs{ + "forecast_id": forecastId, + } + rows, err := bR.connectionPool.Query(ctx, query, args) + + if err != nil { + return fmt.Errorf("Error while querying boat for forecasts %s: %s", forecastId, err.Error()) + } + defer rows.Close() + + for rows.Next() { + var boat domains.Boat + err := rows.Scan( + &boat.Name, + &boat.IsLeavingDock, + &boat.ApproximativeCrossingDate, + &boat.IMO, + &boat.MMSI, + ) + if err != nil { + return fmt.Errorf("Error while scanning row on boat: %s", err) + } + *boats = append(*boats, boat) + } + + return nil +} diff --git a/internal/repositories/postgresql/forecast_repository.go b/internal/repositories/postgresql/forecast_repository.go new file mode 100644 index 00000000..44bb497e --- /dev/null +++ b/internal/repositories/postgresql/forecast_repository.go @@ -0,0 +1,139 @@ +package postgresql + +import ( + "context" + "fmt" + "time" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/vareversat/chabo-api/internal/domains" +) + +type forecastRepository struct { + connectionPool *pgxpool.Pool + boatRepository domains.BoatRepository + forecastsBoatsRepository domains.ForecastsBoatsRepository +} + +func NewForecastRepository(connectionPool *pgxpool.Pool) domains.ForecastRepository { + return &forecastRepository{ + connectionPool: connectionPool, + boatRepository: NewBoatRepository(connectionPool), + forecastsBoatsRepository: NewForecastsBoatsRepository(connectionPool), + } +} + +// InsertAll implements domains.ForecastRepository. +func (rR *forecastRepository) InsertAll(ctx context.Context, forecasts domains.Forecasts) (int, error) { + forecastQuery := ` + INSERT INTO forecasts (forecast_id, + closing_event_name, + closing_duration_min, + circulation_closing_date, + circulation_reopening_date, + is_traffic_fully_closed) + VALUES (@forecast_id, + @closing_event_name, + @closing_duration_min, + @circulation_closing_date, + @circulation_reopening_date, + @is_traffic_fully_closed) + RETURNING forecast_id + ` + for _, forecast := range forecasts { + var forecastId string + args := pgx.NamedArgs{ + "forecast_id": forecast.ID, + "closing_event_name": forecast.ClosingReason, + "closing_duration_min": forecast.ClosingDuration, + "circulation_closing_date": forecast.CirculationClosingDate, + "circulation_reopening_date": forecast.CirculationReopeningDate, + "is_traffic_fully_closed": forecast.IsTrafficFullyClosed, + } + forecastErr := rR.connectionPool.QueryRow(ctx, forecastQuery, args).Scan(&forecastId) + if forecastErr != nil { + return 0, fmt.Errorf("Error while inserting %s into forecast: %s\n", forecast.ID, forecastErr.Error()) + } + for _, boat := range forecast.Boats { + + boatErr, boatId := rR.boatRepository.InsertOne(ctx, boat) + if boatErr != nil { + return 0, fmt.Errorf("Error while inserting %s into boat: %s\n", boat.Name, boatErr.Error()) + } + forecastsBoatErr, forecastsBoatId := rR.forecastsBoatsRepository.InsertOne(ctx, forecast, boat, boatId) + if boatErr != nil { + return 0, fmt.Errorf("Error while inserting %s into forecasts_boats: %s\n", forecastsBoatId, forecastsBoatErr.Error()) + } + } + } + + return len(forecasts), nil +} + +// GetAllFiltered implements domains.ForecastRepository. +func (rR *forecastRepository) GetAllFiltered(ctx context.Context, offset int, limit int, from time.Time, reason string, maneuver string, boat string, forecasts *domains.Forecasts, totalItemCount *int) error { + panic("unimplemented") +} + +// DeleteAll implements domains.ForecastRepository. +func (rR *forecastRepository) DeleteAll(ctx context.Context) (int64, error) { + query := ` + TRUNCATE TABLE forecasts_boats RESTART IDENTITY; + TRUNCATE TABLE boats RESTART IDENTITY CASCADE; + TRUNCATE TABLE forecasts CASCADE; + ` + tag, err := rR.connectionPool.Exec(ctx, query) + return tag.RowsAffected(), err +} + +// GetAllBetweenTwoDates implements domains.ForecastRepository. +func (rR *forecastRepository) GetAllBetweenTwoDates(ctx context.Context, offset int, limit int, from time.Time, to time.Time, forecasts *domains.Forecasts, totalItemCount *int) error { + panic("unimplemented") +} + +// GetByID implements domains.ForecastRepository. +func (rR *forecastRepository) GetByID(ctx context.Context, id string, forecast *domains.Forecast) error { + var closingEventName string + + query := ` + SELECT * FROM forecasts + WHERE forecast_id = @forecast_id + ` + args := pgx.NamedArgs{ + "forecast_id": id, + } + err := rR.connectionPool.QueryRow(ctx, query, args).Scan( + &forecast.ID, + &closingEventName, + &forecast.ClosingDuration, + &forecast.CirculationClosingDate, + &forecast.CirculationReopeningDate, + &forecast.IsTrafficFullyClosed) + + if err != nil { + return fmt.Errorf("Error while scanning forecast with ID %s: %s", id, err.Error()) + } + forecast.ClosingReason = domains.ClosingReason(closingEventName) + + if forecast.ClosingReason == domains.BoatPassage || forecast.ClosingReason == domains.WineFestivalBoats { + var boats domains.Boats + err := rR.boatRepository.GetByForecastId(ctx, id, &boats) + if err != nil { + return fmt.Errorf("Error while getting boats for forecast with ID %s: %s", id, err.Error()) + } + forecast.Boats = boats + } + + return nil +} + +// GetCurrentForecast implements domains.ForecastRepository. +func (rR *forecastRepository) GetCurrentForecast(ctx context.Context, forecast *domains.Forecast) error { + panic("unimplemented") +} + +// GetNextForecast implements domains.ForecastRepository. +func (rR *forecastRepository) GetNextForecast(ctx context.Context, forecast *domains.Forecast, now time.Time) error { + panic("unimplemented") +} diff --git a/internal/repositories/postgresql/forecasts_boats_repository.go b/internal/repositories/postgresql/forecasts_boats_repository.go new file mode 100644 index 00000000..6f44337c --- /dev/null +++ b/internal/repositories/postgresql/forecasts_boats_repository.go @@ -0,0 +1,37 @@ +package postgresql + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/vareversat/chabo-api/internal/domains" +) + +type forecastsBoatsRepository struct { + connectionPool *pgxpool.Pool +} + +func NewForecastsBoatsRepository(connectionPool *pgxpool.Pool) domains.ForecastsBoatsRepository { + return &forecastsBoatsRepository{ + connectionPool: connectionPool, + } +} + +func (fbR *forecastsBoatsRepository) InsertOne(ctx context.Context, forecast domains.Forecast, boat domains.Boat, boatId int) (err error, id string) { + query := ` + INSERT INTO forecasts_boats (forecast_id, boat_id, is_leaving_dock, approximative_crossing_date) + VALUES (@forecast_id, @boat_id, @is_leaving_dock, @approximative_crossing_date) + RETURNING forecast_id + ` + + args := pgx.NamedArgs{ + "forecast_id": forecast.ID, + "boat_id": boatId, + "is_leaving_dock": boat.IsLeavingDock, + "approximative_crossing_date": boat.ApproximativeCrossingDate, + } + err = fbR.connectionPool.QueryRow(ctx, query, args).Scan(&id) + + return err, id +} diff --git a/internal/repositories/postgresql/sync_repository.go b/internal/repositories/postgresql/sync_repository.go new file mode 100644 index 00000000..8569dc69 --- /dev/null +++ b/internal/repositories/postgresql/sync_repository.go @@ -0,0 +1,43 @@ +package postgresql + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/vareversat/chabo-api/internal/domains" +) + +type syncRepository struct { + connectionPool *pgxpool.Pool +} + +func NewSyncRepository(connectionPool *pgxpool.Pool) domains.SyncRepository { + return &syncRepository{ + connectionPool: connectionPool, + } +} + +func (rR *syncRepository) InsertOne(ctx context.Context, sync domains.Sync) error { + query := ` + INSERT INTO syncs (item_count, duration, created_at) + VALUES (@item_count, @duration, @created_at) + RETURNING sync_id + ` + args := pgx.NamedArgs{ + "item_count": sync.ItemCount, + "duration": sync.Duration, + "created_at": sync.CreatedAt, + } + var id string + return rR.connectionPool.QueryRow(ctx, query, args).Scan(&id) +} + +func (rR *syncRepository) GetLast(ctx context.Context, sync *domains.Sync) error { + query := ` + SELECT * FROM syncs + ORDER BY created_at DESC + LIMIT 1 + ` + return rR.connectionPool.QueryRow(ctx, query).Scan(&sync.ID, &sync.ItemCount, &sync.Duration, &sync.CreatedAt) +} diff --git a/internal/repositories/postres_client.go b/internal/repositories/postres_client.go new file mode 100644 index 00000000..ef2c2ea0 --- /dev/null +++ b/internal/repositories/postres_client.go @@ -0,0 +1,25 @@ +package repositories + +import ( + "context" + "os" + + "github.com/jackc/pgx/v5/pgxpool" +) + +var postgresDSN = os.Getenv("POSTGRES_DSN") + +func NewPostgresClient() *pgxpool.Pool { + var ctx = context.Background() + pool, err := pgxpool.New(context.Background(), postgresDSN) + if err != nil { + panic(err) + } else { + // Verify the connection + if err := pool.Ping(ctx); err != nil { + panic(err) + } + } + + return pool +} diff --git a/internal/usecases/boat_usecase.go b/internal/usecases/boat_usecase.go new file mode 100644 index 00000000..61ec02eb --- /dev/null +++ b/internal/usecases/boat_usecase.go @@ -0,0 +1,35 @@ +package usecases + +import ( + "context" + "time" + + "github.com/vareversat/chabo-api/internal/domains" + "github.com/vareversat/chabo-api/internal/errors" +) + +type boatUseCase struct { + boatRepository domains.BoatRepository + contextTimeout time.Duration +} + +func NewBoatUseCase( + boatRepository domains.BoatRepository, + timeout time.Duration, +) domains.BoatUseCase { + return &boatUseCase{ + boatRepository: boatRepository, + contextTimeout: timeout, + } +} + +func (bU *boatUseCase) InsertOne(ctx context.Context, boat domains.Boat) errors.CustomError { + ctx, cancel := context.WithTimeout(ctx, bU.contextTimeout) + defer cancel() + err, _ := bU.boatRepository.InsertOne(ctx, boat) + + if err != nil { + return errors.NewInternalServerError(err.Error()) + } + return nil +} diff --git a/internal/usecases/forecast_usecase.go b/internal/usecases/forecast_usecase.go index 785630f8..4b41b1c4 100644 --- a/internal/usecases/forecast_usecase.go +++ b/internal/usecases/forecast_usecase.go @@ -221,7 +221,7 @@ func (fU *forecastUseCase) TryToSyncAll(ctx context.Context) (domains.Sync, erro sync := domains.Sync{ ItemCount: insertCount, Duration: time.Duration(elapsed.Milliseconds()), - Timestamp: start, + CreatedAt: start, } err = fU.syncRepository.InsertOne(ctx, sync) if err != nil { @@ -236,12 +236,12 @@ func (fU *forecastUseCase) TryToSyncAll(ctx context.Context) (domains.Sync, erro func (fU *forecastUseCase) ComputeBordeauxAPIResponse( forecasts *domains.Forecasts, - boredeauxAPIResponse domains.BordeauxAPIResponse, + bordeauxAPIResponse domains.BordeauxAPIResponse, ) errors.CustomError { // alreadySeenBoatNames is used to compute the maneuver of each boats var alreadySeenBoatNames []string - for _, openAPIForecast := range boredeauxAPIResponse.Records { + for _, openAPIForecast := range bordeauxAPIResponse.Records { _, offset := openAPIForecast.RecordTimestamp.Zone() closingReason := utils.MapClosingReason(openAPIForecast.Fields.Boat) circulationClosingDate, errClosingDate := utils.FormatDataTime( @@ -278,7 +278,7 @@ func (fU *forecastUseCase) ComputeBordeauxAPIResponse( ), // Convert into minutes CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingType: utils.MapClosingType(openAPIForecast.Fields.TotalClosing), + IsTrafficFullyClosed: openAPIForecast.Fields.TotalClosing == "oui", ClosingReason: closingReason, Boats: utils.MapBoats( closingReason, @@ -310,7 +310,7 @@ func (fU *forecastUseCase) SyncIsNeeded(ctx context.Context) bool { return true } else { currentTime := time.Now() - diff := currentTime.Sub(lastSync.Timestamp) + diff := currentTime.Sub(lastSync.CreatedAt) coolDown, _ := strconv.Atoi(os.Getenv("SYNC_COOL_DOWN_SECONDS")) diff --git a/internal/usecases/forecast_usecase_test.go b/internal/usecases/forecast_usecase_test.go index 0e982cfc..6ee8550f 100644 --- a/internal/usecases/forecast_usecase_test.go +++ b/internal/usecases/forecast_usecase_test.go @@ -45,16 +45,16 @@ func TestComputeForecasts(t *testing.T) { expectedForecasts := domains.Forecasts{ { ID: "recordid", - ClosingType: domains.TwoWay, + IsTrafficFullyClosed: true, ClosingDuration: 120, CirculationClosingDate: circulationClosingDate, CirculationReopeningDate: circulationReopeningDate, - ClosingReason: domains.BoatReason, + ClosingReason: domains.BoatPassage, Boats: []domains.Boat{ { Name: "MY_BOAT", - Maneuver: domains.Entering, - CrossingDateApproximation: approximativeCrossingDate, + IsLeavingDock: false, + ApproximativeCrossingDate: approximativeCrossingDate, }, }, Link: domains.APIResponseSelfLink{ diff --git a/internal/usecases/sync_usecase.go b/internal/usecases/sync_usecase.go index 8e95579e..283828d1 100644 --- a/internal/usecases/sync_usecase.go +++ b/internal/usecases/sync_usecase.go @@ -2,6 +2,7 @@ package usecases import ( "context" + "fmt" "time" "github.com/vareversat/chabo-api/internal/domains" @@ -40,7 +41,7 @@ func (rU *syncUseCase) GetLast(ctx context.Context, sync *domains.Sync) errors.C err := rU.syncRepository.GetLast(ctx, sync) if err != nil { - return errors.NewNotFoundError("No sync status exists in database") + return errors.NewNotFoundError(fmt.Sprintf("No sync status exists in database: %s", err.Error())) } return nil } diff --git a/internal/utils/mapping.go b/internal/utils/mapping.go index c520c620..e1fc9656 100644 --- a/internal/utils/mapping.go +++ b/internal/utils/mapping.go @@ -7,15 +7,6 @@ import ( "github.com/vareversat/chabo-api/internal/domains" ) -// MapClosingType Return the corresponding domains.ClosingType according to the string value -func MapClosingType(stringClosingType string) domains.ClosingType { - if stringClosingType == "oui" { - return domains.TwoWay - } else { - return domains.OneWay - } -} - // MapClosingReason Return the corresponding domains.ClosingReason according to the string value func MapClosingReason(stringClosingReason string) domains.ClosingReason { switch { @@ -27,7 +18,7 @@ func MapClosingReason(stringClosingReason string) domains.ClosingReason { case stringClosingReason == "MAINTENANCE": return domains.Maintenance default: - return domains.BoatReason + return domains.BoatPassage } } @@ -57,25 +48,25 @@ func MapBoats( alreadySeenBoatNames *[]string, ) []domains.Boat { var boats []domains.Boat - if closingReason == domains.BoatReason { + if closingReason == domains.BoatPassage { // The string may contain multiple boat name separated by a "/" boatNamesSlice := strings.Split(boatNames, "/") for index, boat := range boatNamesSlice { boatName := strings.TrimSpace(boat) - var action domains.BoatManeuver + var isLeavingDock bool if contains(*alreadySeenBoatNames, boatName) { // If the boat is already in the list, that means that it is docked in Bordeaux *alreadySeenBoatNames = remove(*alreadySeenBoatNames, boatName) - action = domains.Leaving + isLeavingDock = true } else { // If not, that means that it is entering in Bordeaux - action = domains.Entering + isLeavingDock = false *alreadySeenBoatNames = append(*alreadySeenBoatNames, boatName) } boats = append(boats, domains.Boat{ - Name: boatName, - Maneuver: action, - CrossingDateApproximation: computeCrossingDateApproximation( + Name: boatName, + IsLeavingDock: isLeavingDock, + ApproximativeCrossingDate: computeCrossingDateApproximation( circulationClosingDate, closingDuration, len(boatNamesSlice), diff --git a/internal/utils/mapping_test.go b/internal/utils/mapping_test.go index c805e46c..76517789 100644 --- a/internal/utils/mapping_test.go +++ b/internal/utils/mapping_test.go @@ -9,20 +9,6 @@ import ( "github.com/vareversat/chabo-api/internal/domains" ) -func TestMapClosingType_OUI(t *testing.T) { - expected := domains.TwoWay - value := MapClosingType("oui") - - assert.Equal(t, expected, value) -} - -func TestMapClosingType_NON(t *testing.T) { - expected := domains.OneWay - value := MapClosingType("non") - - assert.Equal(t, expected, value) -} - func TestMapClosingReason_MAINTENANCE(t *testing.T) { expected := domains.Maintenance value := MapClosingReason("MAINTENANCE") @@ -31,7 +17,7 @@ func TestMapClosingReason_MAINTENANCE(t *testing.T) { } func TestMapClosingReason_BOAT(t *testing.T) { - expected := domains.BoatReason + expected := domains.BoatPassage value := MapClosingReason("SILVER DAWN") assert.Equal(t, expected, value) @@ -59,9 +45,9 @@ func TestMapBoats(t *testing.T) { crossingTime := localTime.Add(duration / 2) expected := []domains.Boat{ - {Name: "MY_BOAT", Maneuver: domains.Entering, CrossingDateApproximation: crossingTime}, + {Name: "MY_BOAT", IsLeavingDock: false, ApproximativeCrossingDate: crossingTime}, } - value := MapBoats(domains.BoatReason, "MY_BOAT", duration, localTime, &alreadySeenBoatNames) + value := MapBoats(domains.BoatPassage, "MY_BOAT", duration, localTime, &alreadySeenBoatNames) assert.True(t, reflect.DeepEqual(expected, value)) } @@ -75,15 +61,15 @@ func TestMapBoatsMultiBoats(t *testing.T) { crossingTimeBoat2 := localTime.Add(time.Duration(float64(duration) * (float64(2) / float64(3)))) expected := []domains.Boat{ - {Name: "MY_BOAT", Maneuver: domains.Entering, CrossingDateApproximation: crossingTimeBoat1}, + {Name: "MY_BOAT", IsLeavingDock: false, ApproximativeCrossingDate: crossingTimeBoat1}, { Name: "MY_SECOND_BOAT", - Maneuver: domains.Entering, - CrossingDateApproximation: crossingTimeBoat2, + IsLeavingDock: false, + ApproximativeCrossingDate: crossingTimeBoat2, }, } value := MapBoats( - domains.BoatReason, + domains.BoatPassage, "MY_BOAT /MY_SECOND_BOAT", duration, localTime, @@ -102,9 +88,9 @@ func TestMapBoatsExistingBoats(t *testing.T) { crossingTime := localTime.Add(duration / 2) expected := []domains.Boat{ - {Name: "MY_BOAT", Maneuver: domains.Leaving, CrossingDateApproximation: crossingTime}, + {Name: "MY_BOAT", IsLeavingDock: true, ApproximativeCrossingDate: crossingTime}, } - value := MapBoats(domains.BoatReason, "MY_BOAT", duration, localTime, &alreadySeenBoatNames) + value := MapBoats(domains.BoatPassage, "MY_BOAT", duration, localTime, &alreadySeenBoatNames) assert.True(t, reflect.DeepEqual(expected, value)) } diff --git a/main.go b/main.go index 7ebe614c..6ca3cf91 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "os" "github.com/getsentry/sentry-go" + "github.com/jackc/pgx/v5/pgxpool" log "github.com/sirupsen/logrus" "github.com/vareversat/chabo-api/internal/api/routers" "github.com/vareversat/chabo-api/internal/repositories" @@ -16,7 +17,8 @@ var ( Env = os.Getenv("ENV") GinMode = os.Getenv("GIN_MODE") mongoDatabaseName = os.Getenv("MONGO_DATABASE_NAME") - mongoDatabase mongo.Database + mongoDatabase *mongo.Database + postgresPool *pgxpool.Pool ) func init() { @@ -28,7 +30,9 @@ func init() { }) utils.InitOpenApi(openApiLogger) // Init Mongo - mongoDatabase = *repositories.NewMongoClient().Database(mongoDatabaseName) + mongoDatabase = repositories.NewMongoClient().Database(mongoDatabaseName) + // Init Postgres + postgresPool = repositories.NewPostgresClient() } func main() { @@ -50,5 +54,5 @@ func main() { "Welcome to Chabo API ! Starting the project in " + Env + " mode (Gin " + GinMode + ")", ) - routers.MainRouter(mongoDatabase) + routers.MainRouter(mongoDatabase, postgresPool) }