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

@@ -10,9 +10,9 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/food-ai/backend/internal/config" "github.com/food-ai/backend/internal/infra/config"
"github.com/food-ai/backend/internal/database" "github.com/food-ai/backend/internal/infra/database"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
) )
func main() { func main() {

View File

@@ -5,22 +5,22 @@ import (
"time" "time"
"github.com/food-ai/backend/internal/auth" "github.com/food-ai/backend/internal/auth"
"github.com/food-ai/backend/internal/config" "github.com/food-ai/backend/internal/infra/config"
"github.com/food-ai/backend/internal/diary" "github.com/food-ai/backend/internal/diary"
"github.com/food-ai/backend/internal/dish" "github.com/food-ai/backend/internal/dish"
"github.com/food-ai/backend/internal/gemini" "github.com/food-ai/backend/internal/adapters/openai"
"github.com/food-ai/backend/internal/home" "github.com/food-ai/backend/internal/home"
"github.com/food-ai/backend/internal/ingredient" "github.com/food-ai/backend/internal/ingredient"
"github.com/food-ai/backend/internal/menu" "github.com/food-ai/backend/internal/menu"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/food-ai/backend/internal/pexels" "github.com/food-ai/backend/internal/adapters/pexels"
"github.com/food-ai/backend/internal/product" "github.com/food-ai/backend/internal/product"
"github.com/food-ai/backend/internal/recipe" "github.com/food-ai/backend/internal/recipe"
"github.com/food-ai/backend/internal/recognition" "github.com/food-ai/backend/internal/recognition"
"github.com/food-ai/backend/internal/recommendation" "github.com/food-ai/backend/internal/recommendation"
"github.com/food-ai/backend/internal/savedrecipe" "github.com/food-ai/backend/internal/savedrecipe"
"github.com/food-ai/backend/internal/cuisine" "github.com/food-ai/backend/internal/cuisine"
"github.com/food-ai/backend/internal/server" "github.com/food-ai/backend/internal/infra/server"
"github.com/food-ai/backend/internal/tag" "github.com/food-ai/backend/internal/tag"
"github.com/food-ai/backend/internal/units" "github.com/food-ai/backend/internal/units"
"github.com/food-ai/backend/internal/user" "github.com/food-ai/backend/internal/user"
@@ -38,7 +38,7 @@ type tagListHandler http.HandlerFunc
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
type geminiAPIKey string type openaiAPIKey string
type pexelsAPIKey string type pexelsAPIKey string
type jwtSecret string type jwtSecret string
type jwtAccessDuration time.Duration type jwtAccessDuration time.Duration
@@ -49,8 +49,8 @@ type allowedOrigins []string
// Config extractors // Config extractors
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func newGeminiAPIKey(appConfig *config.Config) geminiAPIKey { func newOpenAIAPIKey(appConfig *config.Config) openaiAPIKey {
return geminiAPIKey(appConfig.OpenAIAPIKey) return openaiAPIKey(appConfig.OpenAIAPIKey)
} }
func newPexelsAPIKey(appConfig *config.Config) pexelsAPIKey { func newPexelsAPIKey(appConfig *config.Config) pexelsAPIKey {
@@ -83,8 +83,8 @@ func newFirebaseCredentialsFile(appConfig *config.Config) string {
// Constructor wrappers for functions that accept primitive newtypes // Constructor wrappers for functions that accept primitive newtypes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func newGeminiClient(key geminiAPIKey) *gemini.Client { func newOpenAIClient(key openaiAPIKey) *openai.Client {
return gemini.NewClient(string(key)) return openai.NewClient(string(key))
} }
func newPexelsClient(key pexelsAPIKey) *pexels.Client { func newPexelsClient(key pexelsAPIKey) *pexels.Client {

View File

@@ -6,14 +6,14 @@ import (
"net/http" "net/http"
"github.com/food-ai/backend/internal/auth" "github.com/food-ai/backend/internal/auth"
"github.com/food-ai/backend/internal/config" "github.com/food-ai/backend/internal/infra/config"
"github.com/food-ai/backend/internal/diary" "github.com/food-ai/backend/internal/diary"
"github.com/food-ai/backend/internal/dish" "github.com/food-ai/backend/internal/dish"
"github.com/food-ai/backend/internal/home" "github.com/food-ai/backend/internal/home"
"github.com/food-ai/backend/internal/ingredient" "github.com/food-ai/backend/internal/ingredient"
"github.com/food-ai/backend/internal/menu" "github.com/food-ai/backend/internal/menu"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/food-ai/backend/internal/pexels" "github.com/food-ai/backend/internal/adapters/pexels"
"github.com/food-ai/backend/internal/product" "github.com/food-ai/backend/internal/product"
"github.com/food-ai/backend/internal/recipe" "github.com/food-ai/backend/internal/recipe"
"github.com/food-ai/backend/internal/recognition" "github.com/food-ai/backend/internal/recognition"
@@ -27,7 +27,7 @@ import (
func initRouter(appConfig *config.Config, pool *pgxpool.Pool) (http.Handler, error) { func initRouter(appConfig *config.Config, pool *pgxpool.Pool) (http.Handler, error) {
wire.Build( wire.Build(
// Config extractors // Config extractors
newGeminiAPIKey, newOpenAIAPIKey,
newPexelsAPIKey, newPexelsAPIKey,
newJWTSecret, newJWTSecret,
newJWTAccessDuration, newJWTAccessDuration,
@@ -49,7 +49,7 @@ func initRouter(appConfig *config.Config, pool *pgxpool.Pool) (http.Handler, err
user.NewHandler, user.NewHandler,
// External clients // External clients
newGeminiClient, newOpenAIClient,
newPexelsClient, newPexelsClient,
// Ingredient // Ingredient

View File

@@ -8,7 +8,7 @@ package main
import ( import (
"github.com/food-ai/backend/internal/auth" "github.com/food-ai/backend/internal/auth"
"github.com/food-ai/backend/internal/config" "github.com/food-ai/backend/internal/infra/config"
"github.com/food-ai/backend/internal/diary" "github.com/food-ai/backend/internal/diary"
"github.com/food-ai/backend/internal/dish" "github.com/food-ai/backend/internal/dish"
"github.com/food-ai/backend/internal/home" "github.com/food-ai/backend/internal/home"
@@ -41,8 +41,8 @@ func initRouter(appConfig *config.Config, pool *pgxpool.Pool) (http.Handler, err
handler := auth.NewHandler(service) handler := auth.NewHandler(service)
userService := user.NewService(repository) userService := user.NewService(repository)
userHandler := user.NewHandler(userService) userHandler := user.NewHandler(userService)
mainGeminiAPIKey := newGeminiAPIKey(appConfig) mainGeminiAPIKey := newOpenAIAPIKey(appConfig)
client := newGeminiClient(mainGeminiAPIKey) client := newOpenAIClient(mainGeminiAPIKey)
mainPexelsAPIKey := newPexelsAPIKey(appConfig) mainPexelsAPIKey := newPexelsAPIKey(appConfig)
pexelsClient := newPexelsClient(mainPexelsAPIKey) pexelsClient := newPexelsClient(mainPexelsAPIKey)
productRepository := product.NewRepository(pool) productRepository := product.NewRepository(pool)

View File

@@ -1,4 +1,4 @@
package gemini package openai
import ( import (
"bytes" "bytes"

View File

@@ -1,4 +1,4 @@
package gemini package openai
import ( import (
"context" "context"

View File

@@ -1,4 +1,4 @@
package gemini package openai
import ( import (
"context" "context"
@@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
) )
// RecipeGenerator generates recipes using the Gemini AI. // RecipeGenerator generates recipes using the Gemini AI.

View File

@@ -1,4 +1,4 @@
package gemini package openai
import ( import (
"encoding/json" "encoding/json"

View File

@@ -5,7 +5,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
) )
const maxRequestBodySize = 1 << 20 // 1 MB const maxRequestBodySize = 1 << 20 // 1 MB

View File

@@ -5,7 +5,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

@@ -5,7 +5,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )

View File

@@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

@@ -8,7 +8,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

@@ -3,7 +3,7 @@ package middleware
import ( import (
"net/http" "net/http"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
) )
// Language reads the Accept-Language request header, resolves the best // Language reads the Accept-Language request header, resolves the best

View File

@@ -11,7 +11,7 @@ import (
"github.com/food-ai/backend/internal/ingredient" "github.com/food-ai/backend/internal/ingredient"
"github.com/food-ai/backend/internal/language" "github.com/food-ai/backend/internal/language"
"github.com/food-ai/backend/internal/menu" "github.com/food-ai/backend/internal/menu"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/food-ai/backend/internal/recipe" "github.com/food-ai/backend/internal/recipe"
"github.com/food-ai/backend/internal/product" "github.com/food-ai/backend/internal/product"
"github.com/food-ai/backend/internal/recognition" "github.com/food-ai/backend/internal/recognition"

View File

@@ -6,7 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

@@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
) )
type languageItem struct { type languageItem struct {

View File

@@ -11,9 +11,9 @@ import (
"time" "time"
"github.com/food-ai/backend/internal/dish" "github.com/food-ai/backend/internal/dish"
"github.com/food-ai/backend/internal/gemini" "github.com/food-ai/backend/internal/adapters/openai"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/food-ai/backend/internal/user" "github.com/food-ai/backend/internal/user"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
@@ -41,7 +41,7 @@ type RecipeSaver interface {
// Handler handles menu and shopping-list endpoints. // Handler handles menu and shopping-list endpoints.
type Handler struct { type Handler struct {
repo *Repository repo *Repository
gemini *gemini.Client openaiClient *openai.Client
pexels PhotoSearcher pexels PhotoSearcher
userLoader UserLoader userLoader UserLoader
productLister ProductLister productLister ProductLister
@@ -51,7 +51,7 @@ type Handler struct {
// NewHandler creates a new Handler. // NewHandler creates a new Handler.
func NewHandler( func NewHandler(
repo *Repository, repo *Repository,
geminiClient *gemini.Client, openaiClient *openai.Client,
pexels PhotoSearcher, pexels PhotoSearcher,
userLoader UserLoader, userLoader UserLoader,
productLister ProductLister, productLister ProductLister,
@@ -59,7 +59,7 @@ func NewHandler(
) *Handler { ) *Handler {
return &Handler{ return &Handler{
repo: repo, repo: repo,
gemini: geminiClient, openaiClient: openaiClient,
pexels: pexels, pexels: pexels,
userLoader: userLoader, userLoader: userLoader,
productLister: productLister, productLister: productLister,
@@ -137,7 +137,7 @@ func (h *Handler) GenerateMenu(w http.ResponseWriter, r *http.Request) {
} }
// Generate 7-day plan via Gemini. // Generate 7-day plan via Gemini.
days, err := h.gemini.GenerateMenu(r.Context(), menuReq) days, err := h.openaiClient.GenerateMenu(r.Context(), menuReq)
if err != nil { if err != nil {
slog.Error("generate menu", "user_id", userID, "err", err) slog.Error("generate menu", "user_id", userID, "err", err)
writeError(w, http.StatusServiceUnavailable, "menu generation failed, please try again") writeError(w, http.StatusServiceUnavailable, "menu generation failed, please try again")
@@ -449,8 +449,8 @@ type userPreferences struct {
Restrictions []string `json:"restrictions"` Restrictions []string `json:"restrictions"`
} }
func buildMenuRequest(u *user.User, lang string) gemini.MenuRequest { func buildMenuRequest(u *user.User, lang string) openai.MenuRequest {
req := gemini.MenuRequest{DailyCalories: 2000, Lang: lang} req := openai.MenuRequest{DailyCalories: 2000, Lang: lang}
if u.Goal != nil { if u.Goal != nil {
req.UserGoal = *u.Goal req.UserGoal = *u.Goal
} }
@@ -468,7 +468,7 @@ func buildMenuRequest(u *user.User, lang string) gemini.MenuRequest {
} }
// recipeToCreateRequest converts a Gemini recipe to a dish.CreateRequest. // recipeToCreateRequest converts a Gemini recipe to a dish.CreateRequest.
func recipeToCreateRequest(r gemini.Recipe) dish.CreateRequest { func recipeToCreateRequest(r openai.Recipe) dish.CreateRequest {
cr := dish.CreateRequest{ cr := dish.CreateRequest{
Name: r.Title, Name: r.Title,
Description: r.Description, Description: r.Description,

View File

@@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

@@ -6,7 +6,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )

View File

@@ -5,7 +5,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )

View File

@@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

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

View File

@@ -8,9 +8,9 @@ import (
"strconv" "strconv"
"sync" "sync"
"github.com/food-ai/backend/internal/gemini" "github.com/food-ai/backend/internal/adapters/openai"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/food-ai/backend/internal/user" "github.com/food-ai/backend/internal/user"
) )
@@ -37,16 +37,16 @@ type userPreferences struct {
// Handler handles GET /recommendations. // Handler handles GET /recommendations.
type Handler struct { type Handler struct {
gemini *gemini.Client openaiClient *openai.Client
pexels PhotoSearcher pexels PhotoSearcher
userLoader UserLoader userLoader UserLoader
productLister ProductLister productLister ProductLister
} }
// NewHandler creates a new Handler. // NewHandler creates a new Handler.
func NewHandler(geminiClient *gemini.Client, pexels PhotoSearcher, userLoader UserLoader, productLister ProductLister) *Handler { func NewHandler(openaiClient *openai.Client, pexels PhotoSearcher, userLoader UserLoader, productLister ProductLister) *Handler {
return &Handler{ return &Handler{
gemini: geminiClient, openaiClient: openaiClient,
pexels: pexels, pexels: pexels,
userLoader: userLoader, userLoader: userLoader,
productLister: productLister, productLister: productLister,
@@ -84,7 +84,7 @@ func (h *Handler) GetRecommendations(w http.ResponseWriter, r *http.Request) {
slog.Warn("load products for recommendations", "user_id", userID, "err", err) slog.Warn("load products for recommendations", "user_id", userID, "err", err)
} }
recipes, err := h.gemini.GenerateRecipes(r.Context(), req) recipes, err := h.openaiClient.GenerateRecipes(r.Context(), req)
if err != nil { if err != nil {
slog.Error("generate recipes", "user_id", userID, "err", err) slog.Error("generate recipes", "user_id", userID, "err", err)
writeErrorJSON(w, http.StatusServiceUnavailable, "recipe generation failed, please try again") writeErrorJSON(w, http.StatusServiceUnavailable, "recipe generation failed, please try again")
@@ -109,8 +109,8 @@ func (h *Handler) GetRecommendations(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, recipes) writeJSON(w, http.StatusOK, recipes)
} }
func buildRecipeRequest(u *user.User, count int, lang string) gemini.RecipeRequest { func buildRecipeRequest(u *user.User, count int, lang string) openai.RecipeRequest {
req := gemini.RecipeRequest{ req := openai.RecipeRequest{
Count: count, Count: count,
DailyCalories: 2000, // sensible default DailyCalories: 2000, // sensible default
Lang: lang, Lang: lang,

View File

@@ -6,7 +6,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )

View File

@@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"github.com/food-ai/backend/internal/dish" "github.com/food-ai/backend/internal/dish"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

@@ -5,7 +5,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

@@ -5,7 +5,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )

View File

@@ -5,7 +5,7 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
) )
const maxRequestBodySize = 1 << 20 // 1 MB const maxRequestBodySize = 1 << 20 // 1 MB

View File

@@ -13,7 +13,7 @@ import (
"github.com/food-ai/backend/internal/auth" "github.com/food-ai/backend/internal/auth"
"github.com/food-ai/backend/internal/auth/mocks" "github.com/food-ai/backend/internal/auth/mocks"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/food-ai/backend/internal/testutil" "github.com/food-ai/backend/internal/testutil"
"github.com/food-ai/backend/internal/user" "github.com/food-ai/backend/internal/user"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"

View File

@@ -7,7 +7,7 @@ import (
"testing" "testing"
"github.com/food-ai/backend/internal/ingredient" "github.com/food-ai/backend/internal/ingredient"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
"github.com/food-ai/backend/internal/testutil" "github.com/food-ai/backend/internal/testutil"
) )

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/food-ai/backend/internal/locale" "github.com/food-ai/backend/internal/infra/locale"
) )
func TestParse(t *testing.T) { func TestParse(t *testing.T) {

View File

@@ -7,7 +7,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )

View File

@@ -5,7 +5,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
) )
func TestRecovery_NoPanic(t *testing.T) { func TestRecovery_NoPanic(t *testing.T) {

View File

@@ -5,7 +5,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/food-ai/backend/internal/middleware" "github.com/food-ai/backend/internal/infra/middleware"
) )
func TestRequestID_GeneratesNew(t *testing.T) { func TestRequestID_GeneratesNew(t *testing.T) {