perbaikan config
This commit is contained in:
BIN
api-service
Normal file
BIN
api-service
Normal file
Binary file not shown.
33
example.env
Normal file
33
example.env
Normal file
@@ -0,0 +1,33 @@
|
||||
# Server Configuration
|
||||
PORT=8080
|
||||
GIN_MODE=debug
|
||||
|
||||
# Primary Database Configuration (PostgreSQL)
|
||||
DB_USERNAME=stim
|
||||
DB_PASSWORD=stim*RS54
|
||||
DB_HOST=10.10.123.165
|
||||
DB_PORT=5432
|
||||
DB_DATABASE=satu_db
|
||||
DB_SSLMODE=disable
|
||||
|
||||
SATUDATA_CONNECTION=postgres
|
||||
SATUDATA_USERNAME=stim
|
||||
SATUDATA_PASSWORD=stim*RS54
|
||||
SATUDATA_HOST=10.10.123.165
|
||||
SATUDATA_DATABASE=satu_db
|
||||
SATUDATA_PORT=5000
|
||||
SATUDATA_SSLMODE=disable
|
||||
|
||||
ANTRIAN_CONNECTION=mysqli
|
||||
ANTRIAN_HOST=10.10.123.160
|
||||
ANTRIAN_USERNAME=postgres
|
||||
ANTRIAN_PASSWORD=zahwa2904
|
||||
ANTRIAN_DATABASE=gomed_antrian
|
||||
ANTRIAN_PORT=5000
|
||||
ANTRIAN_SSLMODE=disable
|
||||
|
||||
# Keycloak Configuration (optional)
|
||||
KEYCLOAK_ISSUER=https://auth.rssa.top/realms/sandbox
|
||||
KEYCLOAK_AUDIENCE=nuxtsim-pendaftaran
|
||||
KEYCLOAK_JWKS_URL=https://auth.rssa.top/realms/sandbox/protocol/openid-connect/certs
|
||||
KEYCLOAK_ENABLED=false
|
||||
58
go.mod
58
go.mod
@@ -3,49 +3,29 @@ module api-service
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/coder/websocket v1.8.13
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.8.12
|
||||
github.com/testcontainers/testcontainers-go v0.38.0
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.38.0
|
||||
go.mongodb.org/mongo-driver v1.17.3
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/sync v0.16.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v28.2.2+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
@@ -54,63 +34,35 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.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
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
google.golang.org/grpc v1.73.0 // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
166
go.sum
166
go.sum
@@ -1,13 +1,5 @@
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
@@ -16,57 +8,20 @@ github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQ
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
||||
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
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/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||
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 v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
@@ -87,41 +42,27 @@ github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHO
|
||||
github.com/go-playground/validator/v10 v10.27.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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
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.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
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/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.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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -130,36 +71,12 @@ 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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
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/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
|
||||
github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -167,27 +84,15 @@ 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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
|
||||
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
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=
|
||||
@@ -195,7 +100,6 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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=
|
||||
@@ -207,14 +111,6 @@ github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+z
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w=
|
||||
github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.38.0 h1:KFdx9A0yF94K70T6ibSuvgkQQeX1xKlZVF3hEagXEtY=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.38.0/go.mod h1:T/QRECND6N6tAKMxF1Za+G2tpwnGEHcODzHRsgIpw9M=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
@@ -227,48 +123,19 @@ github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
|
||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -276,34 +143,22 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.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=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
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=
|
||||
@@ -312,25 +167,12 @@ 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.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
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/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -345,5 +187,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
|
||||
@@ -4,11 +4,14 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Database DatabaseConfig
|
||||
Databases map[string]DatabaseConfig
|
||||
ReadReplicas map[string][]DatabaseConfig // For read replicas
|
||||
Keycloak KeycloakConfig
|
||||
}
|
||||
|
||||
@@ -18,12 +21,20 @@ type ServerConfig struct {
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Name string
|
||||
Type string // postgres, mysql, sqlserver, sqlite, mongodb
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Database string
|
||||
Schema string
|
||||
SSLMode string
|
||||
Path string // For SQLite
|
||||
Options string // Additional connection options
|
||||
MaxOpenConns int // Max open connections
|
||||
MaxIdleConns int // Max idle connections
|
||||
ConnMaxLifetime time.Duration // Connection max lifetime
|
||||
}
|
||||
|
||||
type KeycloakConfig struct {
|
||||
@@ -39,14 +50,8 @@ func LoadConfig() *Config {
|
||||
Port: getEnvAsInt("PORT", 8080),
|
||||
Mode: getEnv("GIN_MODE", "debug"),
|
||||
},
|
||||
Database: DatabaseConfig{
|
||||
Host: getEnv("BLUEPRINT_DB_HOST", "localhost"),
|
||||
Port: getEnvAsInt("BLUEPRINT_DB_PORT", 5432),
|
||||
Username: getEnv("BLUEPRINT_DB_USERNAME", "postgres"),
|
||||
Password: getEnv("BLUEPRINT_DB_PASSWORD", "postgres"),
|
||||
Database: getEnv("BLUEPRINT_DB_DATABASE", "api_service"),
|
||||
Schema: getEnv("BLUEPRINT_DB_SCHEMA", "public"),
|
||||
},
|
||||
Databases: make(map[string]DatabaseConfig),
|
||||
ReadReplicas: make(map[string][]DatabaseConfig),
|
||||
Keycloak: KeycloakConfig{
|
||||
Issuer: getEnv("KEYCLOAK_ISSUER", "https://keycloak.example.com/auth/realms/yourrealm"),
|
||||
Audience: getEnv("KEYCLOAK_AUDIENCE", "your-client-id"),
|
||||
@@ -55,9 +60,249 @@ func LoadConfig() *Config {
|
||||
},
|
||||
}
|
||||
|
||||
// Load database configurations
|
||||
config.loadDatabaseConfigs()
|
||||
|
||||
// Load read replica configurations
|
||||
config.loadReadReplicaConfigs()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (c *Config) loadDatabaseConfigs() {
|
||||
envVars := os.Environ()
|
||||
dbConfigs := make(map[string]map[string]string)
|
||||
|
||||
// Parse database configurations from environment variables
|
||||
for _, envVar := range envVars {
|
||||
parts := strings.SplitN(envVar, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := parts[0]
|
||||
value := parts[1]
|
||||
|
||||
// Parse specific database configurations
|
||||
if strings.HasSuffix(key, "_CONNECTION") || strings.HasSuffix(key, "_HOST") ||
|
||||
strings.HasSuffix(key, "_DATABASE") || strings.HasSuffix(key, "_USERNAME") ||
|
||||
strings.HasSuffix(key, "_PASSWORD") || strings.HasSuffix(key, "_PORT") ||
|
||||
strings.HasSuffix(key, "_NAME") {
|
||||
|
||||
segments := strings.Split(key, "_")
|
||||
if len(segments) >= 2 {
|
||||
dbName := strings.ToLower(strings.Join(segments[:len(segments)-1], "_"))
|
||||
property := strings.ToLower(segments[len(segments)-1])
|
||||
|
||||
if dbConfigs[dbName] == nil {
|
||||
dbConfigs[dbName] = make(map[string]string)
|
||||
}
|
||||
dbConfigs[dbName][property] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Parse DB_ prefixed variables
|
||||
if strings.HasPrefix(key, "DB_") && !strings.Contains(key, "_REPLICA_") {
|
||||
segments := strings.Split(key, "_")
|
||||
if len(segments) >= 3 {
|
||||
dbName := strings.ToLower(segments[1])
|
||||
property := strings.ToLower(strings.Join(segments[2:], "_"))
|
||||
|
||||
if dbConfigs[dbName] == nil {
|
||||
dbConfigs[dbName] = make(map[string]string)
|
||||
}
|
||||
dbConfigs[dbName][property] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Parse legacy format (for backward compatibility)
|
||||
if strings.HasPrefix(key, "BLUEPRINT_DB_") {
|
||||
if dbConfigs["primary"] == nil {
|
||||
dbConfigs["primary"] = make(map[string]string)
|
||||
}
|
||||
property := strings.ToLower(strings.TrimPrefix(key, "BLUEPRINT_DB_"))
|
||||
dbConfigs["primary"][property] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Create DatabaseConfig from parsed configurations
|
||||
for name, config := range dbConfigs {
|
||||
// Skip empty configurations or system configurations
|
||||
if name == "" || strings.Contains(name, "chrome_crashpad_pipe") {
|
||||
continue
|
||||
}
|
||||
|
||||
dbConfig := DatabaseConfig{
|
||||
Name: name,
|
||||
Type: getEnvFromMap(config, "connection", getEnvFromMap(config, "type", "postgres")),
|
||||
Host: getEnvFromMap(config, "host", "localhost"),
|
||||
Port: getEnvAsIntFromMap(config, "port", 5432),
|
||||
Username: getEnvFromMap(config, "username", ""),
|
||||
Password: getEnvFromMap(config, "password", ""),
|
||||
Database: getEnvFromMap(config, "database", getEnvFromMap(config, "name", name)),
|
||||
Schema: getEnvFromMap(config, "schema", "public"),
|
||||
SSLMode: getEnvFromMap(config, "sslmode", "disable"),
|
||||
Path: getEnvFromMap(config, "path", ""),
|
||||
Options: getEnvFromMap(config, "options", ""),
|
||||
MaxOpenConns: getEnvAsIntFromMap(config, "max_open_conns", 25),
|
||||
MaxIdleConns: getEnvAsIntFromMap(config, "max_idle_conns", 25),
|
||||
ConnMaxLifetime: parseDuration(getEnvFromMap(config, "conn_max_lifetime", "5m")),
|
||||
}
|
||||
|
||||
// Skip if username is empty and it's not a system config
|
||||
if dbConfig.Username == "" && !strings.HasPrefix(name, "chrome") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle legacy format
|
||||
if name == "primary" && dbConfig.Type == "postgres" && dbConfig.Host == "localhost" {
|
||||
dbConfig.Host = getEnv("BLUEPRINT_DB_HOST", "localhost")
|
||||
dbConfig.Port = getEnvAsInt("BLUEPRINT_DB_PORT", 5432)
|
||||
dbConfig.Username = getEnv("BLUEPRINT_DB_USERNAME", "postgres")
|
||||
dbConfig.Password = getEnv("BLUEPRINT_DB_PASSWORD", "postgres")
|
||||
dbConfig.Database = getEnv("BLUEPRINT_DB_DATABASE", "api_service")
|
||||
dbConfig.Schema = getEnv("BLUEPRINT_DB_SCHEMA", "public")
|
||||
}
|
||||
|
||||
c.Databases[name] = dbConfig
|
||||
}
|
||||
|
||||
// Add specific databases from .env if not already parsed
|
||||
c.addSpecificDatabase("db", "postgres")
|
||||
c.addSpecificDatabase("simrs", "postgres")
|
||||
c.addSpecificDatabase("antrian", "mysql")
|
||||
c.addSpecificDatabase("satudata", "postgres")
|
||||
c.addSpecificDatabase("mongodb_dev", "mongodb")
|
||||
}
|
||||
|
||||
func (c *Config) loadReadReplicaConfigs() {
|
||||
envVars := os.Environ()
|
||||
|
||||
for _, envVar := range envVars {
|
||||
parts := strings.SplitN(envVar, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := parts[0]
|
||||
value := parts[1]
|
||||
|
||||
// Parse read replica configurations (format: [DBNAME]_REPLICA_[INDEX]_[PROPERTY])
|
||||
if strings.Contains(key, "_REPLICA_") {
|
||||
segments := strings.Split(key, "_")
|
||||
if len(segments) >= 5 && strings.ToUpper(segments[2]) == "REPLICA" {
|
||||
dbName := strings.ToLower(segments[1])
|
||||
replicaIndex := segments[3]
|
||||
property := strings.ToLower(strings.Join(segments[4:], "_"))
|
||||
|
||||
replicaKey := dbName + "_replica_" + replicaIndex
|
||||
|
||||
if c.ReadReplicas[dbName] == nil {
|
||||
c.ReadReplicas[dbName] = []DatabaseConfig{}
|
||||
}
|
||||
|
||||
// Find or create replica config
|
||||
var replicaConfig *DatabaseConfig
|
||||
for i := range c.ReadReplicas[dbName] {
|
||||
if c.ReadReplicas[dbName][i].Name == replicaKey {
|
||||
replicaConfig = &c.ReadReplicas[dbName][i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if replicaConfig == nil {
|
||||
// Create new replica config
|
||||
newConfig := DatabaseConfig{
|
||||
Name: replicaKey,
|
||||
Type: c.Databases[dbName].Type,
|
||||
Host: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_HOST", c.Databases[dbName].Host),
|
||||
Port: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_PORT", c.Databases[dbName].Port),
|
||||
Username: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_USERNAME", c.Databases[dbName].Username),
|
||||
Password: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_PASSWORD", c.Databases[dbName].Password),
|
||||
Database: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_DATABASE", c.Databases[dbName].Database),
|
||||
Schema: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_SCHEMA", c.Databases[dbName].Schema),
|
||||
SSLMode: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_SSLMODE", c.Databases[dbName].SSLMode),
|
||||
MaxOpenConns: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_MAX_OPEN_CONNS", c.Databases[dbName].MaxOpenConns),
|
||||
MaxIdleConns: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_MAX_IDLE_CONNS", c.Databases[dbName].MaxIdleConns),
|
||||
ConnMaxLifetime: parseDuration(getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_CONN_MAX_LIFETIME", "5m")),
|
||||
}
|
||||
c.ReadReplicas[dbName] = append(c.ReadReplicas[dbName], newConfig)
|
||||
replicaConfig = &c.ReadReplicas[dbName][len(c.ReadReplicas[dbName])-1]
|
||||
}
|
||||
|
||||
// Update the specific replica
|
||||
switch property {
|
||||
case "host":
|
||||
replicaConfig.Host = value
|
||||
case "port":
|
||||
replicaConfig.Port = getEnvAsInt(key, 5432)
|
||||
case "username":
|
||||
replicaConfig.Username = value
|
||||
case "password":
|
||||
replicaConfig.Password = value
|
||||
case "database":
|
||||
replicaConfig.Database = value
|
||||
case "schema":
|
||||
replicaConfig.Schema = value
|
||||
case "sslmode":
|
||||
replicaConfig.SSLMode = value
|
||||
case "max_open_conns":
|
||||
replicaConfig.MaxOpenConns = getEnvAsInt(key, 25)
|
||||
case "max_idle_conns":
|
||||
replicaConfig.MaxIdleConns = getEnvAsInt(key, 25)
|
||||
case "conn_max_lifetime":
|
||||
replicaConfig.ConnMaxLifetime = parseDuration(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) addSpecificDatabase(prefix, defaultType string) {
|
||||
connection := getEnv(strings.ToUpper(prefix)+"_CONNECTION", defaultType)
|
||||
host := getEnv(strings.ToUpper(prefix)+"_HOST", "")
|
||||
if host != "" {
|
||||
dbConfig := DatabaseConfig{
|
||||
Name: prefix,
|
||||
Type: connection,
|
||||
Host: host,
|
||||
Port: getEnvAsInt(strings.ToUpper(prefix)+"_PORT", 5432),
|
||||
Username: getEnv(strings.ToUpper(prefix)+"_USERNAME", ""),
|
||||
Password: getEnv(strings.ToUpper(prefix)+"_PASSWORD", ""),
|
||||
Database: getEnv(strings.ToUpper(prefix)+"_DATABASE", getEnv(strings.ToUpper(prefix)+"_NAME", prefix)),
|
||||
Schema: getEnv(strings.ToUpper(prefix)+"_SCHEMA", "public"),
|
||||
SSLMode: getEnv(strings.ToUpper(prefix)+"_SSLMODE", "disable"),
|
||||
MaxOpenConns: getEnvAsInt(strings.ToUpper(prefix)+"_MAX_OPEN_CONNS", 25),
|
||||
MaxIdleConns: getEnvAsInt(strings.ToUpper(prefix)+"_MAX_IDLE_CONNS", 25),
|
||||
ConnMaxLifetime: parseDuration(getEnv(strings.ToUpper(prefix)+"_CONN_MAX_LIFETIME", "5m")),
|
||||
}
|
||||
c.Databases[prefix] = dbConfig
|
||||
}
|
||||
}
|
||||
|
||||
func getEnvFromMap(config map[string]string, key, defaultValue string) string {
|
||||
if value, exists := config[key]; exists {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvAsIntFromMap(config map[string]string, key string, defaultValue int) int {
|
||||
if value, exists := config[key]; exists {
|
||||
if intValue, err := strconv.Atoi(value); err == nil {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func parseDuration(durationStr string) time.Duration {
|
||||
if duration, err := time.ParseDuration(durationStr); err == nil {
|
||||
return duration
|
||||
}
|
||||
return 5 * time.Minute
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
@@ -82,17 +327,23 @@ func getEnvAsBool(key string, defaultValue bool) bool {
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
if c.Database.Host == "" {
|
||||
log.Fatal("Database host is required")
|
||||
if len(c.Databases) == 0 {
|
||||
log.Fatal("At least one database configuration is required")
|
||||
}
|
||||
if c.Database.Username == "" {
|
||||
log.Fatal("Database username is required")
|
||||
|
||||
for name, db := range c.Databases {
|
||||
if db.Host == "" {
|
||||
log.Fatalf("Database host is required for %s", name)
|
||||
}
|
||||
if c.Database.Password == "" {
|
||||
log.Fatal("Database password is required")
|
||||
if db.Username == "" {
|
||||
log.Fatalf("Database username is required for %s", name)
|
||||
}
|
||||
if db.Password == "" {
|
||||
log.Fatalf("Database password is required for %s", name)
|
||||
}
|
||||
if db.Database == "" {
|
||||
log.Fatalf("Database name is required for %s", name)
|
||||
}
|
||||
if c.Database.Database == "" {
|
||||
log.Fatal("Database name is required")
|
||||
}
|
||||
|
||||
// Validate Keycloak configuration if enabled
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"api-service/internal/config"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
@@ -26,47 +26,25 @@ const (
|
||||
MongoDB DatabaseType = "mongodb"
|
||||
)
|
||||
|
||||
// DatabaseConfig represents configuration for a single database connection
|
||||
type DatabaseConfig struct {
|
||||
Name string
|
||||
Type DatabaseType
|
||||
Host string
|
||||
Port string
|
||||
Database string
|
||||
Username string
|
||||
Password string
|
||||
Schema string
|
||||
SSLMode string
|
||||
Path string // For SQLite
|
||||
Options string // Additional connection options
|
||||
}
|
||||
|
||||
// Service represents a service that interacts with multiple databases
|
||||
type Service interface {
|
||||
// Health returns health status for all databases
|
||||
Health() map[string]map[string]string
|
||||
|
||||
// GetDB returns a specific SQL database connection by name
|
||||
GetDB(name string) (*sql.DB, error)
|
||||
|
||||
// GetMongoClient returns a specific MongoDB client by name
|
||||
GetMongoClient(name string) (*mongo.Client, error)
|
||||
|
||||
// Close terminates all database connections
|
||||
GetReadDB(name string) (*sql.DB, error) // For read replicas
|
||||
Close() error
|
||||
|
||||
// ListDBs returns list of available database names
|
||||
ListDBs() []string
|
||||
|
||||
// GetDBType returns the type of a specific database
|
||||
GetDBType(name string) (DatabaseType, error)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
sqlDatabases map[string]*sql.DB
|
||||
mongoClients map[string]*mongo.Client
|
||||
configs map[string]DatabaseConfig
|
||||
readReplicas map[string][]*sql.DB // Read replicas for load balancing
|
||||
configs map[string]config.DatabaseConfig
|
||||
readConfigs map[string][]config.DatabaseConfig
|
||||
mu sync.RWMutex
|
||||
readBalancer map[string]int // Round-robin counter for read replicas
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -75,21 +53,33 @@ var (
|
||||
)
|
||||
|
||||
// New creates a new database service with multiple connections
|
||||
func New() Service {
|
||||
func New(cfg *config.Config) Service {
|
||||
once.Do(func() {
|
||||
dbManager = &service{
|
||||
sqlDatabases: make(map[string]*sql.DB),
|
||||
mongoClients: make(map[string]*mongo.Client),
|
||||
configs: make(map[string]DatabaseConfig),
|
||||
readReplicas: make(map[string][]*sql.DB),
|
||||
configs: make(map[string]config.DatabaseConfig),
|
||||
readConfigs: make(map[string][]config.DatabaseConfig),
|
||||
readBalancer: make(map[string]int),
|
||||
}
|
||||
|
||||
// Load database configurations from environment
|
||||
configs := loadDatabaseConfigs()
|
||||
// Load configurations from config
|
||||
dbManager.loadFromConfig(cfg)
|
||||
|
||||
// Initialize all database connections
|
||||
for _, config := range configs {
|
||||
if err := dbManager.addDatabase(config); err != nil {
|
||||
log.Printf("Failed to connect to database %s: %v", config.Name, err)
|
||||
// Initialize all databases
|
||||
for name, dbConfig := range dbManager.configs {
|
||||
if err := dbManager.addDatabase(name, dbConfig); err != nil {
|
||||
log.Printf("Failed to connect to database %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize read replicas
|
||||
for name, replicaConfigs := range dbManager.readConfigs {
|
||||
for i, replicaConfig := range replicaConfigs {
|
||||
if err := dbManager.addReadReplica(name, i, replicaConfig); err != nil {
|
||||
log.Printf("Failed to connect to read replica %s[%d]: %v", name, i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -97,139 +87,95 @@ func New() Service {
|
||||
return dbManager
|
||||
}
|
||||
|
||||
// loadDatabaseConfigs loads database configurations from environment variables
|
||||
func loadDatabaseConfigs() []DatabaseConfig {
|
||||
var configs []DatabaseConfig
|
||||
|
||||
// Load configurations from environment
|
||||
// Format: DB_{NAME}_{PROPERTY}
|
||||
|
||||
// Check for DB_ prefixed configurations
|
||||
envVars := os.Environ()
|
||||
dbConfigs := make(map[string]map[string]string)
|
||||
|
||||
for _, envVar := range envVars {
|
||||
parts := strings.SplitN(envVar, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := parts[0]
|
||||
value := parts[1]
|
||||
|
||||
if strings.HasPrefix(key, "DB_") {
|
||||
segments := strings.Split(key, "_")
|
||||
if len(segments) >= 3 {
|
||||
dbName := strings.ToLower(segments[1])
|
||||
property := strings.ToLower(strings.Join(segments[2:], "_"))
|
||||
|
||||
if dbConfigs[dbName] == nil {
|
||||
dbConfigs[dbName] = make(map[string]string)
|
||||
}
|
||||
dbConfigs[dbName][property] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert map to DatabaseConfig structs
|
||||
for name, config := range dbConfigs {
|
||||
dbType := DatabaseType(getEnvFromMap(config, "type", "postgres"))
|
||||
|
||||
dbConfig := DatabaseConfig{
|
||||
Name: name,
|
||||
Type: dbType,
|
||||
Host: getEnvFromMap(config, "host", "localhost"),
|
||||
Port: getEnvFromMap(config, "port", getDefaultPort(dbType)),
|
||||
Database: getEnvFromMap(config, "database", name),
|
||||
Username: getEnvFromMap(config, "username", ""),
|
||||
Password: getEnvFromMap(config, "password", ""),
|
||||
Schema: getEnvFromMap(config, "schema", ""),
|
||||
SSLMode: getEnvFromMap(config, "sslmode", "disable"),
|
||||
Path: getEnvFromMap(config, "path", ""),
|
||||
Options: getEnvFromMap(config, "options", ""),
|
||||
}
|
||||
|
||||
configs = append(configs, dbConfig)
|
||||
}
|
||||
|
||||
// If no configurations found, use default
|
||||
if len(configs) == 0 {
|
||||
configs = []DatabaseConfig{
|
||||
{
|
||||
Name: "primary",
|
||||
Type: Postgres,
|
||||
Host: getEnv("DB_PRIMARY_HOST", "localhost"),
|
||||
Port: getEnv("DB_PRIMARY_PORT", "5432"),
|
||||
Database: getEnv("DB_PRIMARY_DATABASE", "blueprint"),
|
||||
Username: getEnv("DB_PRIMARY_USERNAME", "postgres"),
|
||||
Password: getEnv("DB_PRIMARY_PASSWORD", ""),
|
||||
Schema: getEnv("DB_PRIMARY_SCHEMA", "public"),
|
||||
SSLMode: getEnv("DB_PRIMARY_SSLMODE", "disable"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return configs
|
||||
}
|
||||
|
||||
// getEnvFromMap helper function
|
||||
func getEnvFromMap(config map[string]string, key, defaultValue string) string {
|
||||
if value, exists := config[key]; exists {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getEnv helper function
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getDefaultPort returns default port for database type
|
||||
func getDefaultPort(dbType DatabaseType) string {
|
||||
switch dbType {
|
||||
case Postgres:
|
||||
return "5432"
|
||||
case MySQL:
|
||||
return "3306"
|
||||
case SQLServer:
|
||||
return "1433"
|
||||
case MongoDB:
|
||||
return "27017"
|
||||
case SQLite:
|
||||
return ""
|
||||
default:
|
||||
return "5432"
|
||||
}
|
||||
}
|
||||
|
||||
// addDatabase adds a new database connection
|
||||
func (s *service) addDatabase(config DatabaseConfig) error {
|
||||
func (s *service) loadFromConfig(cfg *config.Config) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
switch config.Type {
|
||||
case Postgres:
|
||||
return s.addPostgres(config)
|
||||
case MySQL:
|
||||
return s.addMySQL(config)
|
||||
case SQLServer:
|
||||
return s.addSQLServer(config)
|
||||
case SQLite:
|
||||
return s.addSQLite(config)
|
||||
case MongoDB:
|
||||
return s.addMongoDB(config)
|
||||
default:
|
||||
return fmt.Errorf("unsupported database type: %s", config.Type)
|
||||
// Load primary databases
|
||||
for name, dbConfig := range cfg.Databases {
|
||||
s.configs[name] = dbConfig
|
||||
}
|
||||
|
||||
// Load read replicas
|
||||
for name, replicaConfigs := range cfg.ReadReplicas {
|
||||
s.readConfigs[name] = replicaConfigs
|
||||
}
|
||||
}
|
||||
|
||||
// addPostgres adds PostgreSQL connection
|
||||
func (s *service) addPostgres(config DatabaseConfig) error {
|
||||
connStr := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s",
|
||||
func (s *service) addDatabase(name string, config config.DatabaseConfig) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var db *sql.DB
|
||||
var err error
|
||||
|
||||
dbType := DatabaseType(config.Type)
|
||||
|
||||
switch dbType {
|
||||
case Postgres:
|
||||
db, err = s.openPostgresConnection(config)
|
||||
case MySQL:
|
||||
db, err = s.openMySQLConnection(config)
|
||||
case SQLServer:
|
||||
db, err = s.openSQLServerConnection(config)
|
||||
case SQLite:
|
||||
db, err = s.openSQLiteConnection(config)
|
||||
case MongoDB:
|
||||
return s.addMongoDB(name, config)
|
||||
default:
|
||||
return fmt.Errorf("unsupported database type: %s", config.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.configureSQLDB(name, db, config.MaxOpenConns, config.MaxIdleConns, config.ConnMaxLifetime)
|
||||
}
|
||||
|
||||
func (s *service) addReadReplica(name string, index int, config config.DatabaseConfig) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var db *sql.DB
|
||||
var err error
|
||||
|
||||
dbType := DatabaseType(config.Type)
|
||||
|
||||
switch dbType {
|
||||
case Postgres:
|
||||
db, err = s.openPostgresConnection(config)
|
||||
case MySQL:
|
||||
db, err = s.openMySQLConnection(config)
|
||||
case SQLServer:
|
||||
db, err = s.openSQLServerConnection(config)
|
||||
case SQLite:
|
||||
db, err = s.openSQLiteConnection(config)
|
||||
default:
|
||||
return fmt.Errorf("unsupported database type for read replica: %s", config.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.readReplicas[name] == nil {
|
||||
s.readReplicas[name] = make([]*sql.DB, 0)
|
||||
}
|
||||
|
||||
// Ensure we have enough slots
|
||||
for len(s.readReplicas[name]) <= index {
|
||||
s.readReplicas[name] = append(s.readReplicas[name], nil)
|
||||
}
|
||||
|
||||
s.readReplicas[name][index] = db
|
||||
log.Printf("Successfully connected to read replica %s[%d]", name, index)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) openPostgresConnection(config config.DatabaseConfig) (*sql.DB, error) {
|
||||
connStr := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
||||
config.Username,
|
||||
config.Password,
|
||||
config.Host,
|
||||
@@ -244,15 +190,14 @@ func (s *service) addPostgres(config DatabaseConfig) error {
|
||||
|
||||
db, err := sql.Open("pgx", connStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open PostgreSQL connection: %w", err)
|
||||
return nil, fmt.Errorf("failed to open PostgreSQL connection: %w", err)
|
||||
}
|
||||
|
||||
return s.configureSQLDB(config.Name, db)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// addMySQL adds MySQL connection
|
||||
func (s *service) addMySQL(config DatabaseConfig) error {
|
||||
connStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true",
|
||||
func (s *service) openMySQLConnection(config config.DatabaseConfig) (*sql.DB, error) {
|
||||
connStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true",
|
||||
config.Username,
|
||||
config.Password,
|
||||
config.Host,
|
||||
@@ -262,15 +207,14 @@ func (s *service) addMySQL(config DatabaseConfig) error {
|
||||
|
||||
db, err := sql.Open("mysql", connStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open MySQL connection: %w", err)
|
||||
return nil, fmt.Errorf("failed to open MySQL connection: %w", err)
|
||||
}
|
||||
|
||||
return s.configureSQLDB(config.Name, db)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// addSQLServer adds SQL Server connection
|
||||
func (s *service) addSQLServer(config DatabaseConfig) error {
|
||||
connStr := fmt.Sprintf("sqlserver://%s:%s@%s:%s?database=%s",
|
||||
func (s *service) openSQLServerConnection(config config.DatabaseConfig) (*sql.DB, error) {
|
||||
connStr := fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s",
|
||||
config.Username,
|
||||
config.Password,
|
||||
config.Host,
|
||||
@@ -280,30 +224,31 @@ func (s *service) addSQLServer(config DatabaseConfig) error {
|
||||
|
||||
db, err := sql.Open("sqlserver", connStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SQL Server connection: %w", err)
|
||||
return nil, fmt.Errorf("failed to open SQL Server connection: %w", err)
|
||||
}
|
||||
|
||||
return s.configureSQLDB(config.Name, db)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// addSQLite adds SQLite connection
|
||||
func (s *service) addSQLite(config DatabaseConfig) error {
|
||||
func (s *service) openSQLiteConnection(config config.DatabaseConfig) (*sql.DB, error) {
|
||||
dbPath := config.Path
|
||||
if dbPath == "" {
|
||||
dbPath = fmt.Sprintf("./data/%s.db", config.Name)
|
||||
dbPath = fmt.Sprintf("./data/%s.db", config.Database)
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SQLite connection: %w", err)
|
||||
return nil, fmt.Errorf("failed to open SQLite connection: %w", err)
|
||||
}
|
||||
|
||||
return s.configureSQLDB(config.Name, db)
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// addMongoDB adds MongoDB connection
|
||||
func (s *service) addMongoDB(config DatabaseConfig) error {
|
||||
uri := fmt.Sprintf("mongodb://%s:%s@%s:%s/%s",
|
||||
func (s *service) addMongoDB(name string, config config.DatabaseConfig) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
uri := fmt.Sprintf("mongodb://%s:%s@%s:%d/%s",
|
||||
config.Username,
|
||||
config.Password,
|
||||
config.Host,
|
||||
@@ -311,36 +256,22 @@ func (s *service) addMongoDB(config DatabaseConfig) error {
|
||||
config.Database,
|
||||
)
|
||||
|
||||
clientOptions := options.Client().ApplyURI(uri)
|
||||
client, err := mongo.Connect(context.Background(), clientOptions)
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to MongoDB: %w", err)
|
||||
}
|
||||
|
||||
// Test connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := client.Ping(ctx, nil); err != nil {
|
||||
client.Disconnect(context.Background())
|
||||
return fmt.Errorf("failed to ping MongoDB: %w", err)
|
||||
}
|
||||
|
||||
s.mongoClients[config.Name] = client
|
||||
s.configs[config.Name] = config
|
||||
log.Printf("Successfully connected to MongoDB: %s", config.Name)
|
||||
s.mongoClients[name] = client
|
||||
log.Printf("Successfully connected to MongoDB: %s", name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureSQLDB configures common SQL database settings
|
||||
func (s *service) configureSQLDB(name string, db *sql.DB) error {
|
||||
// Configure connection pool
|
||||
db.SetMaxOpenConns(25)
|
||||
db.SetMaxIdleConns(25)
|
||||
db.SetConnMaxLifetime(5 * time.Minute)
|
||||
func (s *service) configureSQLDB(name string, db *sql.DB, maxOpenConns, maxIdleConns int, connMaxLifetime time.Duration) error {
|
||||
db.SetMaxOpenConns(maxOpenConns)
|
||||
db.SetMaxIdleConns(maxIdleConns)
|
||||
db.SetConnMaxLifetime(connMaxLifetime)
|
||||
|
||||
// Test connection
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -355,48 +286,7 @@ func (s *service) configureSQLDB(name string, db *sql.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// # Example multi-database configuration for different database types
|
||||
|
||||
// # PostgreSQL
|
||||
// DB_TYPE_PRIMARY=postgres
|
||||
// DB_HOST_PRIMARY=localhost
|
||||
// DB_PORT_PRIMARY=5432
|
||||
// DB_NAME_PRIMARY=myapp_postgres
|
||||
// DB_USER_PRIMARY=postgres
|
||||
// DB_PASS_PRIMARY=postgres_password
|
||||
// DB_SCHEMA_PRIMARY=public
|
||||
// DB_SSLMODE_PRIMARY=disable
|
||||
|
||||
// # MySQL
|
||||
// DB_TYPE_MYSQL=mysql
|
||||
// DB_HOST_MYSQL=localhost
|
||||
// DB_PORT_MYSQL=3306
|
||||
// DB_NAME_MYSQL=myapp_mysql
|
||||
// DB_USER_MYSQL=root
|
||||
// DB_PASS_MYSQL=mysql_password
|
||||
|
||||
// # SQL Server
|
||||
// DB_TYPE_SQLSERVER=mssql
|
||||
// DB_HOST_SQLSERVER=localhost
|
||||
// DB_PORT_SQLSERVER=1433
|
||||
// DB_NAME_SQLSERVER=myapp_mssql
|
||||
// DB_USER_SQLSERVER=sa
|
||||
// DB_PASS_SQLSERVER=mssql_password
|
||||
|
||||
// # MongoDB
|
||||
// DB_TYPE_MONGODB=mongodb
|
||||
// DB_HOST_MONGODB=localhost
|
||||
// DB_PORT_MONGODB=27017
|
||||
// DB_NAME_MONGODB=myapp_mongo
|
||||
// DB_USER_MONGODB=mongo_user
|
||||
// DB_PASS_MONGODB=mongo_password
|
||||
|
||||
// # SQLite
|
||||
// DB_TYPE_SQLITE=sqlite
|
||||
// DB_PATH_SQLITE=./data/myapp_sqlite.db
|
||||
|
||||
// Health checks the health of all database connections by pinging each database.
|
||||
// It returns a map with database names as keys and their health statistics as values.
|
||||
func (s *service) Health() map[string]map[string]string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@@ -410,22 +300,21 @@ func (s *service) Health() map[string]map[string]string {
|
||||
|
||||
stats := make(map[string]string)
|
||||
|
||||
// Ping the database
|
||||
err := db.PingContext(ctx)
|
||||
if err != nil {
|
||||
stats["status"] = "down"
|
||||
stats["error"] = fmt.Sprintf("db down: %v", err)
|
||||
stats["type"] = "sql"
|
||||
stats["role"] = "primary"
|
||||
result[name] = stats
|
||||
continue
|
||||
}
|
||||
|
||||
// Database is up, add more statistics
|
||||
stats["status"] = "up"
|
||||
stats["message"] = "It's healthy"
|
||||
stats["type"] = "sql"
|
||||
stats["role"] = "primary"
|
||||
|
||||
// Get database stats
|
||||
dbStats := db.Stats()
|
||||
stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections)
|
||||
stats["in_use"] = strconv.Itoa(dbStats.InUse)
|
||||
@@ -435,7 +324,6 @@ func (s *service) Health() map[string]map[string]string {
|
||||
stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10)
|
||||
stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10)
|
||||
|
||||
// Evaluate stats to provide health messages
|
||||
if dbStats.OpenConnections > 40 {
|
||||
stats["message"] = "The database is experiencing heavy load."
|
||||
}
|
||||
@@ -455,6 +343,43 @@ func (s *service) Health() map[string]map[string]string {
|
||||
result[name] = stats
|
||||
}
|
||||
|
||||
// Check read replicas
|
||||
for name, replicas := range s.readReplicas {
|
||||
for i, db := range replicas {
|
||||
if db == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
replicaName := fmt.Sprintf("%s_replica_%d", name, i)
|
||||
stats := make(map[string]string)
|
||||
|
||||
err := db.PingContext(ctx)
|
||||
if err != nil {
|
||||
stats["status"] = "down"
|
||||
stats["error"] = fmt.Sprintf("read replica down: %v", err)
|
||||
stats["type"] = "sql"
|
||||
stats["role"] = "replica"
|
||||
result[replicaName] = stats
|
||||
continue
|
||||
}
|
||||
|
||||
stats["status"] = "up"
|
||||
stats["message"] = "Read replica healthy"
|
||||
stats["type"] = "sql"
|
||||
stats["role"] = "replica"
|
||||
|
||||
dbStats := db.Stats()
|
||||
stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections)
|
||||
stats["in_use"] = strconv.Itoa(dbStats.InUse)
|
||||
stats["idle"] = strconv.Itoa(dbStats.Idle)
|
||||
|
||||
result[replicaName] = stats
|
||||
}
|
||||
}
|
||||
|
||||
// Check MongoDB connections
|
||||
for name, client := range s.mongoClients {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
@@ -462,7 +387,6 @@ func (s *service) Health() map[string]map[string]string {
|
||||
|
||||
stats := make(map[string]string)
|
||||
|
||||
// Ping the MongoDB
|
||||
err := client.Ping(ctx, nil)
|
||||
if err != nil {
|
||||
stats["status"] = "down"
|
||||
@@ -472,7 +396,6 @@ func (s *service) Health() map[string]map[string]string {
|
||||
continue
|
||||
}
|
||||
|
||||
// MongoDB is up
|
||||
stats["status"] = "up"
|
||||
stats["message"] = "It's healthy"
|
||||
stats["type"] = "mongodb"
|
||||
@@ -496,6 +419,29 @@ func (s *service) GetDB(name string) (*sql.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// GetReadDB returns a read replica connection using round-robin load balancing
|
||||
func (s *service) GetReadDB(name string) (*sql.DB, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
replicas, exists := s.readReplicas[name]
|
||||
if !exists || len(replicas) == 0 {
|
||||
// Fallback to primary if no replicas available
|
||||
return s.GetDB(name)
|
||||
}
|
||||
|
||||
// Round-robin load balancing
|
||||
s.readBalancer[name] = (s.readBalancer[name] + 1) % len(replicas)
|
||||
selected := replicas[s.readBalancer[name]]
|
||||
|
||||
if selected == nil {
|
||||
// Fallback to primary if replica is nil
|
||||
return s.GetDB(name)
|
||||
}
|
||||
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// GetMongoClient returns a specific MongoDB client by name
|
||||
func (s *service) GetMongoClient(name string) (*mongo.Client, error) {
|
||||
s.mu.RLock()
|
||||
@@ -516,12 +462,10 @@ func (s *service) ListDBs() []string {
|
||||
|
||||
names := make([]string, 0, len(s.sqlDatabases)+len(s.mongoClients))
|
||||
|
||||
// Add SQL databases
|
||||
for name := range s.sqlDatabases {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
// Add MongoDB clients
|
||||
for name := range s.mongoClients {
|
||||
names = append(names, name)
|
||||
}
|
||||
@@ -539,18 +483,16 @@ func (s *service) GetDBType(name string) (DatabaseType, error) {
|
||||
return "", fmt.Errorf("database %s not found", name)
|
||||
}
|
||||
|
||||
return config.Type, nil
|
||||
return DatabaseType(config.Type), nil
|
||||
}
|
||||
|
||||
// Close closes all database connections
|
||||
// It logs messages indicating disconnection from each database
|
||||
func (s *service) Close() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var errs []error
|
||||
|
||||
// Close SQL databases
|
||||
for name, db := range s.sqlDatabases {
|
||||
if err := db.Close(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to close database %s: %w", name, err))
|
||||
@@ -559,7 +501,18 @@ func (s *service) Close() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Close MongoDB clients
|
||||
for name, replicas := range s.readReplicas {
|
||||
for i, db := range replicas {
|
||||
if db != nil {
|
||||
if err := db.Close(); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to close read replica %s[%d]: %w", name, i, err))
|
||||
} else {
|
||||
log.Printf("Disconnected from read replica: %s[%d]", name, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name, client := range s.mongoClients {
|
||||
if err := client.Disconnect(context.Background()); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to disconnect MongoDB client %s: %w", name, err))
|
||||
@@ -570,7 +523,9 @@ func (s *service) Close() error {
|
||||
|
||||
s.sqlDatabases = make(map[string]*sql.DB)
|
||||
s.mongoClients = make(map[string]*mongo.Client)
|
||||
s.configs = make(map[string]DatabaseConfig)
|
||||
s.readReplicas = make(map[string][]*sql.DB)
|
||||
s.configs = make(map[string]config.DatabaseConfig)
|
||||
s.readConfigs = make(map[string][]config.DatabaseConfig)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("errors closing databases: %v", errs)
|
||||
@@ -578,3 +533,6 @@ func (s *service) Close() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Import necessary packages
|
||||
|
||||
|
||||
@@ -1,52 +1,40 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"database/sql"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func mustStartPostgresContainer() (func(context.Context, ...testcontainers.TerminateOption) error, error) {
|
||||
var (
|
||||
dbName = "testdb"
|
||||
dbPwd = "testpass"
|
||||
dbUser = "testuser"
|
||||
)
|
||||
|
||||
dbContainer, err := postgres.Run(
|
||||
context.Background(),
|
||||
"postgres:latest",
|
||||
postgres.WithDatabase(dbName),
|
||||
postgres.WithUsername(dbUser),
|
||||
postgres.WithPassword(dbPwd),
|
||||
testcontainers.WithWaitStrategy(
|
||||
wait.ForLog("database system is ready to accept connections").
|
||||
WithOccurrence(2).
|
||||
WithStartupTimeout(5*time.Second)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Mock for the database service
|
||||
type MockService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
return dbContainer.Terminate, err
|
||||
func (m *MockService) Health() map[string]map[string]string {
|
||||
args := m.Called()
|
||||
return args.Get(0).(map[string]map[string]string)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
teardown, err := mustStartPostgresContainer()
|
||||
if err != nil {
|
||||
log.Fatalf("could not start postgres container: %v", err)
|
||||
func (m *MockService) GetDB(name string) (*sql.DB, error) {
|
||||
args := m.Called(name)
|
||||
return args.Get(0).(*sql.DB), args.Error(1)
|
||||
}
|
||||
|
||||
m.Run()
|
||||
|
||||
if teardown != nil && teardown(context.Background()) != nil {
|
||||
log.Fatalf("could not teardown postgres container: %v", err)
|
||||
func (m *MockService) Close() error {
|
||||
return m.Called().Error(0)
|
||||
}
|
||||
|
||||
func (m *MockService) ListDBs() []string {
|
||||
args := m.Called()
|
||||
return args.Get(0).([]string)
|
||||
}
|
||||
|
||||
func (m *MockService) GetDBType(name string) (DatabaseType, error) {
|
||||
args := m.Called(name)
|
||||
return args.Get(0).(DatabaseType), args.Error(1)
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
@@ -85,3 +73,52 @@ func TestClose(t *testing.T) {
|
||||
t.Fatalf("expected Close() to return nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Test for loading database configurations
|
||||
func TestLoadDatabaseConfigs(t *testing.T) {
|
||||
// Set environment variables for testing
|
||||
os.Setenv("DB_TEST_TYPE", "postgres")
|
||||
os.Setenv("DB_TEST_HOST", "localhost")
|
||||
os.Setenv("DB_TEST_PORT", "5432")
|
||||
os.Setenv("DB_TEST_DATABASE", "testdb")
|
||||
os.Setenv("DB_TEST_USERNAME", "testuser")
|
||||
os.Setenv("DB_TEST_PASSWORD", "testpass")
|
||||
|
||||
configs := loadDatabaseConfigs()
|
||||
if len(configs) == 0 {
|
||||
t.Fatal("Expected database configurations to be loaded")
|
||||
}
|
||||
|
||||
if configs[0].Type != "postgres" {
|
||||
t.Errorf("Expected database type to be postgres, got %s", configs[0].Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for connection pooling settings
|
||||
func TestConnectionPooling(t *testing.T) {
|
||||
srv := New()
|
||||
// Check health after loading configurations
|
||||
stats := srv.Health()
|
||||
if len(stats) == 0 {
|
||||
t.Fatal("Expected databases to be configured, but found none")
|
||||
}
|
||||
|
||||
db, err := srv.GetDB("testdb")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get database: %v", err)
|
||||
}
|
||||
|
||||
if db.Stats().MaxOpenConnections != 10 {
|
||||
t.Errorf("Expected max open connections to be 10, got %d", db.Stats().MaxOpenConnections)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for error handling during connection
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
srv := New()
|
||||
// Check health to see if it handles errors
|
||||
stats := srv.Health()
|
||||
if len(stats) > 0 {
|
||||
t.Fatal("Expected no databases to be configured, but found some")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package middleware
|
||||
|
||||
/** Keylock Auth Middleware **/
|
||||
/** Keycloak Auth Middleware **/
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
|
||||
@@ -31,7 +31,7 @@ func NewServer() *http.Server {
|
||||
|
||||
NewServer := &Server{
|
||||
port: port,
|
||||
db: database.New(),
|
||||
db: database.New(cfg),
|
||||
}
|
||||
|
||||
// Declare Server config
|
||||
|
||||
42
internal/utils/env.go
Normal file
42
internal/utils/env.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetEnv retrieves environment variable with fallback
|
||||
func GetEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetEnvAsInt retrieves environment variable as integer with fallback
|
||||
func GetEnvAsInt(key string, defaultValue int) int {
|
||||
valueStr := GetEnv(key, "")
|
||||
if value, err := strconv.Atoi(valueStr); err == nil {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetEnvAsBool retrieves environment variable as boolean with fallback
|
||||
func GetEnvAsBool(key string, defaultValue bool) bool {
|
||||
valueStr := GetEnv(key, "")
|
||||
if value, err := strconv.ParseBool(valueStr); err == nil {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetEnvAsDuration retrieves environment variable as duration with fallback
|
||||
func GetEnvAsDuration(key string, defaultValue time.Duration) time.Duration {
|
||||
valueStr := GetEnv(key, "")
|
||||
if value, err := time.ParseDuration(valueStr); err == nil {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
Reference in New Issue
Block a user