package main import ( "github.com/jackc/pgx/v5/pgxpool" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "osdb/internal/handler" mw "osdb/internal/middleware" "osdb/internal/repository" ) // New creates and configures the Echo instance with all routes registered. func New(pool *pgxpool.Pool, users map[string]string, jwtSecret string) *echo.Echo { e := echo.New() e.HideBanner = true e.Use(middleware.Logger()) e.Use(middleware.Recover()) jwtAuth := mw.JWTAuth(jwtSecret) health := handler.NewHealthHandler() // Root health — used by docker healthcheck + ops tooling e.GET("/health", health.Health) // Versioned API group api := e.Group("/api/v1") api.GET("/health", health.Health) // Auth (story #08) authHandler := handler.NewAuthHandler(users, jwtSecret) api.POST("/auth/login", authHandler.Login) // Fruits (story #02) fruitRepo := repository.NewFruitRepo(pool) fruits := handler.NewFruitHandler(fruitRepo) g := api.Group("/fruits") g.GET("", fruits.List) g.POST("", fruits.Create, jwtAuth) g.GET("/:id", fruits.Get) g.PUT("/:id", fruits.Update, jwtAuth) g.DELETE("/:id", fruits.Delete, jwtAuth) g.GET("/:id/images", fruits.ListImages) g.POST("/:id/images", fruits.UploadImage, jwtAuth, middleware.BodyLimit("5M")) g.GET("/:id/images/:imageId", fruits.ServeImage) g.GET("/:id/images/:imageId/thumbnail", fruits.ServeThumbnail) g.DELETE("/:id/images/:imageId", fruits.DeleteImage, jwtAuth) // Publications (story #04) pubRepo := repository.NewPublicationRepo(pool) pubs := handler.NewPublicationHandler(pubRepo) // Fruit-scoped publication reads g.GET("/:id/descriptions", pubs.GetFruitDescriptions) g.GET("/:id/publication-images", pubs.GetFruitPublicationImages) p := api.Group("/publications") p.GET("", pubs.List) p.POST("", pubs.Create, jwtAuth) p.GET("/:id", pubs.Get) p.PUT("/:id", pubs.Update, jwtAuth) p.DELETE("/:id", pubs.Delete, jwtAuth) p.POST("/:id/image", pubs.UploadCoverImage, jwtAuth, middleware.BodyLimit("5M")) p.GET("/:id/image", pubs.ServeCoverImage) p.DELETE("/:id/image", pubs.DeleteCoverImage, jwtAuth) p.GET("/:id/fruits", pubs.ListFruits) p.POST("/:id/fruits", pubs.LinkFruit, jwtAuth) p.DELETE("/:id/fruits/:fruitId", pubs.UnlinkFruit, jwtAuth) p.GET("/:id/descriptions", pubs.ListDescriptions) p.POST("/:id/descriptions", pubs.UploadDescription, jwtAuth, middleware.BodyLimit("5M")) p.GET("/:id/descriptions/:descId", pubs.ServeDescription) p.DELETE("/:id/descriptions/:descId", pubs.DeleteDescription, jwtAuth) p.GET("/:id/fruit-images", pubs.ListFruitImages) p.POST("/:id/fruit-images", pubs.UploadFruitImage, jwtAuth, middleware.BodyLimit("5M")) p.GET("/:id/fruit-images/:imgId", pubs.ServeFruitImage) p.GET("/:id/fruit-images/:imgId/thumbnail", pubs.ServeFruitImageThumbnail) p.DELETE("/:id/fruit-images/:imgId", pubs.DeleteFruitImage, jwtAuth) // Admin (story #07) — all admin routes require auth admin := handler.NewAdminHandler(fruitRepo, pubRepo) adminGroup := api.Group("/admin", jwtAuth) adminGroup.POST("/backfill-thumbnails", admin.BackfillThumbnails) return e }