- Go API server with PostgreSQL + Redis - AI floor replacement (OpenRouter Gemini) - Product database (10 brands, 3539 products) - Recommendation engine, calculator, articles - Redis async queue + worker pool - Hot product caching, brand view tracking - JWT auth, favorites, projects - Docker deployment ready Co-Authored-By: Claude <noreply@anthropic.com>
99 lines
3.1 KiB
Go
99 lines
3.1 KiB
Go
package handler
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"floorvisualizer/internal/middleware"
|
|
"floorvisualizer/internal/openrouter"
|
|
"floorvisualizer/internal/service"
|
|
)
|
|
|
|
func NewProxyClient(proxyURLStr string) *http.Client {
|
|
u, _ := url.Parse(proxyURLStr)
|
|
if u == nil {
|
|
return http.DefaultClient
|
|
}
|
|
return &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(u)}}
|
|
}
|
|
|
|
func RegisterAll(mux *http.ServeMux, client *openrouter.Client, db *sql.DB, authSvc *service.AuthService) {
|
|
requireAuth := middleware.RequireAuth(authSvc)
|
|
|
|
// Auth
|
|
mux.HandleFunc("/auth/register", handleRegister(authSvc, db))
|
|
mux.HandleFunc("/auth/login", handleLogin(authSvc, db))
|
|
|
|
// User (protected)
|
|
mux.HandleFunc("/user/me", requireAuth(handleMe(db)))
|
|
mux.HandleFunc("/user/avatar", requireAuth(handleUploadAvatar(db)))
|
|
mux.HandleFunc("/user/favorites", requireAuth(handleFavorites(db)))
|
|
mux.HandleFunc("/user/favorites/", requireAuth(handleFavorites(db)))
|
|
mux.HandleFunc("/user/projects", requireAuth(handleProjects(db)))
|
|
mux.HandleFunc("/user/projects/", requireAuth(handleProjects(db)))
|
|
mux.HandleFunc("/user/frequent-brands", requireAuth(handleFrequentBrands(db)))
|
|
|
|
// Upload
|
|
mux.HandleFunc("/healthz", handleHealthz)
|
|
mux.HandleFunc("/upload", handleUpload)
|
|
|
|
// Products
|
|
mux.HandleFunc("/products", handleProducts(db))
|
|
mux.HandleFunc("/products/", handleProducts(db))
|
|
mux.HandleFunc("/product-options", handleProductOptions(db))
|
|
mux.HandleFunc("/filter-options", handleFilterOptions(db))
|
|
|
|
// Recommend
|
|
mux.HandleFunc("/recommend/api", handleRecommend(db))
|
|
|
|
// Calculator
|
|
mux.HandleFunc("/calculator/calc", handleCalc)
|
|
|
|
// Floor AI
|
|
mux.HandleFunc("/floor/generate", handleFloorGenerate(client, db))
|
|
mux.HandleFunc("/floor/status", handleFloorStatus)
|
|
mux.HandleFunc("/floor/options", handleFloorOptions(db))
|
|
|
|
// Articles (recommend before /articles/ to avoid path conflict)
|
|
mux.HandleFunc("/articles/recommend", handleArticleRecommend(db))
|
|
mux.HandleFunc("/articles", handleArticles(db))
|
|
mux.HandleFunc("/articles/", handleArticles(db))
|
|
|
|
// Static
|
|
mux.Handle("/outputs/", http.StripPrefix("/outputs/", http.FileServer(http.Dir("./outputs"))))
|
|
mux.Handle("/uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir("./uploads"))))
|
|
mux.Handle("/knowledge_images/", http.StripPrefix("/knowledge_images/", http.FileServer(http.Dir("./knowledge_images"))))
|
|
mux.Handle("/images/knowledge/", http.StripPrefix("/images/knowledge/", http.FileServer(http.Dir("./knowledge_images"))))
|
|
}
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, v any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if status >= 400 {
|
|
msg := "Unknown error"
|
|
switch x := v.(type) {
|
|
case string:
|
|
msg = x
|
|
case map[string]string:
|
|
if e, ok := x["error"]; ok {
|
|
msg = e
|
|
}
|
|
case map[string]any:
|
|
if e, ok := x["error"]; ok {
|
|
msg = fmt.Sprint(e)
|
|
}
|
|
}
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(map[string]any{"code": status, "error": msg})
|
|
return
|
|
}
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(map[string]any{"code": status, "data": v})
|
|
}
|
|
|
|
func writeErr(w http.ResponseWriter, status int, msg string) {
|
|
writeJSON(w, status, msg)
|
|
}
|