updates
This commit is contained in:
187
server/internal/repository/daily_overview_repository.go
Normal file
187
server/internal/repository/daily_overview_repository.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go-server/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DailyOverviewRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewDailyOverviewRepository(db *gorm.DB) *DailyOverviewRepository {
|
||||
return &DailyOverviewRepository{db: db}
|
||||
}
|
||||
|
||||
// NutrientTotal represents the aggregated total for a nutrient across all supplements
|
||||
type NutrientTotal struct {
|
||||
NutrientID models.ULID `json:"nutrientId" db:"nutrient_id"`
|
||||
NutrientName string `json:"nutrientName" db:"nutrient_name"`
|
||||
Description string `json:"description" db:"description"`
|
||||
TotalAmount string `json:"totalAmount" db:"total_amount"`
|
||||
Unit string `json:"unit" db:"unit"`
|
||||
Categories []string `json:"categories" db:"categories"`
|
||||
}
|
||||
|
||||
// SupplementNutrientDetails represents detailed breakdown by supplement
|
||||
type SupplementNutrientDetails struct {
|
||||
SupplementID models.ULID `json:"supplementId" db:"supplement_id"`
|
||||
SupplementName string `json:"supplementName" db:"supplement_name"`
|
||||
NutrientID models.ULID `json:"nutrientId" db:"nutrient_id"`
|
||||
NutrientName string `json:"nutrientName" db:"nutrient_name"`
|
||||
Amount string `json:"amount" db:"amount"`
|
||||
Unit string `json:"unit" db:"unit"`
|
||||
ServingSize string `json:"servingSize" db:"serving_size"`
|
||||
ReferenceIntake string `json:"referenceIntake" db:"reference_intake"`
|
||||
}
|
||||
|
||||
// GetNutrientTotals returns aggregated nutrient totals across all supplements
|
||||
// This is perfect for a daily overview where you want to see total intake
|
||||
func (r *DailyOverviewRepository) GetNutrientTotals(ctx context.Context) ([]NutrientTotal, error) {
|
||||
var results []NutrientTotal
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
n.id as nutrient_id,
|
||||
n.name as nutrient_name,
|
||||
n.description,
|
||||
sn.per_serving_reference_intake,
|
||||
-- For now, we'll concatenate amounts (later we can parse and sum numeric values)
|
||||
STRING_AGG(sn.per_serving, ' + ') as total_amount,
|
||||
-- Extract unit from first entry (assumption: same nutrient has same unit)
|
||||
SPLIT_PART(MIN(sn.per_serving), ' ', 2) as unit,
|
||||
-- Get all categories for this nutrient
|
||||
ARRAY_AGG(DISTINCT c.name) as categories
|
||||
FROM nutrients n
|
||||
LEFT JOIN supplement_nutrients sn ON n.id = sn.nutrient_id
|
||||
LEFT JOIN supplements s ON sn.supplement_id = s.id
|
||||
LEFT JOIN nutrient_categories nc ON n.id = nc.nutrient_id
|
||||
LEFT JOIN categories c ON nc.category_id = c.id
|
||||
WHERE sn.id IS NOT NULL -- Only nutrients that are in supplements
|
||||
GROUP BY n.id, n.name, n.description, sn.per_serving_reference_intake
|
||||
ORDER BY n.name
|
||||
`
|
||||
|
||||
err := r.db.WithContext(ctx).Raw(query).Scan(&results).Error
|
||||
return results, err
|
||||
}
|
||||
|
||||
// GetSupplementBreakdown returns detailed breakdown of nutrients by supplement
|
||||
func (r *DailyOverviewRepository) GetSupplementBreakdown(ctx context.Context) ([]SupplementNutrientDetails, error) {
|
||||
var results []SupplementNutrientDetails
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
s.id as supplement_id,
|
||||
s.name as supplement_name,
|
||||
n.id as nutrient_id,
|
||||
n.name as nutrient_name,
|
||||
sn.per_serving as amount,
|
||||
SPLIT_PART(sn.per_serving, ' ', 2) as unit,
|
||||
sn.serving_size,
|
||||
sn.per_serving_reference_intake as reference_intake
|
||||
FROM supplements s
|
||||
JOIN supplement_nutrients sn ON s.id = sn.supplement_id
|
||||
JOIN nutrients n ON sn.nutrient_id = n.id
|
||||
ORDER BY s.name, n.name
|
||||
`
|
||||
|
||||
err := r.db.WithContext(ctx).Raw(query).Scan(&results).Error
|
||||
return results, err
|
||||
}
|
||||
|
||||
// GetNutrientsByCategory returns nutrients grouped by category with totals
|
||||
func (r *DailyOverviewRepository) GetNutrientsByCategory(ctx context.Context) (map[string][]NutrientTotal, error) {
|
||||
var results []struct {
|
||||
CategoryName string `db:"category_name"`
|
||||
CategoryID models.ULID `db:"category_id"`
|
||||
NutrientID models.ULID `db:"nutrient_id"`
|
||||
NutrientName string `db:"nutrient_name"`
|
||||
Description string `db:"description"`
|
||||
TotalAmount string `db:"total_amount"`
|
||||
Unit string `db:"unit"`
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
c.name as category_name,
|
||||
c.id as category_id,
|
||||
n.id as nutrient_id,
|
||||
n.name as nutrient_name,
|
||||
n.description,
|
||||
STRING_AGG(sn.per_serving, ' + ') as total_amount,
|
||||
SPLIT_PART(MIN(sn.per_serving), ' ', 2) as unit
|
||||
FROM categories c
|
||||
JOIN nutrient_categories nc ON c.id = nc.category_id
|
||||
JOIN nutrients n ON nc.nutrient_id = n.id
|
||||
LEFT JOIN supplement_nutrients sn ON n.id = sn.nutrient_id
|
||||
WHERE sn.id IS NOT NULL
|
||||
GROUP BY c.name, n.id, n.name, n.description, c.id
|
||||
ORDER BY c.name, n.name
|
||||
`
|
||||
|
||||
err := r.db.WithContext(ctx).Raw(query).Scan(&results).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Group by category
|
||||
categoryMap := make(map[string][]NutrientTotal)
|
||||
for _, result := range results {
|
||||
nutrient := NutrientTotal{
|
||||
NutrientID: result.NutrientID,
|
||||
NutrientName: result.NutrientName,
|
||||
Description: result.Description,
|
||||
TotalAmount: result.TotalAmount,
|
||||
Unit: result.Unit,
|
||||
Categories: []string{result.CategoryID.String()},
|
||||
}
|
||||
categoryMap[result.CategoryName] = append(categoryMap[result.CategoryName], nutrient)
|
||||
}
|
||||
|
||||
return categoryMap, nil
|
||||
}
|
||||
|
||||
// ExecuteRawQuery allows executing arbitrary SQL queries and scanning into any struct
|
||||
// This gives you full flexibility for custom queries
|
||||
func (r *DailyOverviewRepository) ExecuteRawQuery(ctx context.Context, query string, dest interface{}, args ...interface{}) error {
|
||||
return r.db.WithContext(ctx).Raw(query, args...).Scan(dest).Error
|
||||
}
|
||||
|
||||
// ExecuteRawQueryWithResult executes a raw query and returns the result as a map
|
||||
// Useful for dynamic queries where you don't know the structure ahead of time
|
||||
func (r *DailyOverviewRepository) ExecuteRawQueryWithResult(ctx context.Context, query string, args ...interface{}) ([]map[string]interface{}, error) {
|
||||
rows, err := r.db.WithContext(ctx).Raw(query, args...).Rows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []map[string]interface{}
|
||||
for rows.Next() {
|
||||
values := make([]interface{}, len(columns))
|
||||
valuePtrs := make([]interface{}, len(columns))
|
||||
for i := range columns {
|
||||
valuePtrs[i] = &values[i]
|
||||
}
|
||||
|
||||
if err := rows.Scan(valuePtrs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
for i, col := range columns {
|
||||
result[col] = values[i]
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
Reference in New Issue
Block a user