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

@@ -0,0 +1,99 @@
package openai
import (
"context"
"fmt"
"sync"
)
// MenuRequest contains parameters for weekly menu generation.
type MenuRequest struct {
UserGoal string
DailyCalories int
Restrictions []string
CuisinePrefs []string
AvailableProducts []string
Lang string // ISO 639-1 target language code, e.g. "en", "ru"
}
// DayPlan is the AI-generated plan for a single day.
type DayPlan struct {
Day int `json:"day"`
Meals []MealEntry `json:"meals"`
}
// MealEntry is a single meal within a day plan.
type MealEntry struct {
MealType string `json:"meal_type"` // breakfast | lunch | dinner
Recipe Recipe `json:"recipe"`
}
// GenerateMenu produces a 7-day × 3-meal plan by issuing three parallel
// GenerateRecipes calls (one per meal type). This avoids token-limit errors
// that arise from requesting 21 full recipes in a single prompt.
func (c *Client) GenerateMenu(ctx context.Context, req MenuRequest) ([]DayPlan, error) {
type mealSlot struct {
mealType string
fraction float64 // share of daily calories
}
slots := []mealSlot{
{"breakfast", 0.25},
{"lunch", 0.40},
{"dinner", 0.35},
}
type mealResult struct {
recipes []Recipe
err error
}
results := make([]mealResult, len(slots))
var wg sync.WaitGroup
for i, slot := range slots {
wg.Add(1)
go func(idx int, mealType string, fraction float64) {
defer wg.Done()
// Scale daily calories to what this meal should contribute.
mealCal := int(float64(req.DailyCalories) * fraction)
r, err := c.GenerateRecipes(ctx, RecipeRequest{
UserGoal: req.UserGoal,
DailyCalories: mealCal * 3, // prompt divides by 3 internally
Restrictions: req.Restrictions,
CuisinePrefs: req.CuisinePrefs,
Count: 7,
AvailableProducts: req.AvailableProducts,
Lang: req.Lang,
})
results[idx] = mealResult{r, err}
}(i, slot.mealType, slot.fraction)
}
wg.Wait()
for i, res := range results {
if res.err != nil {
return nil, fmt.Errorf("generate %s: %w", slots[i].mealType, res.err)
}
if len(res.recipes) == 0 {
return nil, fmt.Errorf("no %s recipes returned", slots[i].mealType)
}
// Pad to exactly 7 by repeating the last recipe.
for len(results[i].recipes) < 7 {
results[i].recipes = append(results[i].recipes, results[i].recipes[len(results[i].recipes)-1])
}
}
days := make([]DayPlan, 7)
for day := range 7 {
days[day] = DayPlan{
Day: day + 1,
Meals: []MealEntry{
{MealType: slots[0].mealType, Recipe: results[0].recipes[day]},
{MealType: slots[1].mealType, Recipe: results[1].recipes[day]},
{MealType: slots[2].mealType, Recipe: results[2].recipes[day]},
},
}
}
return days, nil
}