feat: add url shortner
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
chiMiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
@@ -10,6 +12,8 @@ import (
|
||||
"github.com/prabhupadasays/spsays-bff/internal/handler"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/middleware"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/worker"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/shorturl"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -19,6 +23,11 @@ type Server struct {
|
||||
logger *zap.Logger
|
||||
router chi.Router
|
||||
mongo *repository.MongoDB
|
||||
pg *repository.Postgres
|
||||
redis *repository.Redis
|
||||
|
||||
// Worker lifecycle
|
||||
workerCancel context.CancelFunc
|
||||
|
||||
// Handlers
|
||||
bookH *handler.BookHandler
|
||||
@@ -31,6 +40,7 @@ type Server struct {
|
||||
otherDocH *handler.OtherDocumentHandler
|
||||
transcriptionH *handler.TranscriptionHandler
|
||||
searchH *handler.SearchHandler
|
||||
shortURLH *handler.ShortURLHandler
|
||||
}
|
||||
|
||||
// New creates a new Server with all routes registered.
|
||||
@@ -72,10 +82,52 @@ func New(cfg *config.Config, logger *zap.Logger) *Server {
|
||||
searchH: handler.NewSearchHandler(tsClient),
|
||||
}
|
||||
|
||||
// URL shortener: Postgres (canonical) + Redis (queue + hot cache) + background workers.
|
||||
// Connection failures degrade gracefully — the BFF stays up; only /share returns 503.
|
||||
s.initShortener(cfg, logger)
|
||||
|
||||
s.setupRouter()
|
||||
return s
|
||||
}
|
||||
|
||||
// initShortener wires Postgres + Redis + the shorturl handler/workers.
|
||||
// On connection failure it logs and leaves the components nil — the handler
|
||||
// short-circuits to 503 so the rest of the BFF keeps working.
|
||||
func (s *Server) initShortener(cfg *config.Config, logger *zap.Logger) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s.workerCancel = cancel
|
||||
|
||||
pg, err := repository.NewPostgres(ctx, cfg.PostgresURI, logger)
|
||||
if err != nil {
|
||||
logger.Warn("URL shortener: Postgres unavailable — /share endpoints will return 503",
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
s.pg = pg
|
||||
|
||||
migrationsDir, err := filepath.Abs("migrations")
|
||||
if err == nil {
|
||||
if err := pg.RunMigrations(ctx, migrationsDir); err != nil {
|
||||
logger.Warn("URL shortener: migrations failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
rd, err := repository.NewRedis(ctx, cfg.RedisURI, logger)
|
||||
if err != nil {
|
||||
logger.Warn("URL shortener: Redis unavailable — /share endpoints will return 503",
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
s.redis = rd
|
||||
|
||||
shortRepo := repository.NewShortURLRepo(pg)
|
||||
queue := shorturl.NewQueue(rd.Client)
|
||||
s.shortURLH = handler.NewShortURLHandler(shortRepo, queue, logger)
|
||||
|
||||
w := worker.NewShortURLWorker(shortRepo, queue, logger)
|
||||
w.Run(ctx)
|
||||
}
|
||||
|
||||
// Router returns the chi router.
|
||||
func (s *Server) Router() http.Handler {
|
||||
return s.router
|
||||
@@ -83,10 +135,20 @@ func (s *Server) Router() http.Handler {
|
||||
|
||||
// Close shuts down all connections gracefully.
|
||||
func (s *Server) Close() {
|
||||
if s.workerCancel != nil {
|
||||
s.workerCancel()
|
||||
}
|
||||
if err := s.mongo.Close(); err != nil {
|
||||
s.logger.Error("failed to close MongoDB connection", zap.Error(err))
|
||||
}
|
||||
// TODO: close PostgreSQL, Redis, etc. connections
|
||||
if s.pg != nil {
|
||||
s.pg.Close()
|
||||
}
|
||||
if s.redis != nil {
|
||||
if err := s.redis.Close(); err != nil {
|
||||
s.logger.Error("failed to close Redis connection", zap.Error(err))
|
||||
}
|
||||
}
|
||||
s.logger.Info("all connections closed")
|
||||
}
|
||||
|
||||
@@ -319,9 +381,21 @@ func (s *Server) setupRouter() {
|
||||
// TODO: register mention handlers
|
||||
})
|
||||
|
||||
// URL Shortener
|
||||
r.Route("/s", func(r chi.Router) {
|
||||
// TODO: register URL shortener handlers
|
||||
// URL Shortener — Postgres-backed, Redis-queued.
|
||||
// The handler short-circuits to 503 if Postgres or Redis are unavailable.
|
||||
r.Route("/share", func(r chi.Router) {
|
||||
if s.shortURLH != nil {
|
||||
r.Post("/", s.shortURLH.Create)
|
||||
r.Get("/{code}", s.shortURLH.Resolve)
|
||||
} else {
|
||||
stub := func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
w.Write([]byte(`{"success":false,"error":{"code":503,"message":"url shortener unavailable"}}`))
|
||||
}
|
||||
r.Post("/", stub)
|
||||
r.Get("/{code}", stub)
|
||||
}
|
||||
})
|
||||
|
||||
// Push Notifications
|
||||
|
||||
Reference in New Issue
Block a user