added imlementation of API,
updated AGENTS.md by Codex
This commit is contained in:
@ -5,7 +5,843 @@ namespace TPsoft\Nutrio;
|
||||
require_once __DIR__.'/Init.php';
|
||||
|
||||
use TPsoft\APIlite\APIlite;
|
||||
use TPsoft\Nutrio\Models\DiaryDays;
|
||||
use TPsoft\Nutrio\Models\DiaryEntries;
|
||||
use TPsoft\Nutrio\Models\Ingredients;
|
||||
use TPsoft\Nutrio\Models\MealItems;
|
||||
use TPsoft\Nutrio\Models\Meals;
|
||||
use TPsoft\Nutrio\Models\Options;
|
||||
use TPsoft\Nutrio\Models\Users;
|
||||
|
||||
class API extends APIlite {
|
||||
|
||||
private ?Users $usersModel = null;
|
||||
private ?Ingredients $ingredientsModel = null;
|
||||
private ?Meals $mealsModel = null;
|
||||
private ?MealItems $mealItemsModel = null;
|
||||
private ?DiaryDays $diaryDaysModel = null;
|
||||
private ?DiaryEntries $diaryEntriesModel = null;
|
||||
private ?Options $optionsModel = null;
|
||||
|
||||
public function health(): array
|
||||
{
|
||||
$dbVersion = null;
|
||||
$versionRow = $this->options()->option('version');
|
||||
if (is_array($versionRow) && isset($versionRow['value'])) {
|
||||
$dbVersion = (string) $versionRow['value'];
|
||||
}
|
||||
return array(
|
||||
'service' => 'Nutrio API',
|
||||
'timestamp' => date('c'),
|
||||
'db_type' => $this->users()->getDBtype(),
|
||||
'db_version' => $dbVersion
|
||||
);
|
||||
}
|
||||
|
||||
public function ingredientList(int $user_id, string $query = '', bool $include_global = true): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$search = array(
|
||||
'user_id' => $include_global ? array($user_id, null) : $user_id
|
||||
);
|
||||
$query = trim($query);
|
||||
if (strlen($query) > 0) {
|
||||
$search['name'] = '%' . $query . '%';
|
||||
}
|
||||
$list = $this->ingredients()->getList($search);
|
||||
$list = is_array($list) ? $list : array();
|
||||
$result = array();
|
||||
foreach ($list as $row) {
|
||||
$result[] = $this->mapIngredient($row);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function ingredientGet(int $user_id, int $ingredient_id): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
return $this->mapIngredient($this->getIngredientAccessible($user_id, $ingredient_id));
|
||||
}
|
||||
|
||||
public function ingredientCreate(
|
||||
int $user_id,
|
||||
string $name,
|
||||
float $protein_g_100,
|
||||
float $carbs_g_100,
|
||||
float $sugar_g_100,
|
||||
float $fat_g_100,
|
||||
float $fiber_g_100 = 0,
|
||||
float $kcal_100 = 0
|
||||
): array {
|
||||
$this->assertUserExists($user_id);
|
||||
$name = $this->normalizeName($name);
|
||||
$this->assertNonNegative('protein_g_100', $protein_g_100);
|
||||
$this->assertNonNegative('carbs_g_100', $carbs_g_100);
|
||||
$this->assertNonNegative('sugar_g_100', $sugar_g_100);
|
||||
$this->assertNonNegative('fat_g_100', $fat_g_100);
|
||||
$this->assertNonNegative('fiber_g_100', $fiber_g_100);
|
||||
if ($kcal_100 < 0) {
|
||||
throw new \Exception('kcal_100 cannot be negative');
|
||||
}
|
||||
if ($kcal_100 == 0) {
|
||||
$kcal_100 = $this->computeKcal100($protein_g_100, $carbs_g_100, $fat_g_100);
|
||||
}
|
||||
$ingredientId = $this->ingredients()->ingredientSave(array(
|
||||
'user_id' => $user_id,
|
||||
'name' => $name,
|
||||
'protein_g_100' => $this->round2($protein_g_100),
|
||||
'carbs_g_100' => $this->round2($carbs_g_100),
|
||||
'sugar_g_100' => $this->round2($sugar_g_100),
|
||||
'fat_g_100' => $this->round2($fat_g_100),
|
||||
'fiber_g_100' => $this->round2($fiber_g_100),
|
||||
'kcal_100' => $this->round2($kcal_100),
|
||||
'created_at' => '`NOW`'
|
||||
));
|
||||
if ($ingredientId === false) {
|
||||
throw new \Exception('Failed to create ingredient');
|
||||
}
|
||||
$created = $this->ingredients()->ingredient((int) $ingredientId);
|
||||
if (!is_array($created)) {
|
||||
throw new \Exception('Failed to load created ingredient');
|
||||
}
|
||||
return $this->mapIngredient($created);
|
||||
}
|
||||
|
||||
public function ingredientUpdate(
|
||||
int $user_id,
|
||||
int $ingredient_id,
|
||||
string $name,
|
||||
float $protein_g_100,
|
||||
float $carbs_g_100,
|
||||
float $sugar_g_100,
|
||||
float $fat_g_100,
|
||||
float $fiber_g_100 = 0,
|
||||
float $kcal_100 = 0
|
||||
): array {
|
||||
$this->assertUserExists($user_id);
|
||||
$this->getIngredientOwned($user_id, $ingredient_id);
|
||||
$name = $this->normalizeName($name);
|
||||
$this->assertNonNegative('protein_g_100', $protein_g_100);
|
||||
$this->assertNonNegative('carbs_g_100', $carbs_g_100);
|
||||
$this->assertNonNegative('sugar_g_100', $sugar_g_100);
|
||||
$this->assertNonNegative('fat_g_100', $fat_g_100);
|
||||
$this->assertNonNegative('fiber_g_100', $fiber_g_100);
|
||||
if ($kcal_100 < 0) {
|
||||
throw new \Exception('kcal_100 cannot be negative');
|
||||
}
|
||||
if ($kcal_100 == 0) {
|
||||
$kcal_100 = $this->computeKcal100($protein_g_100, $carbs_g_100, $fat_g_100);
|
||||
}
|
||||
$updated = $this->ingredients()->ingredient($ingredient_id, array(
|
||||
'name' => $name,
|
||||
'protein_g_100' => $this->round2($protein_g_100),
|
||||
'carbs_g_100' => $this->round2($carbs_g_100),
|
||||
'sugar_g_100' => $this->round2($sugar_g_100),
|
||||
'fat_g_100' => $this->round2($fat_g_100),
|
||||
'fiber_g_100' => $this->round2($fiber_g_100),
|
||||
'kcal_100' => $this->round2($kcal_100)
|
||||
));
|
||||
if ($updated === false) {
|
||||
throw new \Exception('Failed to update ingredient');
|
||||
}
|
||||
$row = $this->ingredients()->ingredient($ingredient_id);
|
||||
if (!is_array($row)) {
|
||||
throw new \Exception('Failed to load updated ingredient');
|
||||
}
|
||||
return $this->mapIngredient($row);
|
||||
}
|
||||
|
||||
public function ingredientDelete(int $user_id, int $ingredient_id): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->getIngredientOwned($user_id, $ingredient_id);
|
||||
$deleted = $this->ingredients()->ingredient($ingredient_id, null);
|
||||
if ($deleted === false) {
|
||||
throw new \Exception('Failed to delete ingredient');
|
||||
}
|
||||
return array(
|
||||
'deleted' => true,
|
||||
'ingredient_id' => $ingredient_id
|
||||
);
|
||||
}
|
||||
|
||||
public function mealList(int $user_id, string $meal_type = '', bool $with_items = false, bool $with_totals = false): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$search = array('user_id' => $user_id);
|
||||
$meal_type = trim($meal_type);
|
||||
if (strlen($meal_type) > 0) {
|
||||
$this->assertMealType($meal_type);
|
||||
$search['meal_type'] = $meal_type;
|
||||
}
|
||||
$rows = $this->meals()->getList($search);
|
||||
$rows = is_array($rows) ? $rows : array();
|
||||
$result = array();
|
||||
foreach ($rows as $row) {
|
||||
$meal = $this->mapMeal($row);
|
||||
$calculatedItems = null;
|
||||
if ($with_items) {
|
||||
$calculatedItems = $this->buildMealItems((int) $meal['meal_id'], true);
|
||||
$meal['items'] = $calculatedItems;
|
||||
}
|
||||
if ($with_totals) {
|
||||
if (is_array($calculatedItems)) {
|
||||
$meal['totals'] = $this->totalsFromCalculatedItems($calculatedItems);
|
||||
} else {
|
||||
$meal['totals'] = $this->calculateMealTotals((int) $meal['meal_id']);
|
||||
}
|
||||
}
|
||||
$result[] = $meal;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function mealGet(int $user_id, int $meal_id, bool $with_items = true, bool $with_totals = true): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$meal = $this->mapMeal($this->getMealOwned($user_id, $meal_id));
|
||||
$calculatedItems = null;
|
||||
if ($with_items) {
|
||||
$calculatedItems = $this->buildMealItems($meal_id, true);
|
||||
$meal['items'] = $calculatedItems;
|
||||
}
|
||||
if ($with_totals) {
|
||||
if (is_array($calculatedItems)) {
|
||||
$meal['totals'] = $this->totalsFromCalculatedItems($calculatedItems);
|
||||
} else {
|
||||
$meal['totals'] = $this->calculateMealTotals($meal_id);
|
||||
}
|
||||
}
|
||||
return $meal;
|
||||
}
|
||||
|
||||
public function mealCreate(int $user_id, string $name, string $meal_type): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$name = $this->normalizeName($name);
|
||||
$this->assertMealType($meal_type);
|
||||
$mealId = $this->meals()->mealSave(array(
|
||||
'user_id' => $user_id,
|
||||
'name' => $name,
|
||||
'meal_type' => $meal_type,
|
||||
'created_at' => '`NOW`'
|
||||
));
|
||||
if ($mealId === false) {
|
||||
throw new \Exception('Failed to create meal');
|
||||
}
|
||||
return $this->mealGet($user_id, (int) $mealId, false, false);
|
||||
}
|
||||
|
||||
public function mealUpdate(int $user_id, int $meal_id, string $name, string $meal_type): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->getMealOwned($user_id, $meal_id);
|
||||
$name = $this->normalizeName($name);
|
||||
$this->assertMealType($meal_type);
|
||||
$updated = $this->meals()->meal($meal_id, array(
|
||||
'name' => $name,
|
||||
'meal_type' => $meal_type
|
||||
));
|
||||
if ($updated === false) {
|
||||
throw new \Exception('Failed to update meal');
|
||||
}
|
||||
return $this->mealGet($user_id, $meal_id, false, false);
|
||||
}
|
||||
|
||||
public function mealDelete(int $user_id, int $meal_id): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->getMealOwned($user_id, $meal_id);
|
||||
$deleted = $this->meals()->meal($meal_id, null);
|
||||
if ($deleted === false) {
|
||||
throw new \Exception('Failed to delete meal');
|
||||
}
|
||||
return array(
|
||||
'deleted' => true,
|
||||
'meal_id' => $meal_id
|
||||
);
|
||||
}
|
||||
|
||||
public function mealItemList(int $user_id, int $meal_id, bool $with_calculated = true): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->getMealOwned($user_id, $meal_id);
|
||||
return array(
|
||||
'meal_id' => $meal_id,
|
||||
'items' => $this->buildMealItems($meal_id, $with_calculated)
|
||||
);
|
||||
}
|
||||
|
||||
public function mealItemAdd(int $user_id, int $meal_id, int $ingredient_id, float $grams, int $position = 1): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->getMealOwned($user_id, $meal_id);
|
||||
$this->assertPositive('grams', $grams);
|
||||
if ($position < 1) {
|
||||
throw new \Exception('position must be >= 1');
|
||||
}
|
||||
$this->getIngredientAccessible($user_id, $ingredient_id);
|
||||
$mealItemId = $this->mealItems()->mealItemSave(array(
|
||||
'meal_id' => $meal_id,
|
||||
'ingredient_id' => $ingredient_id,
|
||||
'grams' => $this->round2($grams),
|
||||
'position' => $position
|
||||
));
|
||||
if ($mealItemId === false) {
|
||||
throw new \Exception('Failed to add meal item');
|
||||
}
|
||||
$item = $this->getMealItemOwned($user_id, (int) $mealItemId);
|
||||
return $this->enrichMealItem($item);
|
||||
}
|
||||
|
||||
public function mealItemUpdate(int $user_id, int $meal_item_id, int $ingredient_id, float $grams, int $position): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$item = $this->getMealItemOwned($user_id, $meal_item_id);
|
||||
$this->assertPositive('grams', $grams);
|
||||
if ($position < 1) {
|
||||
throw new \Exception('position must be >= 1');
|
||||
}
|
||||
$this->getIngredientAccessible($user_id, $ingredient_id);
|
||||
$updated = $this->mealItems()->mealItem($meal_item_id, array(
|
||||
'ingredient_id' => $ingredient_id,
|
||||
'grams' => $this->round2($grams),
|
||||
'position' => $position
|
||||
));
|
||||
if ($updated === false) {
|
||||
throw new \Exception('Failed to update meal item');
|
||||
}
|
||||
$item = $this->getMealItemOwned($user_id, (int) $item['meal_item_id']);
|
||||
return $this->enrichMealItem($item);
|
||||
}
|
||||
|
||||
public function mealItemDelete(int $user_id, int $meal_item_id): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$item = $this->getMealItemOwned($user_id, $meal_item_id);
|
||||
$deleted = $this->mealItems()->mealItem($meal_item_id, null);
|
||||
if ($deleted === false) {
|
||||
throw new \Exception('Failed to delete meal item');
|
||||
}
|
||||
return array(
|
||||
'deleted' => true,
|
||||
'meal_item_id' => $meal_item_id,
|
||||
'meal_id' => (int) $item['meal_id']
|
||||
);
|
||||
}
|
||||
|
||||
public function mealItemReorder(int $user_id, int $meal_id, array $ordered_item_ids): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->getMealOwned($user_id, $meal_id);
|
||||
if (count($ordered_item_ids) <= 0) {
|
||||
throw new \Exception('ordered_item_ids cannot be empty');
|
||||
}
|
||||
$currentItems = $this->buildMealItems($meal_id, false);
|
||||
$currentIds = array();
|
||||
foreach ($currentItems as $item) {
|
||||
$currentIds[] = (int) $item['meal_item_id'];
|
||||
}
|
||||
$orderedIds = array_values(array_map('intval', $ordered_item_ids));
|
||||
sort($currentIds);
|
||||
$checkIds = $orderedIds;
|
||||
sort($checkIds);
|
||||
if ($currentIds !== $checkIds) {
|
||||
throw new \Exception('ordered_item_ids must match all meal item ids exactly');
|
||||
}
|
||||
foreach ($orderedIds as $index => $mealItemId) {
|
||||
$ok = $this->mealItems()->mealItem($mealItemId, array('position' => $index + 1));
|
||||
if ($ok === false) {
|
||||
throw new \Exception('Failed to reorder meal items');
|
||||
}
|
||||
}
|
||||
return array(
|
||||
'meal_id' => $meal_id,
|
||||
'items' => $this->buildMealItems($meal_id, true)
|
||||
);
|
||||
}
|
||||
|
||||
public function mealTotals(int $user_id, int $meal_id): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->getMealOwned($user_id, $meal_id);
|
||||
return array(
|
||||
'meal_id' => $meal_id,
|
||||
'totals' => $this->calculateMealTotals($meal_id)
|
||||
);
|
||||
}
|
||||
|
||||
public function diaryDayGet(int $user_id, string $day_date, bool $with_totals = true): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->assertDate($day_date);
|
||||
return $this->buildDiaryDay($user_id, $day_date, $with_totals);
|
||||
}
|
||||
|
||||
public function diaryDaySetMeal(int $user_id, string $day_date, string $meal_type, int $meal_id): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->assertDate($day_date);
|
||||
$this->assertMealType($meal_type);
|
||||
$meal = $this->getMealOwned($user_id, $meal_id);
|
||||
if ($meal['meal_type'] !== $meal_type) {
|
||||
throw new \Exception('meal_type does not match selected meal');
|
||||
}
|
||||
$day = $this->ensureDiaryDay($user_id, $day_date);
|
||||
$existing = $this->diaryEntries()->search('diaryEntries')
|
||||
->where(array(
|
||||
'diary_day_id' => (int) $day['diary_day_id'],
|
||||
'meal_type' => $meal_type
|
||||
))
|
||||
->toArrayFirst();
|
||||
if (is_array($existing) && isset($existing['diary_entry_id'])) {
|
||||
$updated = $this->diaryEntries()->diaryEntry((int) $existing['diary_entry_id'], array(
|
||||
'meal_id' => $meal_id
|
||||
));
|
||||
if ($updated === false) {
|
||||
throw new \Exception('Failed to update diary entry');
|
||||
}
|
||||
} else {
|
||||
$inserted = $this->diaryEntries()->diaryEntrySave(array(
|
||||
'diary_day_id' => (int) $day['diary_day_id'],
|
||||
'meal_type' => $meal_type,
|
||||
'meal_id' => $meal_id,
|
||||
'created_at' => '`NOW`'
|
||||
));
|
||||
if ($inserted === false) {
|
||||
throw new \Exception('Failed to create diary entry');
|
||||
}
|
||||
}
|
||||
return $this->buildDiaryDay($user_id, $day_date, true);
|
||||
}
|
||||
|
||||
public function diaryDayUnsetMeal(int $user_id, string $day_date, string $meal_type): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->assertDate($day_date);
|
||||
$this->assertMealType($meal_type);
|
||||
$day = $this->getDiaryDay($user_id, $day_date);
|
||||
if ($day === false) {
|
||||
return $this->buildDiaryDay($user_id, $day_date, true);
|
||||
}
|
||||
$existing = $this->diaryEntries()->search('diaryEntries')
|
||||
->where(array(
|
||||
'diary_day_id' => (int) $day['diary_day_id'],
|
||||
'meal_type' => $meal_type
|
||||
))
|
||||
->toArrayFirst();
|
||||
if (is_array($existing) && isset($existing['diary_entry_id'])) {
|
||||
$deleted = $this->diaryEntries()->diaryEntry((int) $existing['diary_entry_id'], null);
|
||||
if ($deleted === false) {
|
||||
throw new \Exception('Failed to delete diary entry');
|
||||
}
|
||||
}
|
||||
return $this->buildDiaryDay($user_id, $day_date, true);
|
||||
}
|
||||
|
||||
public function diaryRange(int $user_id, string $date_from, string $date_to): array
|
||||
{
|
||||
$this->assertUserExists($user_id);
|
||||
$this->assertDate($date_from);
|
||||
$this->assertDate($date_to);
|
||||
if ($date_from > $date_to) {
|
||||
throw new \Exception('date_from must be before or equal to date_to');
|
||||
}
|
||||
$rows = $this->diaryDays()->search('diaryDays')
|
||||
->where(array('user_id' => $user_id))
|
||||
->where(array('day_date' => '>=' . $date_from))
|
||||
->where(array('day_date' => '<=' . $date_to))
|
||||
->order(array('day_date' => 'ASC'))
|
||||
->toArray();
|
||||
$rows = is_array($rows) ? $rows : array();
|
||||
$days = array();
|
||||
$totals = $this->emptyTotals();
|
||||
foreach ($rows as $row) {
|
||||
$day = $this->buildDiaryDay($user_id, (string) $row['day_date'], true);
|
||||
$days[] = $day;
|
||||
if (isset($day['totals']) && is_array($day['totals'])) {
|
||||
$totals = $this->addTotals($totals, $day['totals']);
|
||||
}
|
||||
}
|
||||
return array(
|
||||
'user_id' => $user_id,
|
||||
'date_from' => $date_from,
|
||||
'date_to' => $date_to,
|
||||
'days' => $days,
|
||||
'totals' => $totals
|
||||
);
|
||||
}
|
||||
|
||||
private function users(): Users
|
||||
{
|
||||
if ($this->usersModel === null) {
|
||||
$this->usersModel = new Users();
|
||||
}
|
||||
return $this->usersModel;
|
||||
}
|
||||
|
||||
private function ingredients(): Ingredients
|
||||
{
|
||||
if ($this->ingredientsModel === null) {
|
||||
$this->ingredientsModel = new Ingredients();
|
||||
}
|
||||
return $this->ingredientsModel;
|
||||
}
|
||||
|
||||
private function meals(): Meals
|
||||
{
|
||||
if ($this->mealsModel === null) {
|
||||
$this->mealsModel = new Meals();
|
||||
}
|
||||
return $this->mealsModel;
|
||||
}
|
||||
|
||||
private function mealItems(): MealItems
|
||||
{
|
||||
if ($this->mealItemsModel === null) {
|
||||
$this->mealItemsModel = new MealItems();
|
||||
}
|
||||
return $this->mealItemsModel;
|
||||
}
|
||||
|
||||
private function diaryDays(): DiaryDays
|
||||
{
|
||||
if ($this->diaryDaysModel === null) {
|
||||
$this->diaryDaysModel = new DiaryDays();
|
||||
}
|
||||
return $this->diaryDaysModel;
|
||||
}
|
||||
|
||||
private function diaryEntries(): DiaryEntries
|
||||
{
|
||||
if ($this->diaryEntriesModel === null) {
|
||||
$this->diaryEntriesModel = new DiaryEntries();
|
||||
}
|
||||
return $this->diaryEntriesModel;
|
||||
}
|
||||
|
||||
private function options(): Options
|
||||
{
|
||||
if ($this->optionsModel === null) {
|
||||
$this->optionsModel = new Options();
|
||||
}
|
||||
return $this->optionsModel;
|
||||
}
|
||||
|
||||
private function assertUserExists(int $user_id): void
|
||||
{
|
||||
if ($user_id <= 0) {
|
||||
throw new \Exception('user_id must be > 0');
|
||||
}
|
||||
if (!$this->users()->exist($user_id)) {
|
||||
throw new \Exception('User not found');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertMealType(string $meal_type): void
|
||||
{
|
||||
$allowed = array('breakfast', 'lunch', 'dinner');
|
||||
if (!in_array($meal_type, $allowed, true)) {
|
||||
throw new \Exception('Invalid meal_type');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertDate(string $day_date): void
|
||||
{
|
||||
$dt = \DateTime::createFromFormat('Y-m-d', $day_date);
|
||||
if (!$dt || $dt->format('Y-m-d') !== $day_date) {
|
||||
throw new \Exception('Invalid date format, expected YYYY-MM-DD');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertNonNegative(string $name, float $value): void
|
||||
{
|
||||
if ($value < 0) {
|
||||
throw new \Exception($name . ' cannot be negative');
|
||||
}
|
||||
}
|
||||
|
||||
private function assertPositive(string $name, float $value): void
|
||||
{
|
||||
if ($value <= 0) {
|
||||
throw new \Exception($name . ' must be > 0');
|
||||
}
|
||||
}
|
||||
|
||||
private function normalizeName(string $name): string
|
||||
{
|
||||
$name = trim($name);
|
||||
if (strlen($name) <= 0) {
|
||||
throw new \Exception('name is required');
|
||||
}
|
||||
if (strlen($name) > 255) {
|
||||
throw new \Exception('name is too long');
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
private function round2(float $value): float
|
||||
{
|
||||
return round($value, 2);
|
||||
}
|
||||
|
||||
private function computeKcal100(float $protein_g_100, float $carbs_g_100, float $fat_g_100): float
|
||||
{
|
||||
return $this->round2($protein_g_100 * 4 + $carbs_g_100 * 4 + $fat_g_100 * 9);
|
||||
}
|
||||
|
||||
private function getMealOwned(int $user_id, int $meal_id): array
|
||||
{
|
||||
$meal = $this->meals()->meal($meal_id);
|
||||
if (!is_array($meal)) {
|
||||
throw new \Exception('Meal not found');
|
||||
}
|
||||
if ((int) $meal['user_id'] !== $user_id) {
|
||||
throw new \Exception('Meal does not belong to user');
|
||||
}
|
||||
return $meal;
|
||||
}
|
||||
|
||||
private function getIngredientAccessible(int $user_id, int $ingredient_id): array
|
||||
{
|
||||
$ingredient = $this->ingredients()->ingredient($ingredient_id);
|
||||
if (!is_array($ingredient)) {
|
||||
throw new \Exception('Ingredient not found');
|
||||
}
|
||||
if (!is_null($ingredient['user_id']) && (int) $ingredient['user_id'] !== $user_id) {
|
||||
throw new \Exception('Ingredient is not accessible for user');
|
||||
}
|
||||
return $ingredient;
|
||||
}
|
||||
|
||||
private function getIngredientOwned(int $user_id, int $ingredient_id): array
|
||||
{
|
||||
$ingredient = $this->ingredients()->ingredient($ingredient_id);
|
||||
if (!is_array($ingredient)) {
|
||||
throw new \Exception('Ingredient not found');
|
||||
}
|
||||
if (is_null($ingredient['user_id']) || (int) $ingredient['user_id'] !== $user_id) {
|
||||
throw new \Exception('Ingredient does not belong to user');
|
||||
}
|
||||
return $ingredient;
|
||||
}
|
||||
|
||||
private function getMealItemOwned(int $user_id, int $meal_item_id): array
|
||||
{
|
||||
$item = $this->mealItems()->mealItem($meal_item_id);
|
||||
if (!is_array($item)) {
|
||||
throw new \Exception('Meal item not found');
|
||||
}
|
||||
$this->getMealOwned($user_id, (int) $item['meal_id']);
|
||||
return $this->mapMealItem($item);
|
||||
}
|
||||
|
||||
private function buildMealItems(int $meal_id, bool $with_calculated = true): array
|
||||
{
|
||||
$rows = $this->mealItems()->search('mealItems')
|
||||
->where(array('meal_id' => $meal_id))
|
||||
->order(array('position' => 'ASC', 'meal_item_id' => 'ASC'))
|
||||
->toArray();
|
||||
$rows = is_array($rows) ? $rows : array();
|
||||
$result = array();
|
||||
foreach ($rows as $row) {
|
||||
$item = $this->mapMealItem($row);
|
||||
if ($with_calculated) {
|
||||
$item = $this->enrichMealItem($item);
|
||||
}
|
||||
$result[] = $item;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function enrichMealItem(array $item): array
|
||||
{
|
||||
$ingredient = $this->ingredients()->ingredient((int) $item['ingredient_id']);
|
||||
if (!is_array($ingredient)) {
|
||||
throw new \Exception('Ingredient for meal item was not found');
|
||||
}
|
||||
$ingredient = $this->mapIngredient($ingredient);
|
||||
$factor = ((float) $item['grams']) / 100;
|
||||
$kcal100 = (float) $ingredient['kcal_100'];
|
||||
if ($kcal100 <= 0) {
|
||||
$kcal100 = $this->computeKcal100(
|
||||
(float) $ingredient['protein_g_100'],
|
||||
(float) $ingredient['carbs_g_100'],
|
||||
(float) $ingredient['fat_g_100']
|
||||
);
|
||||
}
|
||||
$item['ingredient'] = $ingredient;
|
||||
$item['nutrition'] = array(
|
||||
'protein_g' => $this->round2(((float) $ingredient['protein_g_100']) * $factor),
|
||||
'carbs_g' => $this->round2(((float) $ingredient['carbs_g_100']) * $factor),
|
||||
'sugar_g' => $this->round2(((float) $ingredient['sugar_g_100']) * $factor),
|
||||
'fat_g' => $this->round2(((float) $ingredient['fat_g_100']) * $factor),
|
||||
'fiber_g' => $this->round2(((float) $ingredient['fiber_g_100']) * $factor),
|
||||
'kcal' => $this->round2($kcal100 * $factor)
|
||||
);
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function calculateMealTotals(int $meal_id): array
|
||||
{
|
||||
$items = $this->buildMealItems($meal_id, true);
|
||||
return $this->totalsFromCalculatedItems($items);
|
||||
}
|
||||
|
||||
private function totalsFromCalculatedItems(array $items): array
|
||||
{
|
||||
$totals = $this->emptyTotals();
|
||||
foreach ($items as $item) {
|
||||
if (!isset($item['nutrition']) || !is_array($item['nutrition'])) {
|
||||
continue;
|
||||
}
|
||||
$totals = $this->addTotals($totals, $item['nutrition']);
|
||||
}
|
||||
return $totals;
|
||||
}
|
||||
|
||||
private function emptyTotals(): array
|
||||
{
|
||||
return array(
|
||||
'protein_g' => 0.0,
|
||||
'carbs_g' => 0.0,
|
||||
'sugar_g' => 0.0,
|
||||
'fat_g' => 0.0,
|
||||
'fiber_g' => 0.0,
|
||||
'kcal' => 0.0
|
||||
);
|
||||
}
|
||||
|
||||
private function addTotals(array $base, array $add): array
|
||||
{
|
||||
foreach ($base as $key => $value) {
|
||||
$base[$key] = $this->round2((float) $value + (float) ($add[$key] ?? 0));
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
private function getDiaryDay(int $user_id, string $day_date): array|false
|
||||
{
|
||||
$rows = $this->diaryDays()->getList(array(
|
||||
'user_id' => $user_id,
|
||||
'day_date' => $day_date
|
||||
));
|
||||
if (!is_array($rows) || count($rows) <= 0) {
|
||||
return false;
|
||||
}
|
||||
return $rows[0];
|
||||
}
|
||||
|
||||
private function ensureDiaryDay(int $user_id, string $day_date): array
|
||||
{
|
||||
$day = $this->getDiaryDay($user_id, $day_date);
|
||||
if (is_array($day)) {
|
||||
return $this->mapDiaryDay($day);
|
||||
}
|
||||
$dayId = $this->diaryDays()->diaryDaySave(array(
|
||||
'user_id' => $user_id,
|
||||
'day_date' => $day_date,
|
||||
'created_at' => '`NOW`'
|
||||
));
|
||||
if ($dayId === false) {
|
||||
throw new \Exception('Failed to create diary day');
|
||||
}
|
||||
$created = $this->diaryDays()->diaryDay((int) $dayId);
|
||||
if (!is_array($created)) {
|
||||
throw new \Exception('Failed to load created diary day');
|
||||
}
|
||||
return $this->mapDiaryDay($created);
|
||||
}
|
||||
|
||||
private function buildDiaryDay(int $user_id, string $day_date, bool $with_totals): array
|
||||
{
|
||||
$day = $this->getDiaryDay($user_id, $day_date);
|
||||
if (!is_array($day)) {
|
||||
$ret = array(
|
||||
'user_id' => $user_id,
|
||||
'day_date' => $day_date,
|
||||
'diary_day_id' => null,
|
||||
'entries' => array()
|
||||
);
|
||||
if ($with_totals) {
|
||||
$ret['totals'] = $this->emptyTotals();
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
$day = $this->mapDiaryDay($day);
|
||||
$entriesRows = $this->diaryEntries()->search('diaryEntries')
|
||||
->where(array('diary_day_id' => (int) $day['diary_day_id']))
|
||||
->order(array('diary_entry_id' => 'ASC'))
|
||||
->toArray();
|
||||
$entriesRows = is_array($entriesRows) ? $entriesRows : array();
|
||||
$entries = array();
|
||||
$totals = $this->emptyTotals();
|
||||
foreach ($entriesRows as $row) {
|
||||
$entry = $this->mapDiaryEntry($row);
|
||||
$meal = $this->mapMeal($this->getMealOwned($user_id, (int) $entry['meal_id']));
|
||||
$entry['meal'] = $meal;
|
||||
if ($with_totals) {
|
||||
$mealTotals = $this->calculateMealTotals((int) $entry['meal_id']);
|
||||
$entry['meal_totals'] = $mealTotals;
|
||||
$totals = $this->addTotals($totals, $mealTotals);
|
||||
}
|
||||
$entries[] = $entry;
|
||||
}
|
||||
$ret = array(
|
||||
'user_id' => $user_id,
|
||||
'day_date' => $day_date,
|
||||
'diary_day_id' => (int) $day['diary_day_id'],
|
||||
'entries' => $entries
|
||||
);
|
||||
if ($with_totals) {
|
||||
$ret['totals'] = $totals;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function mapIngredient(array $row): array
|
||||
{
|
||||
$row['ingredient_id'] = (int) $row['ingredient_id'];
|
||||
$row['user_id'] = is_null($row['user_id']) ? null : (int) $row['user_id'];
|
||||
$row['protein_g_100'] = $this->round2((float) $row['protein_g_100']);
|
||||
$row['carbs_g_100'] = $this->round2((float) $row['carbs_g_100']);
|
||||
$row['sugar_g_100'] = $this->round2((float) $row['sugar_g_100']);
|
||||
$row['fat_g_100'] = $this->round2((float) $row['fat_g_100']);
|
||||
$row['fiber_g_100'] = $this->round2((float) $row['fiber_g_100']);
|
||||
$row['kcal_100'] = $this->round2((float) $row['kcal_100']);
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function mapMeal(array $row): array
|
||||
{
|
||||
$row['meal_id'] = (int) $row['meal_id'];
|
||||
$row['user_id'] = (int) $row['user_id'];
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function mapMealItem(array $row): array
|
||||
{
|
||||
$row['meal_item_id'] = (int) $row['meal_item_id'];
|
||||
$row['meal_id'] = (int) $row['meal_id'];
|
||||
$row['ingredient_id'] = (int) $row['ingredient_id'];
|
||||
$row['grams'] = $this->round2((float) $row['grams']);
|
||||
$row['position'] = (int) $row['position'];
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function mapDiaryDay(array $row): array
|
||||
{
|
||||
$row['diary_day_id'] = (int) $row['diary_day_id'];
|
||||
$row['user_id'] = (int) $row['user_id'];
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function mapDiaryEntry(array $row): array
|
||||
{
|
||||
$row['diary_entry_id'] = (int) $row['diary_entry_id'];
|
||||
$row['diary_day_id'] = (int) $row['diary_day_id'];
|
||||
$row['meal_id'] = (int) $row['meal_id'];
|
||||
return $row;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user