Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions backend/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/rsa"
"crypto/x509"
"database/sql"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
Expand Down Expand Up @@ -733,6 +734,17 @@ func registerHandlers(e *echo.Echo, cfg config.Config, db *sql.DB) (*sshinfra.SS
rateLimitHandler := handler.NewRateLimitHandler()
rootHandler := handler.NewRootHandler()

thirdPartyLicenses := loadLicensesFromFile(cfg.LicensesFilePath)
metaHandler := handler.NewMetaHandler(handler.BuildInfo{
AppName: cfg.AppName,
Version: version,
GitCommit: commit,
BuildDate: buildTime,
LicenseName: cfg.LicenseName,
SourceURL: cfg.SourceURL,
}, thirdPartyLicenses)
metaHandler.RegisterRoutes(e)

gitHTTPHandler := handler.NewGitHTTPHandler(
cfg.GitDataRoot,
repoGitResolver,
Expand Down Expand Up @@ -923,6 +935,22 @@ func registerHandlers(e *echo.Echo, cfg config.Config, db *sql.DB) (*sshinfra.SS
return sshServer, nil
}

func loadLicensesFromFile(path string) []handler.LicenseEntry {
log := logger.Global()
data, err := os.ReadFile(path)
if err != nil {
log.Warn().Err(err).Str("path", path).Msg("failed to load licenses file")
return []handler.LicenseEntry{}
}

var entries []handler.LicenseEntry
if err := json.Unmarshal(data, &entries); err != nil {
log.Warn().Err(err).Str("path", path).Msg("failed to parse licenses file")
return []handler.LicenseEntry{}
}
return entries
}

type noopMCPEnqueuer struct{}

func (noopMCPEnqueuer) EnqueueMCPVerification(context.Context, queue.MCPVerificationPayload) error {
Expand Down
8 changes: 8 additions & 0 deletions backend/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ type Config struct {
TLSCertFile string
TLSKeyFile string
TrustedProxyCIDRs string
AppName string
LicenseName string
SourceURL string
LicensesFilePath string
}

func Load() Config {
Expand Down Expand Up @@ -96,6 +100,10 @@ func Load() Config {
TLSCertFile: os.Getenv("TLS_CERT_FILE"),
TLSKeyFile: os.Getenv("TLS_KEY_FILE"),
TrustedProxyCIDRs: os.Getenv("TRUSTED_PROXY_CIDRS"),
AppName: getenv("APP_NAME", "OpenGit"),
LicenseName: getenv("LICENSE_NAME", "Apache-2.0"),
SourceURL: getenv("SOURCE_URL", ""),
LicensesFilePath: getenv("LICENSES_FILE_PATH", "./licenses.json"),
}
}

Expand Down
21 changes: 21 additions & 0 deletions backend/internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,27 @@ func TestMetricsConfig(t *testing.T) {
})
}

func TestLoadBrandingDefaults(t *testing.T) {
t.Setenv("APP_NAME", "")
t.Setenv("LICENSE_NAME", "")
t.Setenv("SOURCE_URL", "")
t.Setenv("LICENSES_FILE_PATH", "")

cfg := config.Load()
if cfg.AppName != "OpenGit" {
t.Fatalf("AppName = %q, want OpenGit", cfg.AppName)
}
if cfg.LicenseName != "Apache-2.0" {
t.Fatalf("LicenseName = %q, want Apache-2.0", cfg.LicenseName)
}
if cfg.SourceURL != "" {
t.Fatalf("SourceURL = %q, want empty", cfg.SourceURL)
}
if cfg.LicensesFilePath != "./licenses.json" {
t.Fatalf("LicensesFilePath = %q, want ./licenses.json", cfg.LicensesFilePath)
}
}

func TestMaskDSN(t *testing.T) {
masked := database.MaskDSN("postgres://user:secret@host/db")
if strings.Contains(masked, "secret") {
Expand Down
67 changes: 67 additions & 0 deletions backend/internal/handler/meta_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package handler

import (
"net/http"

"github.com/labstack/echo/v4"
)

type BuildInfo struct {
AppName string
Version string
GitCommit string
BuildDate string
LicenseName string
SourceURL string
}

type LicenseEntry struct {
Name string `json:"name"`
Version string `json:"version"`
License string `json:"license"`
URL string `json:"url"`
}

type MetaHandler struct {
info BuildInfo
thirdParty []LicenseEntry
}

func NewMetaHandler(info BuildInfo, thirdParty []LicenseEntry) *MetaHandler {
entries := make([]LicenseEntry, 0)
if thirdParty != nil {
entries = append(entries, thirdParty...)
}
return &MetaHandler{
info: info,
thirdParty: entries,
}
}

func (h *MetaHandler) GetMeta(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"app_name": h.info.AppName,
"version": h.info.Version,
"git_commit": h.info.GitCommit,
"build_date": h.info.BuildDate,
"license": h.info.LicenseName,
"source_url": h.info.SourceURL,
})
}

type licensesResponse struct {
AppLicense string `json:"app_license"`
ThirdParty []LicenseEntry `json:"third_party"`
}

func (h *MetaHandler) GetLicenses(c echo.Context) error {
return c.JSON(http.StatusOK, licensesResponse{
AppLicense: h.info.LicenseName,
ThirdParty: h.thirdParty,
})
}

func (h *MetaHandler) RegisterRoutes(e *echo.Echo) {
e.GET("/api/meta", h.GetMeta)
e.GET("/api/licenses", h.GetLicenses)
}
144 changes: 144 additions & 0 deletions backend/internal/handler/meta_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package handler_test

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/labstack/echo/v4"

"github.com/open-git/backend/internal/handler"
)

func TestGetMetaOK(t *testing.T) {
e := echo.New()
metaHandler := handler.NewMetaHandler(handler.BuildInfo{
AppName: "OpenGit",
Version: "1.0.0",
GitCommit: "abc1234",
BuildDate: "2025-01-01T00:00:00Z",
LicenseName: "Apache-2.0",
SourceURL: "https://example.org/org/repo",
}, nil)
metaHandler.RegisterRoutes(e)

req := httptest.NewRequest(http.MethodGet, "/api/meta", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}

var body map[string]string
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal: %v", err)
}

for _, key := range []string{"app_name", "version", "git_commit", "build_date", "license", "source_url"} {
if _, ok := body[key]; !ok {
t.Fatalf("%s key missing", key)
}
}
}

func TestGetLicensesWithEntries(t *testing.T) {
e := echo.New()
entry := handler.LicenseEntry{
Name: "github.com/labstack/echo/v4",
Version: "v4.11.0",
License: "MIT",
URL: "https://github.com/labstack/echo",
}
metaHandler := handler.NewMetaHandler(handler.BuildInfo{
LicenseName: "Apache-2.0",
}, []handler.LicenseEntry{entry})
metaHandler.RegisterRoutes(e)

req := httptest.NewRequest(http.MethodGet, "/api/licenses", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}

var body struct {
AppLicense string `json:"app_license"`
ThirdParty []handler.LicenseEntry `json:"third_party"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if body.AppLicense != "Apache-2.0" {
t.Fatalf("app_license = %q, want Apache-2.0", body.AppLicense)
}
if len(body.ThirdParty) != 1 {
t.Fatalf("third_party len = %d, want 1", len(body.ThirdParty))
}
if body.ThirdParty[0].Name != entry.Name {
t.Fatalf("third_party[0].name = %q, want %q", body.ThirdParty[0].Name, entry.Name)
}
}

func TestGetLicensesEmpty(t *testing.T) {
e := echo.New()
metaHandler := handler.NewMetaHandler(handler.BuildInfo{
LicenseName: "Apache-2.0",
}, nil)
metaHandler.RegisterRoutes(e)

req := httptest.NewRequest(http.MethodGet, "/api/licenses", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}

raw := rec.Body.String()
if raw == "" {
t.Fatal("response body is empty")
}
if !json.Valid([]byte(raw)) {
t.Fatalf("response is not valid JSON: %s", raw)
}

var body map[string]json.RawMessage
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal: %v", err)
}
thirdParty, ok := body["third_party"]
if !ok {
t.Fatal("third_party key missing")
}
if string(thirdParty) != "[]" {
t.Fatalf("third_party = %s, want []", string(thirdParty))
}
}

func TestGetMetaDevVersion(t *testing.T) {
e := echo.New()
metaHandler := handler.NewMetaHandler(handler.BuildInfo{
AppName: "OpenGit",
Version: "dev",
}, nil)
metaHandler.RegisterRoutes(e)

req := httptest.NewRequest(http.MethodGet, "/api/meta", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)

if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}

var body map[string]string
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if body["version"] != "dev" {
t.Fatalf("version = %q, want dev", body["version"])
}
}
1 change: 1 addition & 0 deletions backend/licenses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading