feat(report): improve daily intelligence UX and price tracking
This commit is contained in:
@@ -6,10 +6,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
@@ -55,7 +58,13 @@ type subscriptionPlanResponse struct {
|
||||
}
|
||||
|
||||
type apiEnvelope struct {
|
||||
Data any `json:"data"`
|
||||
Data any `json:"data,omitempty"`
|
||||
Error *apiError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type apiError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type modelFetcher func(context.Context, *sql.DB) ([]modelResponse, error)
|
||||
@@ -74,6 +83,173 @@ type latestReportResponse struct {
|
||||
MarkdownURL string `json:"markdownUrl"`
|
||||
HTMLURL string `json:"htmlUrl"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
AppendixJSONURL string `json:"appendixJsonUrl"`
|
||||
|
||||
}
|
||||
|
||||
type serverConfig struct {
|
||||
BasicAuthUser string
|
||||
BasicAuthPass string
|
||||
ServiceToken string
|
||||
RateLimitPerWindow int
|
||||
RateLimitWindow time.Duration
|
||||
now func() time.Time
|
||||
limiter *ipRateLimiter
|
||||
}
|
||||
|
||||
type ipRateLimiter struct {
|
||||
mu sync.Mutex
|
||||
limit int
|
||||
window time.Duration
|
||||
entries map[string]rateLimitEntry
|
||||
}
|
||||
|
||||
type rateLimitEntry struct {
|
||||
windowStart time.Time
|
||||
count int
|
||||
}
|
||||
|
||||
func newIPRateLimiter(limit int, window time.Duration) *ipRateLimiter {
|
||||
if limit <= 0 || window <= 0 {
|
||||
return nil
|
||||
}
|
||||
return &ipRateLimiter{
|
||||
limit: limit,
|
||||
window: window,
|
||||
entries: make(map[string]rateLimitEntry),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ipRateLimiter) Allow(key string, now time.Time) bool {
|
||||
if l == nil {
|
||||
return true
|
||||
}
|
||||
if key == "" {
|
||||
key = "unknown"
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
entry := l.entries[key]
|
||||
if entry.windowStart.IsZero() || now.Sub(entry.windowStart) >= l.window {
|
||||
entry = rateLimitEntry{windowStart: now}
|
||||
}
|
||||
if entry.count >= l.limit {
|
||||
return false
|
||||
}
|
||||
entry.count++
|
||||
l.entries[key] = entry
|
||||
|
||||
for candidate, candidateEntry := range l.entries {
|
||||
if now.Sub(candidateEntry.windowStart) >= l.window {
|
||||
delete(l.entries, candidate)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func loadServerConfigFromEnv() serverConfig {
|
||||
limit := 60
|
||||
if raw := strings.TrimSpace(os.Getenv("API_RATE_LIMIT_PER_WINDOW")); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil && parsed >= 0 {
|
||||
limit = parsed
|
||||
}
|
||||
}
|
||||
|
||||
window := time.Minute
|
||||
if raw := strings.TrimSpace(os.Getenv("API_RATE_LIMIT_WINDOW_SEC")); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 {
|
||||
window = time.Duration(parsed) * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
return serverConfig{
|
||||
BasicAuthUser: os.Getenv("API_BASIC_AUTH_USER"),
|
||||
BasicAuthPass: os.Getenv("API_BASIC_AUTH_PASS"),
|
||||
ServiceToken: os.Getenv("API_AUTH_TOKEN"),
|
||||
RateLimitPerWindow: limit,
|
||||
RateLimitWindow: window,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg serverConfig) withRuntimeDefaults() serverConfig {
|
||||
if cfg.now == nil {
|
||||
cfg.now = time.Now
|
||||
}
|
||||
if cfg.limiter == nil {
|
||||
cfg.limiter = newIPRateLimiter(cfg.RateLimitPerWindow, cfg.RateLimitWindow)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg serverConfig) wrap(path string, next http.HandlerFunc) http.HandlerFunc {
|
||||
cfg = cfg.withRuntimeDefaults()
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
clientIP := requestClientIP(r)
|
||||
trustedClient := isTrustedClientIP(clientIP)
|
||||
|
||||
if path == "/health" && !trustedClient {
|
||||
writeError(w, http.StatusForbidden, "health_endpoint_internal_only", "health endpoint is restricted to trusted networks")
|
||||
return
|
||||
}
|
||||
|
||||
if path != "/health" && !trustedClient {
|
||||
if !cfg.isAuthorized(r) {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="llm-intelligence"`)
|
||||
writeError(w, http.StatusUnauthorized, "auth_required", "authentication required for external API access")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if path != "/health" && cfg.limiter != nil {
|
||||
if !cfg.limiter.Allow(clientIP, cfg.now()) {
|
||||
writeError(w, http.StatusTooManyRequests, "rate_limited", "rate limit exceeded")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg serverConfig) isAuthorized(r *http.Request) bool {
|
||||
authHeader := strings.TrimSpace(r.Header.Get("Authorization"))
|
||||
if cfg.ServiceToken != "" {
|
||||
const bearerPrefix = "Bearer "
|
||||
if strings.HasPrefix(authHeader, bearerPrefix) {
|
||||
return strings.TrimSpace(strings.TrimPrefix(authHeader, bearerPrefix)) == cfg.ServiceToken
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.BasicAuthUser == "" && cfg.BasicAuthPass == "" {
|
||||
return false
|
||||
}
|
||||
username, password, ok := r.BasicAuth()
|
||||
return ok && username == cfg.BasicAuthUser && password == cfg.BasicAuthPass
|
||||
}
|
||||
|
||||
func requestClientIP(r *http.Request) string {
|
||||
if forwardedFor := strings.TrimSpace(r.Header.Get("X-Forwarded-For")); forwardedFor != "" {
|
||||
parts := strings.Split(forwardedFor, ",")
|
||||
if len(parts) > 0 {
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr))
|
||||
if err == nil {
|
||||
return host
|
||||
}
|
||||
return strings.TrimSpace(r.RemoteAddr)
|
||||
}
|
||||
|
||||
func isTrustedClientIP(raw string) bool {
|
||||
ip := net.ParseIP(strings.TrimSpace(raw))
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
return ip.IsLoopback() || ip.IsPrivate()
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -96,7 +272,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
mux := newMux(db, fetchModels, fetchSubscriptionPlans, fetchLatestReport)
|
||||
mux := newMuxWithConfig(db, fetchModels, fetchSubscriptionPlans, fetchLatestReport, loadServerConfigFromEnv())
|
||||
|
||||
log.Printf("server listening on :%s", addr)
|
||||
if err := http.ListenAndServe(":"+addr, mux); err != nil {
|
||||
@@ -106,72 +282,83 @@ func main() {
|
||||
|
||||
func newMux(db *sql.DB, fetchModelsFn modelFetcher, fetchPlansFn subscriptionPlanFetcher, fetchLatestReportFn latestReportFetcher) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
registerRoutes(mux, db, fetchModelsFn, fetchPlansFn, fetchLatestReportFn, func(_ string, handler http.HandlerFunc) http.HandlerFunc {
|
||||
return handler
|
||||
})
|
||||
return mux
|
||||
}
|
||||
|
||||
func newMuxWithConfig(db *sql.DB, fetchModelsFn modelFetcher, fetchPlansFn subscriptionPlanFetcher, fetchLatestReportFn latestReportFetcher, cfg serverConfig) *http.ServeMux {
|
||||
mux := http.NewServeMux()
|
||||
registerRoutes(mux, db, fetchModelsFn, fetchPlansFn, fetchLatestReportFn, cfg.wrap)
|
||||
return mux
|
||||
}
|
||||
|
||||
func registerRoutes(mux *http.ServeMux, db *sql.DB, fetchModelsFn modelFetcher, fetchPlansFn subscriptionPlanFetcher, fetchLatestReportFn latestReportFetcher, wrap func(string, http.HandlerFunc) http.HandlerFunc) {
|
||||
mux.HandleFunc("/health", wrap("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
if db == nil {
|
||||
http.Error(w, "database not configured", http.StatusServiceUnavailable)
|
||||
writeError(w, http.StatusServiceUnavailable, "database_not_configured", "database not configured")
|
||||
return
|
||||
}
|
||||
if err := db.PingContext(r.Context()); err != nil {
|
||||
http.Error(w, "database unavailable", http.StatusServiceUnavailable)
|
||||
writeError(w, http.StatusServiceUnavailable, "database_unavailable", "database unavailable")
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/models", func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
mux.HandleFunc("/api/v1/models", wrap("/api/v1/models", func(w http.ResponseWriter, r *http.Request) {
|
||||
if db == nil {
|
||||
http.Error(w, "database not configured", http.StatusServiceUnavailable)
|
||||
writeError(w, http.StatusServiceUnavailable, "database_not_configured", "database not configured")
|
||||
return
|
||||
}
|
||||
models, err := fetchModelsFn(r.Context(), db)
|
||||
if err != nil {
|
||||
http.Error(w, "query failed", http.StatusInternalServerError)
|
||||
writeError(w, http.StatusInternalServerError, "query_failed", "query failed")
|
||||
log.Printf("fetch models failed: %v", err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, apiEnvelope{Data: models})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/subscription-plans", func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
mux.HandleFunc("/api/v1/subscription-plans", wrap("/api/v1/subscription-plans", func(w http.ResponseWriter, r *http.Request) {
|
||||
if db == nil {
|
||||
http.Error(w, "database not configured", http.StatusServiceUnavailable)
|
||||
writeError(w, http.StatusServiceUnavailable, "database_not_configured", "database not configured")
|
||||
return
|
||||
}
|
||||
plans, err := fetchPlansFn(r.Context(), db)
|
||||
if err != nil {
|
||||
http.Error(w, "query failed", http.StatusInternalServerError)
|
||||
writeError(w, http.StatusInternalServerError, "query_failed", "query failed")
|
||||
log.Printf("fetch subscription plans failed: %v", err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, apiEnvelope{Data: plans})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/reports/latest/html", func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
mux.HandleFunc("/api/v1/reports/latest/html", wrap("/api/v1/reports/latest/html", func(w http.ResponseWriter, r *http.Request) {
|
||||
serveLatestReportArtifact(w, r, db, fetchLatestReportFn, "html")
|
||||
})
|
||||
mux.HandleFunc("/api/v1/reports/latest/markdown", func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
mux.HandleFunc("/api/v1/reports/latest/markdown", wrap("/api/v1/reports/latest/markdown", func(w http.ResponseWriter, r *http.Request) {
|
||||
serveLatestReportArtifact(w, r, db, fetchLatestReportFn, "markdown")
|
||||
})
|
||||
mux.HandleFunc("/api/v1/reports/latest", func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
mux.HandleFunc("/api/v1/reports/latest", wrap("/api/v1/reports/latest", func(w http.ResponseWriter, r *http.Request) {
|
||||
if db == nil {
|
||||
http.Error(w, "database not configured", http.StatusServiceUnavailable)
|
||||
writeError(w, http.StatusServiceUnavailable, "database_not_configured", "database not configured")
|
||||
return
|
||||
}
|
||||
report, err := fetchLatestReportFn(r.Context(), db)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
http.Error(w, "latest report not found", http.StatusNotFound)
|
||||
writeError(w, http.StatusNotFound, "latest_report_not_found", "latest report not found")
|
||||
return
|
||||
}
|
||||
http.Error(w, "query failed", http.StatusInternalServerError)
|
||||
writeError(w, http.StatusInternalServerError, "query_failed", "query failed")
|
||||
log.Printf("fetch latest report failed: %v", err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, apiEnvelope{Data: report})
|
||||
})
|
||||
return mux
|
||||
}))
|
||||
}
|
||||
|
||||
func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
WITH latest_prices AS (
|
||||
const fetchModelsQuery = `
|
||||
WITH ranked_prices AS (
|
||||
SELECT
|
||||
rp.model_id,
|
||||
rp.pricing_mode,
|
||||
@@ -183,7 +370,16 @@ func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
|
||||
rp.is_free,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY rp.model_id
|
||||
ORDER BY rp.effective_date DESC NULLS LAST, rp.id DESC
|
||||
ORDER BY
|
||||
CASE WHEN lower(rp.region) = 'global' THEN 0 ELSE 1 END,
|
||||
CASE rp.source_type
|
||||
WHEN 'official' THEN 0
|
||||
WHEN 'reseller' THEN 1
|
||||
WHEN 'free_tier' THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
rp.effective_date DESC NULLS LAST,
|
||||
rp.id DESC
|
||||
) AS rn
|
||||
FROM region_pricing rp
|
||||
)
|
||||
@@ -204,10 +400,13 @@ func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
|
||||
COALESCE(m.data_confidence, 'official')
|
||||
FROM models m
|
||||
LEFT JOIN model_provider mp ON mp.id = m.provider_id
|
||||
LEFT JOIN latest_prices lp ON lp.model_id = m.id AND lp.rn = 1
|
||||
LEFT JOIN ranked_prices lp ON lp.model_id = m.id AND lp.rn = 1
|
||||
WHERE m.deleted_at IS NULL
|
||||
ORDER BY m.id DESC
|
||||
`)
|
||||
`
|
||||
|
||||
func fetchModels(ctx context.Context, db *sql.DB) ([]modelResponse, error) {
|
||||
rows, err := db.QueryContext(ctx, fetchModelsQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -291,22 +490,23 @@ func fetchLatestReport(ctx context.Context, db *sql.DB) (*latestReportResponse,
|
||||
report.ArchiveHTMLPath = deriveReportArchivePath(report.HTMLPath, report.ReportDate)
|
||||
report.MarkdownURL = "/api/v1/reports/latest/markdown"
|
||||
report.HTMLURL = "/api/v1/reports/latest/html"
|
||||
report.AppendixJSONURL = "/reports/daily/appendix/" + report.ReportDate + "/full_appendix.json"
|
||||
return &report, nil
|
||||
}
|
||||
|
||||
func serveLatestReportArtifact(w http.ResponseWriter, r *http.Request, db *sql.DB, fetchLatestReportFn latestReportFetcher, artifactType string) {
|
||||
if db == nil {
|
||||
http.Error(w, "database not configured", http.StatusServiceUnavailable)
|
||||
writeError(w, http.StatusServiceUnavailable, "database_not_configured", "database not configured")
|
||||
return
|
||||
}
|
||||
|
||||
report, err := fetchLatestReportFn(r.Context(), db)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
http.Error(w, "latest report not found", http.StatusNotFound)
|
||||
writeError(w, http.StatusNotFound, "latest_report_not_found", "latest report not found")
|
||||
return
|
||||
}
|
||||
http.Error(w, "query failed", http.StatusInternalServerError)
|
||||
writeError(w, http.StatusInternalServerError, "query_failed", "query failed")
|
||||
log.Printf("fetch latest report failed: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -320,7 +520,7 @@ func serveLatestReportArtifact(w http.ResponseWriter, r *http.Request, db *sql.D
|
||||
}
|
||||
|
||||
if _, err := os.Stat(targetPath); err != nil {
|
||||
http.Error(w, "report artifact not found", http.StatusNotFound)
|
||||
writeError(w, http.StatusNotFound, "report_artifact_not_found", "report artifact not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -417,6 +617,10 @@ func writeJSON(w http.ResponseWriter, status int, value any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
if err := json.NewEncoder(w).Encode(value); err != nil {
|
||||
http.Error(w, "encode failed", http.StatusInternalServerError)
|
||||
log.Printf("encode response failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, status int, code, message string) {
|
||||
writeJSON(w, status, apiEnvelope{Error: &apiError{Code: code, Message: message}})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user