some fixes for docker and migrations

This commit is contained in:
Pedro Pérez 2025-10-31 00:17:48 +01:00
parent 84ec54a893
commit 8d908a0502
13 changed files with 202 additions and 26 deletions

View File

@ -1,2 +0,0 @@
POSTGRES_USER=developer
POSTGRES_PW=secret

View File

@ -1,2 +0,0 @@
POSTGRES_USER=developer
POSTGRES_PW=secret

View File

@ -4,26 +4,41 @@ services:
image: postgres:17.6-alpine3.22
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PW}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=meteologica
ports:
- "5432:5432"
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d meteologica"]
interval: 5s
timeout: 5s
retries: 5
service_a:
build:
context: ./service_a
dockerfile: Dockerfile
context: .
dockerfile: ./service_a/Dockerfile
container_name: service_a
environment:
- URL_SERVICE_A=${URL_SERVICE_A}
- DSN=${DSN}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
ports:
- "8080:8080"
restart: unless-stopped
depends_on:
database:
condition: service_healthy
service_b:
build:
context: ./service_b
dockerfile: Dockerfile
context: .
dockerfile: ./service_b/Dockerfile
container_name: service_b
environment:
- URL_SERVICE_A=${URL_SERVICE_A}
ports:
- "8090:8090"
restart: unless-stopped

31
pkg/app.go Normal file
View File

@ -0,0 +1,31 @@
package pkg
import (
"bufio"
"os"
"strings"
)
func LoadEnvFile(envDirectory string) error {
file, err := os.Open(envDirectory)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
os.Setenv(key, value)
}
return scanner.Err()
}

View File

@ -2,10 +2,13 @@ FROM golang:1.25.2-alpine3.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
COPY server/ ./server/
COPY internal/ ./internal/
COPY assets/ ./assets/
COPY pkg/ ./pkg/
COPY service_a/go.mod service_a/go.sum ./service_a/
COPY service_a/server/ ./service_a/server/
COPY service_a/internal/ ./service_a/internal/
COPY service_a/assets/ ./service_a/assets/
WORKDIR /app/service_a
RUN go mod download
@ -13,13 +16,13 @@ RUN go test ./... -v
RUN rm -rf ./assets/
RUN go build -o /app/service_a ./server/main.go
RUN go build -o /app/bin/service_a ./server/main.go
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/service_a /app/service_a
COPY --from=builder /app/bin/service_a /app/service_a
EXPOSE 8080

View File

@ -3,6 +3,7 @@ module servicea
go 1.25.2
require (
github.com/golang-migrate/migrate/v4 v4.19.0
github.com/jackc/pgx/v5 v5.7.6
github.com/stretchr/testify v1.11.1
pkg v0.0.0-00010101000000-000000000000
@ -10,10 +11,13 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
golang.org/x/crypto v0.37.0 // indirect

View File

@ -1,7 +1,40 @@
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
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.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE=
github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
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=
@ -14,6 +47,20 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@ -23,10 +70,22 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -2,8 +2,16 @@ package app
import (
"context"
"database/sql"
"embed"
"errors"
"fmt"
"log/slog"
"os"
mig "github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
"github.com/golang-migrate/migrate/v4/source/iofs"
"github.com/jackc/pgx/v5/pgxpool"
_ "github.com/jackc/pgx/v5/stdlib"
)
@ -22,3 +30,36 @@ func NewPGXPool(datasource string) *pgxpool.Pool {
slog.Info("connected to database", "datasource", datasource)
return dbPool
}
func Migrate(database embed.FS) {
dbConn, err := sql.Open("pgx", os.Getenv("DATASOURCE"))
if err != nil {
slog.Error("error opening database connection", "error", err)
return
}
defer dbConn.Close()
d, err := iofs.New(database, "database/migrations")
if err != nil {
slog.Error("error creating migration source", "error", err)
return
}
m, err := mig.NewWithSourceInstance("iofs", d, fmt.Sprintf("postgres://%s:%s@%s", os.Getenv("POSTGRES_USER"), os.Getenv("POSTGRES_PASSWORD"), os.Getenv("DSN")))
if err != nil {
slog.Error("error creating migration instance", "error", err)
return
}
err = m.Up()
if err != nil && !errors.Is(err, mig.ErrNoChange) {
slog.Error("cannot migrate", "error", err)
panic(err)
}
if errors.Is(err, mig.ErrNoChange) {
slog.Info("migration has no changes")
return
}
slog.Info("migration done")
}

View File

@ -32,7 +32,7 @@ func Test_CSV_ParseFile(t *testing.T) {
assert.Equal(t, float32(11.55), record.MaxTemp)
assert.Equal(t, float32(6.25), record.MinTemp)
assert.Equal(t, float32(0), record.Rainfall)
assert.Equal(t, float32(10), record.Cloudiness)
assert.Equal(t, int(10), record.Cloudiness)
},
validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) {
assert.Empty(t, rejected)
@ -61,7 +61,7 @@ func Test_CSV_ParseFile(t *testing.T) {
},
validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) {
assert.Equal(t, 1, len(rejected))
assert.Contains(t, rejected[0].Reason, "missing or invalid city field")
assert.Contains(t, rejected[0].Reason, "missing or invalid location")
assert.Equal(t, "2025/10/12;11,55;6,25;0;10", rejected[0].RowValue)
},
},
@ -87,7 +87,7 @@ func Test_CSV_ParseFile(t *testing.T) {
},
validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) {
assert.Equal(t, 1, len(rejected))
assert.Contains(t, rejected[0].Reason, "missing or invalid max temp field")
assert.Contains(t, rejected[0].Reason, "missing or invalid max temp")
assert.Equal(t, "2025/10/12;Madrid;;6,25;0;10", rejected[0].RowValue)
},
},
@ -101,7 +101,7 @@ func Test_CSV_ParseFile(t *testing.T) {
},
validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) {
assert.Equal(t, 1, len(rejected))
assert.Contains(t, rejected[0].Reason, "missing or invalid city field")
assert.Contains(t, rejected[0].Reason, "missing or invalid location")
assert.Equal(t, "2025/10/12;;11,55;6,25;0;10", rejected[0].RowValue)
},
},
@ -116,7 +116,7 @@ func Test_CSV_ParseFile(t *testing.T) {
},
validateRejected: func(t *testing.T, rejected []meteo.RejectedMeteoData) {
assert.Equal(t, 1, len(rejected))
assert.Contains(t, rejected[0].Reason, "missing or invalid date field")
assert.Contains(t, rejected[0].Reason, "missing or invalid date")
assert.Equal(t, ";Madrid;11,55;6,25;0;10", rejected[0].RowValue)
},
},

View File

@ -1,17 +1,31 @@
package main
import (
"embed"
"fmt"
"log/slog"
"net/http"
"os"
"pkg"
"servicea/internal/app"
"servicea/internal/domains/meteo"
"servicea/internal/router"
"time"
)
//go:embed database/migrations
var database embed.FS
func init() {
err := pkg.LoadEnvFile("./../.env")
if err != nil {
slog.Warn("error loading env file", "error", err)
}
}
func main() {
pool := app.NewPGXPool("postgres://developer:secret@localhost:5432/meteologica?sslmode=disable")
pool := app.NewPGXPool(fmt.Sprintf("postgres://%s:%s@%s", os.Getenv("POSTGRES_USER"), os.Getenv("POSTGRES_PASSWORD"), os.Getenv("DSN")))
app.Migrate(database)
mux := router.SetupRoutes()

View File

@ -2,17 +2,21 @@ FROM golang:1.25.2-alpine3.22 AS builder
WORKDIR /app
COPY go.mod ./
COPY server/ ./server/
COPY pkg/ ./pkg/
COPY service_b/go.mod service_b/go.sum ./service_b/
COPY service_b/server/ ./service_b/server/
COPY service_b/internal/ ./service_b/internal/
WORKDIR /app/service_b
RUN go mod download
RUN go build -o /app/service_b ./server/main.go
RUN go build -o /app/bin/service_b ./server/main.go
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/service_b /app/service_b
COPY --from=builder /app/bin/service_b /app/service_b
EXPOSE 8090

View File

@ -8,6 +8,7 @@ import (
"io"
"log/slog"
"net/http"
"os"
"time"
"github.com/cenkalti/backoff/v5"
@ -42,7 +43,7 @@ func (s *Service) GetWeatherByCity(ctx context.Context, params GetMeteoData) (Me
toDate := fromDate.AddDate(0, 0, params.Days-1)
operation := func() (*http.Response, error) {
url := fmt.Sprintf("http://localhost:8080/data?city=%s&from=%s&to=%s",
url := fmt.Sprintf("%s/data?city=%s&from=%s&to=%s", os.Getenv("URL_SERVICE_A"),
params.Location, params.Date, toDate.Format("2006-01-02"))
slog.Info("url", "url", url)

View File

@ -4,11 +4,19 @@ import (
"fmt"
"log/slog"
"net/http"
"pkg"
"serviceb/internal/domains/meteo"
"serviceb/internal/router"
"time"
)
func init() {
err := pkg.LoadEnvFile("./../.env")
if err != nil {
slog.Warn("error loading env file", "error", err)
}
}
func main() {
mux := router.SetupRoutes()