Files
juanatsap 8205a22972 feat: Ollama adapter + chat rate limiter (30 req/hour)
Ollama adapter (internal/chat/ollama.go):
- Implements model.LLM interface for ADK Go
- Talks to Ollama's OpenAI-compatible API (/v1/chat/completions)
- Full tool/function calling support (tested with Mistral Small 3.2)
- Converts ADK types to OpenAI format (messages, tools, tool_calls)
- Configurable via OLLAMA_HOST and OLLAMA_MODEL env vars

Multi-provider handler:
- MODEL_PROVIDER env: "gemini" (default) or "ollama"
- Gemini: requires GOOGLE_API_KEY (pay-as-you-go recommended)
- Ollama: connects to local or Tailscale-remote instance

Rate limiter:
- 30 requests/hour per IP on /api/chat endpoint
- Uses existing middleware.NewRateLimiter pattern

Tested: Ollama + Mistral Small 3.2 on M4 Pro 64GB — correct answers
2026-04-08 14:47:14 +01:00

297 lines
8.7 KiB
Go

// Package constants provides global constants used across the application.
package constants
import (
"fmt"
"time"
)
// ==============================================================================
// HTTP CONTENT TYPES
// ==============================================================================
const (
ContentTypeJSON = "application/json"
ContentTypeHTML = "text/html; charset=utf-8"
ContentTypeHTMLFragment = "text/html" // For HTMX fragments
ContentTypePlainText = "text/plain; charset=utf-8"
ContentTypePlainSimple = "text/plain" // For Accept header matching
ContentTypePDF = "application/pdf"
ContentTypeFormURLEnc = "application/x-www-form-urlencoded"
)
// ==============================================================================
// HTTP HEADERS
// ==============================================================================
const (
HeaderContentType = "Content-Type"
HeaderContentDisposition = "Content-Disposition"
HeaderContentLength = "Content-Length"
HeaderCacheControl = "Cache-Control"
HeaderXContentTypeOpts = "X-Content-Type-Options"
// HTMX headers
HeaderHXRequest = "HX-Request"
HeaderHXTrigger = "HX-Trigger"
)
// ==============================================================================
// CACHE CONTROL VALUES
// ==============================================================================
const (
// CachePublic1Hour is for relatively static content (1 hour)
CachePublic1Hour = "public, max-age=3600"
// CachePublic1Day is for static files in production (1 day)
CachePublic1Day = "public, max-age=86400"
// CachePublic5Min is for dynamic content that can be cached briefly
CachePublic5Min = "public, max-age=300, must-revalidate"
// CacheNoStore prevents caching entirely
CacheNoStore = "no-cache, no-store, must-revalidate"
// CacheStatic is for truly static assets (1 year)
CacheStatic = "public, max-age=31536000, immutable"
)
// Cache durations in seconds
const (
CacheDuration1Hour = 3600
CacheDuration5Min = 300
CacheDuration1Year = 31536000
CacheDuration1Day = 86400
CacheDuration1Week = 604800
CacheDuration1Month = 2592000
)
// ==============================================================================
// LANGUAGE CODES
// ==============================================================================
const (
LangEnglish = "en"
LangSpanish = "es"
LangDefault = LangEnglish
)
// SupportedLanguages is the set of valid language codes
var SupportedLanguages = map[string]bool{
LangEnglish: true,
LangSpanish: true,
}
// AllLangs returns all supported language codes
func AllLangs() []string {
return []string{LangEnglish, LangSpanish}
}
// IsValidLang checks if a language code is supported
func IsValidLang(lang string) bool {
return SupportedLanguages[lang]
}
// ValidateLang returns an error if the language code is unsupported.
// It provides helpful error messages showing all supported languages.
func ValidateLang(lang string) error {
if !IsValidLang(lang) {
return fmt.Errorf("unsupported language: %s (supported: %v)", lang, AllLangs())
}
return nil
}
// ==============================================================================
// CV PREFERENCES
// ==============================================================================
const (
CVLengthShort = "short"
CVLengthLong = "long"
CVIconsShow = "show"
CVIconsHide = "hide"
CVThemeDefault = "default"
CVThemeClean = "clean"
)
// ==============================================================================
// COOKIE SETTINGS
// ==============================================================================
const (
CookieMaxAge = 365 * 24 * 60 * 60 // 1 year in seconds
CookiePath = "/"
)
// ==============================================================================
// RATE LIMITING
// ==============================================================================
const (
RateLimitPDFRequests = 3
RateLimitPDFWindow = 1 * time.Minute
RateLimitGeneralRequests = 100
RateLimitGeneralWindow = 1 * time.Minute
RateLimitContactRequests = 5
RateLimitContactWindow = 1 * time.Hour
RateLimitChatRequests = 30
RateLimitChatWindow = 1 * time.Hour
)
// ==============================================================================
// TIMEOUTS
// ==============================================================================
const (
TimeoutPDFGeneration = 30 * time.Second
TimeoutHTTPRequest = 10 * time.Second
TimeoutIdleConnection = 120 * time.Second
TimeoutGracefulShutdown = 30 * time.Second
FormMinSubmitTime = 2 * time.Second // Min time form must be displayed (bot protection)
)
// ==============================================================================
// DIRECTORIES
// ==============================================================================
const (
DirData = "data"
DirTemplates = "templates"
DirPartials = "templates/partials"
DirStatic = "static"
)
// ==============================================================================
// PDF DIMENSIONS
// ==============================================================================
const (
A4WidthInches = 8.27
A4HeightInches = 11.69
)
// ==============================================================================
// CSRF PROTECTION
// ==============================================================================
const (
CSRFTokenLength = 32
CSRFTokenTTL = 24 * time.Hour
CSRFCookieName = "csrf_token"
CSRFFormField = "csrf_token"
CSRFCleanupPeriod = 1 * time.Hour
)
// ==============================================================================
// CLEANUP INTERVALS
// ==============================================================================
const (
RateLimitCleanupPeriod = 10 * time.Minute // For contact rate limiter
RateLimitGeneralCleanupPeriod = 1 * time.Minute // For general rate limiter
)
// ==============================================================================
// SECURITY
// ==============================================================================
const (
// HSTS max-age (1 year)
HSTSMaxAge = "max-age=31536000; includeSubDomains; preload"
// Content type options
NoSniff = "nosniff"
// Frame options
FrameOptionsSameOrigin = "SAMEORIGIN"
// XSS Protection
XSSProtection = "1; mode=block"
// Referrer Policy
ReferrerPolicy = "strict-origin-when-cross-origin"
)
// ==============================================================================
// SECURITY HEADERS
// ==============================================================================
const (
HeaderXFrameOptions = "X-Frame-Options"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderReferrerPolicy = "Referrer-Policy"
HeaderPermissionsPolicy = "Permissions-Policy"
HeaderCSP = "Content-Security-Policy"
HeaderHSTS = "Strict-Transport-Security"
HeaderRetryAfter = "Retry-After"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXRealIP = "X-Real-IP"
HeaderXCSRFToken = "X-CSRF-Token"
)
// ==============================================================================
// REQUEST HEADERS
// ==============================================================================
const (
HeaderUserAgent = "User-Agent"
HeaderAccept = "Accept"
HeaderOrigin = "Origin"
HeaderReferer = "Referer"
HeaderXRequestedWith = "X-Requested-With"
HeaderXBrowserReq = "X-Browser-Request"
)
// Header values
const (
HeaderValueXMLHTTPRequest = "XMLHttpRequest"
)
// ==============================================================================
// ENVIRONMENT
// ==============================================================================
const (
EnvProduction = "production"
EnvDevelopment = "development"
EnvVarGOEnv = "GO_ENV"
EnvVarPort = "PORT"
DefaultPort = "1999"
)
// ==============================================================================
// COOKIE NAMES
// ==============================================================================
const (
CookieCVLength = "cv-length"
CookieCVIcons = "cv-icons"
CookieCVLanguage = "cv-language"
CookieCVTheme = "cv-theme"
CookieColorTheme = "color-theme"
)
// ==============================================================================
// COLOR THEMES
// ==============================================================================
const (
ColorThemeLight = "light"
ColorThemeDark = "dark"
)
// ==============================================================================
// ROUTES
// ==============================================================================
const (
RouteHome = "/"
RouteHealth = "/health"
RouteExportPDF = "/export/pdf"
RouteAPIContact = "/api/contact"
RouteAPICmdK = "/api/cmd-k"
)