代码分层 #1
@ -256,7 +256,7 @@ func FloorStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func FloorOptions(db *sql.DB) http.HandlerFunc {
|
func FloorOptions(db *sql.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
woodFloors, _ := loadFloorOptions(db, "Hardwood")
|
woodFloors, _ := loadFloorOptions(db, "Hardwood") // non-critical: returns empty on error
|
||||||
woodFloors2, _ := loadFloorOptions(db, "Engineered Wood")
|
woodFloors2, _ := loadFloorOptions(db, "Engineered Wood")
|
||||||
tileFloors, _ := loadFloorOptions(db, "Wood-Look Tile")
|
tileFloors, _ := loadFloorOptions(db, "Wood-Look Tile")
|
||||||
vinylFloors, _ := loadFloorOptions(db, "SPC/LVP")
|
vinylFloors, _ := loadFloorOptions(db, "SPC/LVP")
|
||||||
@ -316,34 +316,6 @@ Style: photorealistic, soft natural matte finish, minimal reflections, no mirror
|
|||||||
|
|
||||||
// ── Content Check ─────────────────────────────────────────
|
// ── Content Check ─────────────────────────────────────────
|
||||||
|
|
||||||
func CheckContent(client *openrouter.Client) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodPost {
|
|
||||||
writeErr(w, 405, "Method not allowed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := r.ParseMultipartForm(20 << 20); err != nil {
|
|
||||||
writeJSON(w, 400, map[string]string{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, _, _ := r.FormFile("image")
|
|
||||||
defer file.Close()
|
|
||||||
os.MkdirAll("uploads", 0755)
|
|
||||||
tmpPath := filepath.Join("uploads", fmt.Sprintf("check_%d.png", time.Now().UnixNano()))
|
|
||||||
dst, _ := os.Create(tmpPath)
|
|
||||||
io.Copy(dst, file)
|
|
||||||
dst.Close()
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
passed, score := validateContent(client, ctx, tmpPath)
|
|
||||||
msg := fmt.Sprintf("✅ Indoor photo score: %d/10", score)
|
|
||||||
if !passed {
|
|
||||||
msg = fmt.Sprintf("Non-indoor photo score: %d/10", score)
|
|
||||||
}
|
|
||||||
writeJSON(w, 200, map[string]any{"passed": passed, "score": score, "message": msg})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateContent(client *openrouter.Client, ctx context.Context, imagePath string) (bool, int) {
|
func validateContent(client *openrouter.Client, ctx context.Context, imagePath string) (bool, int) {
|
||||||
imgData, err := os.ReadFile(imagePath)
|
imgData, err := os.ReadFile(imagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -79,12 +79,13 @@ func Products(db *sql.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Attach variants (same style_name, different sizes)
|
// Attach variants (same style_name, different sizes)
|
||||||
if variants, _ := repository.GetVariantsByStyle(db, sku); len(variants) > 0 {
|
variants, _ := repository.GetVariantsByStyle(db, sku)
|
||||||
|
if len(variants) > 0 {
|
||||||
product.Variants = variants
|
product.Variants = variants
|
||||||
}
|
}
|
||||||
// Check favorite status and build specs
|
// Check favorite status and build specs
|
||||||
userID := middleware.UserID(r.Context())
|
userID := middleware.UserID(r.Context())
|
||||||
favMap, _ := repository.GetFavoritedSKUs(db, userID, []string{sku})
|
favMap, _ := repository.GetFavoritedSKUs(db, userID, []string{sku}) // ok to ignore err: non-critical
|
||||||
product.IsFavorited = favMap[sku]
|
product.IsFavorited = favMap[sku]
|
||||||
// Build specs: main product + variants as size options
|
// Build specs: main product + variants as size options
|
||||||
allVariants := append([]model.Product{*product}, product.Variants...)
|
allVariants := append([]model.Product{*product}, product.Variants...)
|
||||||
@ -139,7 +140,7 @@ func Products(db *sql.DB) http.HandlerFunc {
|
|||||||
skus[i] = p.SKU
|
skus[i] = p.SKU
|
||||||
}
|
}
|
||||||
userID := middleware.UserID(r.Context())
|
userID := middleware.UserID(r.Context())
|
||||||
favSKUs, _ := repository.GetFavoritedSKUs(db, userID, skus)
|
favSKUs, _ := repository.GetFavoritedSKUs(db, userID, skus) // ok to ignore err: non-critical
|
||||||
specCounts := repository.GetSpecCounts(db, skus)
|
specCounts := repository.GetSpecCounts(db, skus)
|
||||||
|
|
||||||
items := make([]ProductItem, 0, len(prods))
|
items := make([]ProductItem, 0, len(prods))
|
||||||
|
|||||||
@ -2,8 +2,8 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"floorvisualizer/internal/logger"
|
"floorvisualizer/internal/logger"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"floorvisualizer/internal/model"
|
"floorvisualizer/internal/model"
|
||||||
|
|||||||
@ -3,8 +3,8 @@ package queue
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"floorvisualizer/internal/logger"
|
"floorvisualizer/internal/logger"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@ -77,7 +77,11 @@ func processJob(ctx context.Context, rdb *redis.Client, client *openrouter.Clien
|
|||||||
// Step 0: content check
|
// Step 0: content check
|
||||||
SetJobProgress(jobCtx, rdb, jobID, "check", "Checking content...")
|
SetJobProgress(jobCtx, rdb, jobID, "check", "Checking content...")
|
||||||
logger.Info("[%s] Content check", jobID)
|
logger.Info("[%s] Content check", jobID)
|
||||||
if passed, _ := validateContent(client, jobCtx, inputPath); !passed {
|
passed, score := validateContent(client, jobCtx, inputPath)
|
||||||
|
if score == 0 {
|
||||||
|
logger.Warn("[%s] Content check failed (score=0)", jobID)
|
||||||
|
}
|
||||||
|
if !passed {
|
||||||
SetJobError(jobCtx, rdb, jobID, "Content check failed: Please upload an indoor room photo")
|
SetJobError(jobCtx, rdb, jobID, "Content check failed: Please upload an indoor room photo")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -102,7 +106,7 @@ func processJob(ctx context.Context, rdb *redis.Client, client *openrouter.Clien
|
|||||||
logger.Info("[%s] Inpainting: %s + %s", jobID, floor.Name, pattern.Name)
|
logger.Info("[%s] Inpainting: %s + %s", jobID, floor.Name, pattern.Name)
|
||||||
|
|
||||||
floorPrompt := handler.BuildFloorPromptPublic(*floor, *pattern, room)
|
floorPrompt := handler.BuildFloorPromptPublic(*floor, *pattern, room)
|
||||||
result, err := client.GenerateImageToFile(jobCtx, openrouter.ImageGenerationRequest{
|
_, err = client.GenerateImageToFile(jobCtx, openrouter.ImageGenerationRequest{
|
||||||
Prompt: floorPrompt, OutputPath: outputPath, InputImagePath: inputPath,
|
Prompt: floorPrompt, OutputPath: outputPath, InputImagePath: inputPath,
|
||||||
MaskImagePath: maskPath, ImageSize: "2K",
|
MaskImagePath: maskPath, ImageSize: "2K",
|
||||||
})
|
})
|
||||||
@ -112,10 +116,13 @@ func processJob(ctx context.Context, rdb *redis.Client, client *openrouter.Clien
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up intermediate files
|
// Clean up intermediate files
|
||||||
os.Remove(inputPath)
|
if err := os.Remove(inputPath); err != nil {
|
||||||
os.Remove(maskPath)
|
logger.Warn("[%s] Failed to remove input: %v", jobID, err)
|
||||||
|
}
|
||||||
|
if err := os.Remove(maskPath); err != nil {
|
||||||
|
logger.Warn("[%s] Failed to remove mask: %v", jobID, err)
|
||||||
|
}
|
||||||
|
|
||||||
_ = result
|
|
||||||
SetJobResult(jobCtx, rdb, jobID, "/"+filepath.ToSlash(outputPath))
|
SetJobResult(jobCtx, rdb, jobID, "/"+filepath.ToSlash(outputPath))
|
||||||
logger.Info("[%s] Done", jobID)
|
logger.Info("[%s] Done", jobID)
|
||||||
}
|
}
|
||||||
@ -180,4 +187,3 @@ func base64enc(data []byte) string {
|
|||||||
}
|
}
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user