package diary import ( "context" "errors" "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" ) // ErrNotFound is returned when a diary entry does not exist for the user. var ErrNotFound = errors.New("diary entry not found") // Repository handles persistence for meal diary entries. type Repository struct { pool *pgxpool.Pool } // NewRepository creates a new Repository. func NewRepository(pool *pgxpool.Pool) *Repository { return &Repository{pool: pool} } // ListByDate returns all diary entries for a user on a given date (YYYY-MM-DD). func (r *Repository) ListByDate(ctx context.Context, userID, date string) ([]*Entry, error) { rows, err := r.pool.Query(ctx, ` SELECT id, date::text, meal_type, name, portions, calories, protein_g, fat_g, carbs_g, source, recipe_id, created_at FROM meal_diary WHERE user_id = $1 AND date = $2::date ORDER BY created_at ASC`, userID, date) if err != nil { return nil, fmt.Errorf("list diary: %w", err) } defer rows.Close() var result []*Entry for rows.Next() { e, err := scanEntry(rows) if err != nil { return nil, fmt.Errorf("scan diary entry: %w", err) } result = append(result, e) } return result, rows.Err() } // Create inserts a new diary entry and returns the stored record. func (r *Repository) Create(ctx context.Context, userID string, req CreateRequest) (*Entry, error) { portions := req.Portions if portions <= 0 { portions = 1 } source := req.Source if source == "" { source = "manual" } row := r.pool.QueryRow(ctx, ` INSERT INTO meal_diary (user_id, date, meal_type, name, portions, calories, protein_g, fat_g, carbs_g, source, recipe_id) VALUES ($1, $2::date, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id, date::text, meal_type, name, portions, calories, protein_g, fat_g, carbs_g, source, recipe_id, created_at`, userID, req.Date, req.MealType, req.Name, portions, req.Calories, req.ProteinG, req.FatG, req.CarbsG, source, req.RecipeID, ) return scanEntry(row) } // Delete removes a diary entry for the given user. func (r *Repository) Delete(ctx context.Context, id, userID string) error { tag, err := r.pool.Exec(ctx, `DELETE FROM meal_diary WHERE id = $1 AND user_id = $2`, id, userID) if err != nil { return fmt.Errorf("delete diary entry: %w", err) } if tag.RowsAffected() == 0 { return ErrNotFound } return nil } // --- helpers --- type scannable interface { Scan(dest ...any) error } func scanEntry(s scannable) (*Entry, error) { var e Entry err := s.Scan( &e.ID, &e.Date, &e.MealType, &e.Name, &e.Portions, &e.Calories, &e.ProteinG, &e.FatG, &e.CarbsG, &e.Source, &e.RecipeID, &e.CreatedAt, ) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrNotFound } return &e, err }