diff --git a/api-service b/api-service new file mode 100644 index 00000000..51280c7d Binary files /dev/null and b/api-service differ diff --git a/example.env b/example.env new file mode 100644 index 00000000..efa0b83e --- /dev/null +++ b/example.env @@ -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 diff --git a/go.mod b/go.mod index a907269d..0876ed0e 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 5c726af9..f52505ad 100644 --- a/go.sum +++ b/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= diff --git a/internal/config/config.go b/internal/config/config.go index 1a98dca9..86271490 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,12 +4,15 @@ import ( "log" "os" "strconv" + "strings" + "time" ) type Config struct { - Server ServerConfig - Database DatabaseConfig - Keycloak KeycloakConfig + Server ServerConfig + Databases map[string]DatabaseConfig + ReadReplicas map[string][]DatabaseConfig // For read replicas + Keycloak KeycloakConfig } type ServerConfig struct { @@ -18,12 +21,20 @@ type ServerConfig struct { } type DatabaseConfig struct { - Host string - Port int - Username string - Password string - Database string - Schema string + 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") - } - if c.Database.Password == "" { - log.Fatal("Database password is required") - } - if c.Database.Database == "" { - log.Fatal("Database name is required") + + for name, db := range c.Databases { + if db.Host == "" { + log.Fatalf("Database host is required for %s", name) + } + 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) + } } // Validate Keycloak configuration if enabled diff --git a/internal/database/database.go b/internal/database/database.go index 99cf1125..69824dc7 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -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 - mu sync.RWMutex + sqlDatabases map[string]*sql.DB + mongoClients map[string]*mongo.Client + 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 + diff --git a/internal/database/database_test.go b/internal/database/database_test.go index c193671e..8331ee6d 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -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 - } - - return dbContainer.Terminate, err +// Mock for the database service +type MockService struct { + mock.Mock } -func TestMain(m *testing.M) { - teardown, err := mustStartPostgresContainer() - if err != nil { - log.Fatalf("could not start postgres container: %v", err) - } +func (m *MockService) Health() map[string]map[string]string { + args := m.Called() + return args.Get(0).(map[string]map[string]string) +} - m.Run() +func (m *MockService) GetDB(name string) (*sql.DB, error) { + args := m.Called(name) + return args.Get(0).(*sql.DB), args.Error(1) +} - 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") + } +} diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index a88bc46a..5da76ca6 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -1,6 +1,6 @@ package middleware -/** Keylock Auth Middleware **/ +/** Keycloak Auth Middleware **/ import ( "crypto/rsa" "encoding/base64" diff --git a/internal/server/server.go b/internal/server/server.go index 4a0b8252..cef71ca2 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -31,7 +31,7 @@ func NewServer() *http.Server { NewServer := &Server{ port: port, - db: database.New(), + db: database.New(cfg), } // Declare Server config diff --git a/internal/utils/env.go b/internal/utils/env.go new file mode 100644 index 00000000..da733e4b --- /dev/null +++ b/internal/utils/env.go @@ -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 +}