refactor: restructure internal/ into adapters/, infra/, and app layers

- internal/gemini/ → internal/adapters/openai/ (renamed package to openai)
- internal/pexels/ → internal/adapters/pexels/
- internal/config/   → internal/infra/config/
- internal/database/ → internal/infra/database/
- internal/locale/   → internal/infra/locale/
- internal/middleware/ → internal/infra/middleware/
- internal/server/   → internal/infra/server/

All import paths and call sites updated accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
dbastrikin
2026-03-15 21:10:37 +02:00
parent b427576629
commit 19a985ad49
44 changed files with 87 additions and 87 deletions

View File

@@ -8,9 +8,9 @@ import (
"strings"
"sync"
"github.com/food-ai/backend/internal/gemini"
"github.com/food-ai/backend/internal/adapters/openai"
"github.com/food-ai/backend/internal/infra/middleware"
"github.com/food-ai/backend/internal/ingredient"
"github.com/food-ai/backend/internal/middleware"
)
// IngredientRepository is the subset of ingredient.Repository used by this handler.
@@ -23,13 +23,13 @@ type IngredientRepository interface {
// Handler handles POST /ai/* recognition endpoints.
type Handler struct {
gemini *gemini.Client
openaiClient *openai.Client
ingredientRepo IngredientRepository
}
// NewHandler creates a new Handler.
func NewHandler(geminiClient *gemini.Client, repo IngredientRepository) *Handler {
return &Handler{gemini: geminiClient, ingredientRepo: repo}
func NewHandler(openaiClient *openai.Client, repo IngredientRepository) *Handler {
return &Handler{openaiClient: openaiClient, ingredientRepo: repo}
}
// ---------------------------------------------------------------------------
@@ -60,12 +60,12 @@ type EnrichedItem struct {
// ReceiptResponse is the response for POST /ai/recognize-receipt.
type ReceiptResponse struct {
Items []EnrichedItem `json:"items"`
Unrecognized []gemini.UnrecognizedItem `json:"unrecognized"`
Items []EnrichedItem `json:"items"`
Unrecognized []openai.UnrecognizedItem `json:"unrecognized"`
}
// DishResponse is the response for POST /ai/recognize-dish.
type DishResponse = gemini.DishResult
type DishResponse = openai.DishResult
// ---------------------------------------------------------------------------
// Handlers
@@ -83,7 +83,7 @@ func (h *Handler) RecognizeReceipt(w http.ResponseWriter, r *http.Request) {
return
}
result, err := h.gemini.RecognizeReceipt(r.Context(), req.ImageBase64, req.MimeType)
result, err := h.openaiClient.RecognizeReceipt(r.Context(), req.ImageBase64, req.MimeType)
if err != nil {
slog.Error("recognize receipt", "err", err)
writeErrorJSON(w, http.StatusServiceUnavailable, "recognition failed, please try again")
@@ -110,13 +110,13 @@ func (h *Handler) RecognizeProducts(w http.ResponseWriter, r *http.Request) {
}
// Process each image in parallel.
allItems := make([][]gemini.RecognizedItem, len(req.Images))
allItems := make([][]openai.RecognizedItem, len(req.Images))
var wg sync.WaitGroup
for i, img := range req.Images {
wg.Add(1)
go func(i int, img imageRequest) {
defer wg.Done()
items, err := h.gemini.RecognizeProducts(r.Context(), img.ImageBase64, img.MimeType)
items, err := h.openaiClient.RecognizeProducts(r.Context(), img.ImageBase64, img.MimeType)
if err != nil {
slog.Warn("recognize products from image", "index", i, "err", err)
return
@@ -140,7 +140,7 @@ func (h *Handler) RecognizeDish(w http.ResponseWriter, r *http.Request) {
return
}
result, err := h.gemini.RecognizeDish(r.Context(), req.ImageBase64, req.MimeType)
result, err := h.openaiClient.RecognizeDish(r.Context(), req.ImageBase64, req.MimeType)
if err != nil {
slog.Error("recognize dish", "err", err)
writeErrorJSON(w, http.StatusServiceUnavailable, "recognition failed, please try again")
@@ -156,7 +156,7 @@ func (h *Handler) RecognizeDish(w http.ResponseWriter, r *http.Request) {
// enrichItems matches each recognized item against ingredient_mappings.
// Items without a match trigger a Gemini classification call and upsert into the DB.
func (h *Handler) enrichItems(ctx context.Context, items []gemini.RecognizedItem) []EnrichedItem {
func (h *Handler) enrichItems(ctx context.Context, items []openai.RecognizedItem) []EnrichedItem {
result := make([]EnrichedItem, 0, len(items))
for _, item := range items {
enriched := EnrichedItem{
@@ -188,7 +188,7 @@ func (h *Handler) enrichItems(ctx context.Context, items []gemini.RecognizedItem
}
} else {
// No mapping — ask AI to classify and save for future reuse.
classification, err := h.gemini.ClassifyIngredient(ctx, item.Name)
classification, err := h.openaiClient.ClassifyIngredient(ctx, item.Name)
if err != nil {
slog.Warn("classify unknown ingredient", "name", item.Name, "err", err)
} else {
@@ -208,7 +208,7 @@ func (h *Handler) enrichItems(ctx context.Context, items []gemini.RecognizedItem
}
// saveClassification upserts an AI-produced ingredient classification into the DB.
func (h *Handler) saveClassification(ctx context.Context, c *gemini.IngredientClassification) *ingredient.IngredientMapping {
func (h *Handler) saveClassification(ctx context.Context, c *openai.IngredientClassification) *ingredient.IngredientMapping {
if c == nil || c.CanonicalName == "" {
return nil
}
@@ -252,8 +252,8 @@ func (h *Handler) saveClassification(ctx context.Context, c *gemini.IngredientCl
// mergeAndDeduplicate combines results from multiple images.
// Items sharing the same name (case-insensitive) have their quantities summed.
func mergeAndDeduplicate(batches [][]gemini.RecognizedItem) []gemini.RecognizedItem {
seen := make(map[string]*gemini.RecognizedItem)
func mergeAndDeduplicate(batches [][]openai.RecognizedItem) []openai.RecognizedItem {
seen := make(map[string]*openai.RecognizedItem)
var order []string
for _, batch := range batches {
@@ -273,7 +273,7 @@ func mergeAndDeduplicate(batches [][]gemini.RecognizedItem) []gemini.RecognizedI
}
}
result := make([]gemini.RecognizedItem, 0, len(order))
result := make([]openai.RecognizedItem, 0, len(order))
for _, key := range order {
result = append(result, *seen[key])
}