Files
marktvogt.de/backend/internal/server/server.go
T
vikingowl a1d93f7a8e feat: implement MVP backend API
Go backend with Gin, pgx, Valkey (go-valkey), and PostGIS.

Domains:
- Market search with PostGIS geo-queries (ST_DWithin, ST_Distance),
  German full-text search (tsvector + ILIKE fallback for compound words),
  date range filtering, pagination, and slug-based detail endpoint
- Auth with email+password (bcrypt), JWT access tokens (15min),
  session tokens (30d, dual Valkey+Postgres storage), OAuth
  (Google/GitHub/Facebook), magic links, and TOTP 2FA
- User profile with CRUD, soft-delete (30d grace), and restore

Infrastructure:
- 6 database migrations (users, sessions, oauth_accounts, magic_links,
  markets with PostGIS+FTS, totp_secrets)
- Middleware: recovery, request ID, structured logging (slog), CORS,
  per-IP rate limiting, JWT auth
- Seed data: 10 medieval markets across DACH region
- Docker Compose (PostGIS 17 + Valkey 8), multi-stage Dockerfile,
  Woodpecker CI pipeline, Kubernetes manifests
- Justfile, golangci-lint config, env example
2026-02-18 05:52:20 +01:00

72 lines
1.3 KiB
Go

package server
import (
"context"
"fmt"
"log/slog"
"net/http"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/valkey-io/valkey-go"
"marktvogt.de/backend/internal/config"
"marktvogt.de/backend/internal/middleware"
)
type Server struct {
cfg *config.Config
router *gin.Engine
http *http.Server
db *pgxpool.Pool
valkey valkey.Client
}
func New(cfg *config.Config, db *pgxpool.Pool, vk valkey.Client) *Server {
if !cfg.IsDev() {
gin.SetMode(gin.ReleaseMode)
}
router := gin.New()
router.Use(
middleware.Recovery(),
middleware.RequestID(),
middleware.Logging(),
middleware.CORS(cfg.CORS.Origins),
middleware.RateLimit(cfg.Rate.RPS, cfg.Rate.Burst),
)
s := &Server{
cfg: cfg,
router: router,
db: db,
valkey: vk,
http: &http.Server{
Addr: cfg.Addr(),
Handler: router,
},
}
s.registerRoutes()
return s
}
func (s *Server) Start() error {
slog.Info("starting server", "addr", s.cfg.Addr(), "env", s.cfg.App.Env)
if err := s.http.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return fmt.Errorf("server listen: %w", err)
}
return nil
}
func (s *Server) Shutdown(ctx context.Context) error {
slog.Info("shutting down server")
return s.http.Shutdown(ctx)
}
func (s *Server) Router() *gin.Engine {
return s.router
}