diff --git a/cmd/server/http/handlers.go b/cmd/server/http/handlers.go index 40a1a5a..9f3debf 100644 --- a/cmd/server/http/handlers.go +++ b/cmd/server/http/handlers.go @@ -8,7 +8,7 @@ import ( "runtime/debug" "github.com/bnkamalesh/errors" - "github.com/bnkamalesh/webgo/v6" + "github.com/bnkamalesh/webgo/v7" "github.com/bnkamalesh/goapp/internal/api" "github.com/bnkamalesh/goapp/internal/pkg/logger" @@ -106,7 +106,9 @@ func errWrapper(h func(w http.ResponseWriter, r *http.Request) error) http.Handl status, msg, _ := errors.HTTPStatusCodeMessage(err) webgo.SendError(w, msg, status) - _ = logger.Error(r.Context(), errors.Stacktrace(err)) + if status > 499 { + _ = logger.Error(r.Context(), errors.Stacktrace(err)) + } } } diff --git a/cmd/server/http/handlers_users.go b/cmd/server/http/handlers_users.go index bd7d15f..ae1684f 100644 --- a/cmd/server/http/handlers_users.go +++ b/cmd/server/http/handlers_users.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/bnkamalesh/errors" - "github.com/bnkamalesh/webgo/v6" + "github.com/bnkamalesh/webgo/v7" "github.com/bnkamalesh/goapp/internal/users" ) diff --git a/cmd/server/http/http.go b/cmd/server/http/http.go index 8e72afe..43513e1 100644 --- a/cmd/server/http/http.go +++ b/cmd/server/http/http.go @@ -9,8 +9,8 @@ import ( "github.com/bnkamalesh/goapp/internal/api" "github.com/bnkamalesh/goapp/internal/pkg/apm" - "github.com/bnkamalesh/webgo/v6" - "github.com/bnkamalesh/webgo/v6/middleware/accesslog" + "github.com/bnkamalesh/webgo/v7" + "github.com/bnkamalesh/webgo/v7/middleware/accesslog" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) @@ -24,18 +24,18 @@ type Config struct { DialTimeout time.Duration TemplatesBasePath string + EnableAccessLog bool } type HTTP struct { - apis api.Server listener string - server *http.Server + server *webgo.Router } // Start starts the HTTP server func (h *HTTP) Start() error { - webgo.LOGHANDLER.Info("HTTP server, listening on", h.listener) - return h.server.ListenAndServe() + h.server.Start() + return nil } // NewService returns an instance of HTTP with all its dependencies set @@ -61,7 +61,10 @@ func NewService(cfg *Config, apis api.Server) (*HTTP, error) { handlers.routes()..., ) - router.Use(accesslog.AccessLog) + if cfg.EnableAccessLog { + router.Use(accesslog.AccessLog) + router.UseOnSpecialHandlers(accesslog.AccessLog) + } router.Use(panicRecoverer) // in this app, /-/ prefixed routes are used for healthchecks, readiness checks etc. @@ -79,17 +82,9 @@ func NewService(cfg *Config, apis api.Server) (*HTTP, error) { return wctx.Route.Pattern }) - httpServer := &http.Server{ - Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), - Handler: apm.NewHTTPMiddleware()(router), - ReadTimeout: cfg.ReadTimeout, - ReadHeaderTimeout: cfg.ReadTimeout, - WriteTimeout: cfg.WriteTimeout, - IdleTimeout: cfg.ReadTimeout * 2, - } - + _ = apm.NewHTTPMiddleware() return &HTTP{ - server: httpServer, + server: router, listener: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), }, nil } diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 5100910..d27eef6 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,26 +1,35 @@ version: "3.9" services: redis: - image: "redis:alpine" + image: "redis:7" networks: - goapp_network postgres: - image: "postgres" + image: "postgres:16" environment: POSTGRES_PASSWORD: gauserpassword POSTGRES_USER: gauser POSTGRES_DB: goapp + ports: + - "5432:5432" networks: - goapp_network goapp: - image: golang:1.21 + image: golang:1.22 volumes: - ${PWD}:/app working_dir: /app tty: true - # command: go run main.go + environment: + TEMPLATES_BASEPATH: /app/cmd/server/http/web/templates + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + POSTGRES_STORENAME: "goapp" + POSTGRES_USERNAME: "gauser" + POSTGRES_PASSWORD: "gauserpassword" + command: go run main.go ports: - "8080:8080" depends_on: diff --git a/go.mod b/go.mod index 429b95f..3b7be55 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,8 @@ go 1.21 require ( github.com/Masterminds/squirrel v1.5.4 github.com/bnkamalesh/errors v0.11.1 - github.com/bnkamalesh/webgo/v6 v6.7.0 + github.com/bnkamalesh/webgo/v7 v7.0.0 + github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.5.3 github.com/prometheus/client_golang v1.18.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 @@ -33,7 +34,6 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect diff --git a/go.sum b/go.sum index b9c3960..d6108e8 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bnkamalesh/errors v0.11.1 h1:lxPIza88BwIf/k8jD1UlQyLS+QzEMCwqM2I6Uo81GVs= github.com/bnkamalesh/errors v0.11.1/go.mod h1:X8+uM23IDiSq8q5kYC3dEP+sMTRlZz0IWFXo7sfrn18= -github.com/bnkamalesh/webgo/v6 v6.7.0 h1:OjS87ncnwzHeWCXyr4TPIOcW4qlNifHiRy7/Kk8VXRA= -github.com/bnkamalesh/webgo/v6 v6.7.0/go.mod h1:QHOzBydSNRq5oYWgg9v7A0RIUlyaZeT9nYCx41Sgzd8= +github.com/bnkamalesh/webgo/v7 v7.0.0 h1:Kdr32fx45IlSKmJfUeFipLKXxV+ETgynKpFV1/fCtmU= +github.com/bnkamalesh/webgo/v7 v7.0.0/go.mod h1:zS1pR4L5TSKja095la5nbv2yZ59/c4PoSungO93E8UQ= 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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= diff --git a/internal/configs/configs.go b/internal/configs/configs.go index 6e61993..05636c7 100644 --- a/internal/configs/configs.go +++ b/internal/configs/configs.go @@ -6,6 +6,7 @@ import ( "time" "github.com/bnkamalesh/goapp/cmd/server/http" + "github.com/bnkamalesh/goapp/internal/pkg/postgres" ) type env string @@ -31,6 +32,7 @@ type Configs struct { // HTTP returns the configuration required for HTTP package func (cfg *Configs) HTTP() (*http.Config, error) { return &http.Config{ + EnableAccessLog: (cfg.Environment == EnvLocal) || (cfg.Environment == EnvTest), TemplatesBasePath: strings.TrimSpace(os.Getenv("TEMPLATES_BASEPATH")), Port: 8080, ReadTimeout: time.Second * 5, @@ -39,6 +41,32 @@ func (cfg *Configs) HTTP() (*http.Config, error) { }, nil } +func (cfg *Configs) Postgres() *postgres.Config { + return &postgres.Config{ + Host: os.Getenv("POSTGRES_HOST"), + Port: os.Getenv("POSTGRES_PORT"), + Driver: "postgres", + + StoreName: os.Getenv("POSTGRES_STORENAME"), + Username: os.Getenv("POSTGRES_USERNAME"), + Password: os.Getenv("POSTGRES_PASSWORD"), + + ConnPoolSize: 24, + ReadTimeout: time.Second * 3, + WriteTimeout: time.Second * 6, + IdleTimeout: time.Minute, + DialTimeout: time.Second * 3, + } +} + +func (cfg *Configs) UserPostgresTable() string { + return "users" +} + +func (cfg *Configs) UserNotesPostgresTable() string { + return "user_notes" +} + func loadEnv() env { switch env(os.Getenv("ENV")) { case EnvLocal: diff --git a/internal/pkg/postgres/postgres.go b/internal/pkg/postgres/postgres.go index e9fee84..cf7673f 100644 --- a/internal/pkg/postgres/postgres.go +++ b/internal/pkg/postgres/postgres.go @@ -49,8 +49,8 @@ func (cfg *Config) ConnURL() string { ) } -// NewService returns a new instance of PGX pool -func NewService(cfg *Config) (*pgxpool.Pool, error) { +// NewPool returns a new instance of PGX pool +func NewPool(cfg *Config) (*pgxpool.Pool, error) { poolcfg, err := pgxpool.ParseConfig(cfg.ConnURL()) if err != nil { return nil, errors.Wrap(err, "failed to parse config") diff --git a/internal/users/store_postgres.go b/internal/users/store_postgres.go index e4d7a43..dcbc6d5 100644 --- a/internal/users/store_postgres.go +++ b/internal/users/store_postgres.go @@ -39,6 +39,9 @@ func (ps *pgstore) GetUserByEmail(ctx context.Context, email string) (*User, err row := ps.pqdriver.QueryRow(ctx, query, args...) err = row.Scan(user.ID, user.Name, phone, address) if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, errors.NotFoundErr(ErrUserEmailNotFound, email) + } return nil, errors.Wrap(err, "failed getting user info") } user.Address = address.String diff --git a/internal/users/users.go b/internal/users/users.go index bce9b3a..4dcc577 100644 --- a/internal/users/users.go +++ b/internal/users/users.go @@ -8,6 +8,10 @@ import ( "github.com/bnkamalesh/goapp/internal/pkg/logger" ) +var ( + ErrUserEmailNotFound = errors.New("user with the email not found") +) + type User struct { ID string Name string diff --git a/main.go b/main.go index 33cf488..f9b2af6 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/bnkamalesh/goapp/internal/configs" "github.com/bnkamalesh/goapp/internal/pkg/apm" "github.com/bnkamalesh/goapp/internal/pkg/logger" + "github.com/bnkamalesh/goapp/internal/pkg/postgres" "github.com/bnkamalesh/goapp/internal/pkg/sysignals" "github.com/bnkamalesh/goapp/internal/users" ) @@ -131,8 +132,12 @@ func main() { ) apmhandler := startAPM(ctx, cfgs) + pqdriver, err := postgres.NewPool(cfgs.Postgres()) + if err != nil { + panic(errors.Wrap(err)) + } - userPGstore := users.NewPostgresStore(nil, "") + userPGstore := users.NewPostgresStore(pqdriver, cfgs.UserPostgresTable()) userSvc := users.NewService(userPGstore) svrAPIs := api.NewServer(userSvc) hserver, gserver := startServers(svrAPIs, cfgs) diff --git a/schemas/functions.sql b/schemas/functions.sql new file mode 100644 index 0000000..1e3ea13 --- /dev/null +++ b/schemas/functions.sql @@ -0,0 +1,11 @@ +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN + NEW.updated_at = now(); + RETURN NEW; + ELSE + RETURN OLD; + END IF; +END; +$$ language 'plpgsql'; diff --git a/schemas/user_notes.sql b/schemas/user_notes.sql new file mode 100644 index 0000000..16944de --- /dev/null +++ b/schemas/user_notes.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS user_notes ( + id UUID PRIMARY KEY, + title TEXT, + content TEXT, + user_id UUID references users(id), + created_at timestamptz DEFAULT now(), + updated_at timestamptz DEFAULT now() +); + +CREATE TRIGGER tr_users_bu BEFORE UPDATE on user_notes + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file diff --git a/schemas/users.sql b/schemas/users.sql index 7f07cc4..c42b924 100644 --- a/schemas/users.sql +++ b/schemas/users.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS Users ( +CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY, email TEXT UNIQUE, uname TEXT, @@ -6,37 +6,7 @@ CREATE TABLE IF NOT EXISTS Users ( uaddress TEXT, created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now() -) +); -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN - NEW.updated_at = now(); - RETURN NEW; - ELSE - RETURN OLD; - END IF; -END; -$$ language 'plpgsql'; --- -CREATE TABLE IF NOT EXISTS UserNotes ( - id UUID PRIMARY KEY, - title TEXT, - content TEXT, - user_id references Users(id), - created_at timestamptz DEFAULT now(), - updated_at timestamptz DEFAULT now() -) - -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - IF row(NEW.*) IS DISTINCT FROM row(OLD.*) THEN - NEW.updated_at = now(); - RETURN NEW; - ELSE - RETURN OLD; - END IF; -END; -$$ language 'plpgsql'; +CREATE TRIGGER tr_users_bu BEFORE UPDATE on users + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); \ No newline at end of file