- Changed module from github.com/bsf/orchard to gitlab.global.bsf.tools/esv/bsf/bsf-integration/orchard/orchard-mvp - Updated all internal import paths to match 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
154 lines
3.6 KiB
Go
154 lines
3.6 KiB
Go
package api
|
|
|
|
import (
|
|
"embed"
|
|
"io/fs"
|
|
|
|
"gitlab.global.bsf.tools/esv/bsf/bsf-integration/orchard/orchard-mvp/internal/storage"
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
//go:embed static/*
|
|
var staticFiles embed.FS
|
|
|
|
// SetupRouter configures all API routes
|
|
func SetupRouter(db *storage.Database, s3 *storage.S3Storage, logger *zap.Logger) *gin.Engine {
|
|
router := gin.New()
|
|
router.Use(gin.Recovery())
|
|
router.Use(LoggerMiddleware(logger))
|
|
|
|
handler := NewHandler(db, s3, logger)
|
|
|
|
// Serve static files from embedded FS
|
|
staticFS, _ := fs.Sub(staticFiles, "static")
|
|
|
|
// Root - serve index.html
|
|
router.GET("/", func(c *gin.Context) {
|
|
data, err := fs.ReadFile(staticFS, "index.html")
|
|
if err != nil {
|
|
c.String(500, "Error loading page: "+err.Error())
|
|
return
|
|
}
|
|
c.Data(200, "text/html; charset=utf-8", data)
|
|
})
|
|
|
|
// CSS file
|
|
router.GET("/static/style.css", func(c *gin.Context) {
|
|
data, err := fs.ReadFile(staticFS, "style.css")
|
|
if err != nil {
|
|
c.String(404, "Not found: "+err.Error())
|
|
return
|
|
}
|
|
c.Data(200, "text/css; charset=utf-8", data)
|
|
})
|
|
|
|
// JS file
|
|
router.GET("/static/app.js", func(c *gin.Context) {
|
|
data, err := fs.ReadFile(staticFS, "app.js")
|
|
if err != nil {
|
|
c.String(404, "Not found: "+err.Error())
|
|
return
|
|
}
|
|
c.Data(200, "application/javascript; charset=utf-8", data)
|
|
})
|
|
|
|
// Health check
|
|
router.GET("/health", handler.Health)
|
|
|
|
// API v1
|
|
v1 := router.Group("/api/v1")
|
|
{
|
|
// Authentication middleware
|
|
v1.Use(AuthMiddleware(db))
|
|
|
|
// Grove routes
|
|
groves := v1.Group("/groves")
|
|
{
|
|
groves.GET("", handler.ListGroves)
|
|
groves.POST("", handler.CreateGrove)
|
|
groves.GET("/:grove", handler.GetGrove)
|
|
}
|
|
|
|
// Tree routes
|
|
trees := v1.Group("/grove/:grove/trees")
|
|
{
|
|
trees.GET("", handler.ListTrees)
|
|
trees.POST("", handler.CreateTree)
|
|
}
|
|
|
|
// Fruit routes (content-addressable storage)
|
|
fruits := v1.Group("/grove/:grove/:tree")
|
|
{
|
|
// Upload artifact (cultivate)
|
|
fruits.POST("/cultivate", handler.Cultivate)
|
|
|
|
// Download artifact (harvest)
|
|
fruits.GET("/+/:ref", handler.Harvest)
|
|
|
|
// List grafts (tags/versions)
|
|
fruits.GET("/grafts", handler.ListGrafts)
|
|
|
|
// Create graft (tag)
|
|
fruits.POST("/graft", handler.CreateGraft)
|
|
|
|
// Get consumers
|
|
fruits.GET("/consumers", handler.GetConsumers)
|
|
}
|
|
|
|
// Direct fruit access by hash
|
|
v1.GET("/fruit/:fruit", handler.GetFruit)
|
|
|
|
// Search
|
|
v1.GET("/search", handler.Search)
|
|
}
|
|
|
|
// Compatibility endpoint matching the URL pattern from spec
|
|
// /grove/{project}/{tree}/+/{ref}
|
|
router.GET("/grove/:grove/:tree/+/:ref", AuthMiddleware(db), handler.Harvest)
|
|
|
|
return router
|
|
}
|
|
|
|
// LoggerMiddleware logs requests
|
|
func LoggerMiddleware(logger *zap.Logger) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
c.Next()
|
|
|
|
logger.Info("request",
|
|
zap.String("method", c.Request.Method),
|
|
zap.String("path", c.Request.URL.Path),
|
|
zap.Int("status", c.Writer.Status()),
|
|
zap.String("client_ip", c.ClientIP()),
|
|
)
|
|
}
|
|
}
|
|
|
|
// AuthMiddleware handles authentication
|
|
func AuthMiddleware(db *storage.Database) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Check for API key in header
|
|
apiKey := c.GetHeader("X-Orchard-API-Key")
|
|
if apiKey != "" {
|
|
// TODO: Validate API key against database
|
|
// For now, extract user ID from key
|
|
c.Set("user_id", "api-user")
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Check for Bearer token
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader != "" {
|
|
// TODO: Implement OIDC/SAML validation
|
|
c.Set("user_id", "bearer-user")
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Allow anonymous access for public groves (read only)
|
|
c.Set("user_id", "anonymous")
|
|
c.Next()
|
|
}
|
|
}
|