API- Recipe stuff

master
Eirik Th S 2021-08-12 00:25:59 +02:00
parent ae840610d8
commit e0f394a051
5 changed files with 336 additions and 15 deletions

View File

@ -22,6 +22,28 @@ function returns($content = 'Success', $code = 0){
die();
}
function returnsErr($alternate = ''){
global $db;
if($error = $db->error){
returns("Database error: ".$error, 1);
}
if($alternate != ''){
returns($alternate, 2);
}
returns("Error", 2);
}
function checkArgs($args){
foreach($args as $key => $arg){
if($arg == ""){
return $key;
}
}
return true;
}
function sanitize(): array{
global $_GET, $_POST;
$data = array();
@ -29,6 +51,7 @@ function sanitize(): array{
foreach([$_GET, $_POST] as $request){
if(!empty($request)){
foreach($request as $key => $value){
if(in_array($value, ['string', 'integer', 'number', 'boolean'])){ $value = ''; } // API DEFAULTS CLEAN
if(($data[$key] = filter($value)) === false){
print_r($value);
echo "Failed to sanitize: `".$key."`: ".$value." \t-\t type: ".gettype($value)."\n";

View File

@ -7,6 +7,9 @@ error_reporting(E_ALL);
$docs['general'][] = array("method"=>'GET', "href"=>"/", "name"=>"Nothing", "body"=>"{}");
$docs['recipe'][] = array("method"=>'GET', "href"=>"/recipe", "name"=>"Get all available recipes", "body"=>"{}");
$docs['recipe'][] = array("method"=>'POST', "href"=>"/recipe", "name"=>"Create recipe", "body"=>"{\n \"name\": \"string\",\n \"portions\": \"integer\",\n \"public\": \"boolean\" \n}");
$docs['recipe'][] = array("method"=>'POST', "href"=>"/recipe", "name"=>"Add item to recipe", "body"=>"{\n \"recipe_id\": \"integer\",\n \"name\": \"string\",\n \"price\": \"number\",\n \"amount\": \"integer\" \n}");
$docs['recipe'][] = array("method"=>'POST', "href"=>"/recipe", "name"=>"Edit recipe item", "body"=>"{\n \"recipe_id\": \"integer\",\n \"recipe_item_id\": \"integer\",\n \"newName\": \"string\",\n \"newPrice\": \"number\",\n \"newAmount\": \"integer\",\n \"newItem_id\": \"integer\" \n}");
$docs['recipe'][] = array("method"=>'POST', "href"=>"/recipe", "name"=>"Delete recipe item", "body"=>"{\n \"recipe_id\": \"integer\",\n \"del_item_id\": \"integer\",\n \"delName\": \"string\" \n}");
@ -61,7 +64,7 @@ function capitalizeFirst($input){
?>
<div class="accordion-item">
<h2 class="accordion-header" id="heading<?=$i;?>">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse<?=$i;?>" aria-expanded="true" aria-controls="collapse<?=$i;?>">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse<?=$i;?>" aria-expanded="true" aria-controls="collapse<?=$i;?>">
<div class="badge <?=$methodClass;?>" style="margin-right: 5px;"><?=$row['method'];?></div>
<div style="font-family: var(--bs-font-monospace); margin-right: 5px;"><?=$row['href'];?></div>
<div><strong><?=$row['name'];?></strong></div>
@ -95,6 +98,8 @@ function capitalizeFirst($input){
let formElem = $(ev.currentTarget);
let params = formElem.serializeArray();
formElem.find(".apiExecResponse").html("...")
let url = "."+params[0].value;
let body = JSON.parse( params[1].value );

View File

@ -17,14 +17,47 @@ switch ($group){
case "recipe":
include 'recipes.php';
if(!empty($_POST)){
// CREATE RECIPE
if(isset($data['name']) && $data['name'] != "string"){
if(isset($data['recipe_id'])){
if($rec = new Recipes($data['recipe_id'])){
if(isset($data['name'])){
$amount = 1;
$price = 0;
if(is_numeric($data['amount'])){
$amount = $data['amount'];
}
if(is_numeric($data['price'])){
$price = $data['price'];
}
if($rec->addItemToRecipe($data['name'], $amount, $price)){
returns();
}
}
elseif(isset($data['recipe_item_id'])){
if($rec->editRecipeItem($data['recipe_item_id'], $data['newName'], $data['newPrice'], $data['newAmount'], $data['newItem_id'])){
returns();
}
}
elseif(isset($data['del_item_id'], $data['delName'])){
if($rec->deleteRecipeItem($data['del_item_id'], $data['delName'])){
returns();
}
}
}
else {
returnsErr("Recipe might not be owned by user");
}
}
// CREATE RECIPE
if(isset($data['name'])){
if($newID = Recipes::createRecipe($data['name'], $data['portions'], $data['public'])){
returns("OK: ".$newID);
}
else {
returns($db->error, 1);
returnsErr();
}
}
}
@ -35,13 +68,10 @@ switch ($group){
break;
case "plan":
returns("Coming soon...");
returns("Coming soon... See /plan/do.php in the meantime.");
break;
default:
returns('Nothing to do');
}
returns('Failed.',2);
$docs['recipe'][] = array("method"=>'POST', "href"=>"/recipe", "name"=>"Create recipe", "body"=>"{\n \"name\": \"string\",\n \"public\": \"boolean\" \n}");
returnsErr('Failed.');

View File

@ -1,8 +1,6 @@
<?php
class Recipes {
// function __construct($recipeID = 0){
static function getAll(): array{
global $user_id, $db;
$return = array();
@ -11,9 +9,15 @@ class Recipes {
$res = $db->query($sql);
while($row = $res->fetch_assoc()){
$return[$row['recipe_id']] = $row;
unset($return[$row['recipe_id']]['author']);
if($row['author'] == $user_id){
$return[$row['recipe_id']]['owner'] = true;
}
// $return[$row['recipe_id']]['items'] = ;
$itemsRes = $db->query("SELECT * FROM recipe_item WHERE `recipe_id` = '$row[recipe_id]'");
while ($item = $itemsRes->fetch_assoc()){
$return[$row['recipe_id']]['items'][] = $item;
}
}
if(empty($return)){
@ -23,20 +27,107 @@ class Recipes {
return $return;
}
static function createRecipe($name, $portions = 1, $public = 0) {
static function createRecipe($name, $portions = 1, $public = 0): bool {
global $user_id, $db;
if($portions == null){
$portions = 1;
}
if(strlen($name) <= 200 && is_numeric($public)){
if(strlenBetween($name,2,200) && is_numeric($public)){
$createRecipeSQL = "INSERT INTO `recipe` (name, author, portions, public) VALUES ('$name', $user_id, $portions, $public);";
if($db->query($createRecipeSQL)){
return $db->insert_id;
}
else {
// file_put_contents('test.txt', $createRecipeSQL);
}
}
return false;
}
private $recipeId;
function __construct($recipeID = 0){
global $db, $user_id;
if(!is_numeric($recipeID)){ return false; }
$checkRecipeOwnerRes = $db->query("SELECT `recipe_id` FROM recipe WHERE `recipe_id` = '$recipeID' AND `author` = '$user_id' LIMIT 1");
if($checkRecipeOwnerRes->fetch_row()[0] == $recipeID){
$this->recipeId = $recipeID;
return true;
}
return false;
}
public function addItemToRecipe($name, $amount, $price = 0): bool{
global $db;
if((is_numeric($name) || (strlen($name) > 0 && strlen($name) < 255)) && $amount > 0 && $amount < 99){
$nextItemIdQue = $db->query('SELECT count(0)+1 FROM recipe_item WHERE `recipe_id` = '.$this->recipeId);
$nextItemId = $nextItemIdQue->fetch_row()[0];
if(is_numeric($name)){
$addItemSql = "INSERT INTO `recipe_item` (recipe_id, item_num, name, price, amount, item_id) VALUES (".verifyRecipeOwnerSQL($this->recipeId).", $nextItemId, '$name', $price, $amount, $name)";
}
else {
$addItemSql = "INSERT INTO `recipe_item` (recipe_id, item_num, name, price, amount) VALUES (".verifyRecipeOwnerSQL($this->recipeId).", $nextItemId, '$name', $price, $amount)";
}
if($db->query($addItemSql)){
return true;
}
}
return false;
}
public function editRecipeItem($recipe_item_id, $newName, $newPrice, $newAmount, $newItemId = null): bool{
global $db;
if($newName && strlenBetween($newName,0,255)){ $set[] = "`name` = '$newName'"; }
if(is_numeric($newPrice)){ $set[] = "`price` = '$newPrice'"; }
if($newAmount && $newAmount > 0 && $newAmount < 99){ $set[] = "`amount` = '$newAmount'"; }
if(is_numeric($newItemId)){ $set[] = "`item_id` = '$newItemId'"; }
if(!empty($set)){
$editItemSql = "UPDATE `recipe_item` SET ".implode(', ', $set)." WHERE `recipe_id` = $this->recipeId AND `recipe_item_id` = '$recipe_item_id';";
// file_put_contents('test.txt', $editItemSql);
if($db->query($editItemSql)){
return true;
}
}
return false;
}
public function deleteRecipeItem($recipe_item_id, $name): bool{
global $db;
$deleteItemSql = "DELETE FROM `recipe_item` WHERE recipe_id = $this->recipeId AND `recipe_item_id` = '$recipe_item_id' AND name LIKE '$name' LIMIT 1";
if($db->query($deleteItemSql) && $db->affected_rows > 0){
return true;
}
else {
returnsErr("No rows deleted");
}
// file_put_contents('test.txt', $deleteItemSql);
return false;
}
}
function verifyRecipeOwnerSQL($recipeId): string{
global $user_id;
return "(SELECT `recipe_id` FROM recipe WHERE `recipe_id` = '$recipeId' AND `author` = '$user_id' LIMIT 1)";
}
function strlenBetween($str, $min, $max): bool{
return strlen($str) > $min && strlen($str) <= $max;
}
// file_put_contents('test.txt', $addItemSql);

172
www/plan/recipe.js Normal file
View File

@ -0,0 +1,172 @@
/*jshint sub:true, esversion: 6, -W083 */
class Recipe {
constructor(jsonObj){
this.portionAmount = 1;
if(typeof jsonObj === "undefined"){
alert('create new store?');
}
else {
this.recipes = jsonObj.data;
}
}
getAccordionHtml(appendId){
let appendElem = $(appendId);
if(!appendElem.hasClass('accordion')){ appendElem.addClass('accordion'); }
for(const key in this.recipes){
const recipe = this.recipes[key];
let html = "<div class='accordion-item'>"+
" <h2 class='accordion-header' id='heading"+key+"'>"+
" <button class='accordion-button' type='button' data-bs-toggle='collapse' data-bs-target='#collapse"+key+"' aria-expanded='true' aria-controls='collapse"+key+"'>"+recipe.name+"</button>"+
" </h2>"+
" <div id='collapse"+key+"' class='accordion-collapse collapse' aria-labelledby='heading"+key+"' data-bs-parent='#addStoreRecipe'>"+
" <div class='accordion-body'>"+
" <ul class='list-group list-group-flush' id='recipeItems"+key+"' data-recipe-name='"+recipe.name+"' data-recipe-id='"+recipe['recipe_id']+"'>";
for(const itemKey in recipe.items){
const recipeItem = recipe.items[itemKey];
html += "<li class='list-group-item'>"+
"<div class='row'>"+
"<span class='col-1 number recipeItemAmount' data-amount='"+recipeItem.amount+"'>"+recipeItem.amount+"</span> "+
"<span class='col itemName'>"+recipeItem.name+"</span> "+
"<span class='col-3 price'>"+recipeItem.price+"</span> ";
if(recipe.owner) {
html += "<span class='col-2 editing gx-3' style='display: none;' data-itemid='"+recipeItem['recipe_item_id']+"'><img src='../icon/pencil-square.svg' class='ariaButton editRow' alt='Edit'><img src='../icon/x-circle.svg' class='ariaButton delRow' alt='Remove' style='filter: invert(28%) sepia(93%) saturate(1776%) hue-rotate(334deg) brightness(89%) contrast(94%);'></span> ";
}
html += "</div>"+
"</li>";
}
html += " </ul>"+
" <div class='row row-cols-2'><label >Portions:</label><div class='input-group portionAmountBtns'>"+
" <button class='btn btn-outline-danger' data-type='descend' type='button'>-</button>"+
" <input class='form-control' data-type='edit' type='number' value='"+this.portionAmount+"' min='1' max='99' aria-label='Amount of portions'>"+
" <button class='btn btn-outline-success' data-type='ascend' type='button'>+</button>"+
" </div></div>"+
" <button class='btn btn-primary m-1 addStoreContents'>Add as a store</button>";
if(recipe.owner){
html += " <button class='btn btn-secondary m-1 editList' data-recipeid='"+key+"'>Edit list...</button>";
}
html += " </div>"+
"</div>";
let htmlElem = $(html);
// MODIFY PORTIONS
htmlElem.find('.portionAmountBtns button, .portionAmountBtns input').on('click', portionEv=>{
let clickedElem = $(portionEv.currentTarget);
let inputElem = clickedElem.parent().find('input');
let amountNum = Number(inputElem.val());
switch (clickedElem.attr('data-type')){
case 'descend':
if(amountNum > 1){
amountNum--;
}
break;
case 'ascend':
if(amountNum < 99){
amountNum++;
}
break;
}
this.portionAmount = amountNum;
inputElem.val(amountNum);
inputElem.trigger('change');
});
htmlElem.find('.portionAmountBtns input').on('change', portionChangeEv => {
let amountModifier = Number($(portionChangeEv.currentTarget).val());
$("#recipeItems"+key+" li .number").each((k, portionVal)=>{
let elem = $(portionVal);
elem.html( Number(elem.attr('data-amount'))*amountModifier );
});
});
// SAVE AS A STORE
htmlElem.find('.addStoreContents').one('click', addStoreEv => {
let recipeList = $(addStoreEv.currentTarget).parent().find('ul');
let recipeItems = [];
recipeList.find("li").each((k, recipeItem)=> {
let elem = $(recipeItem);
recipeItems.push([elem.find('.itemName').html(), elem.find('.price').html(), elem.find('.recipeItemAmount').html()]);
});
this.saveToStore(recipeList.attr('data-recipe-name'), recipeItems);
addStoreModal.hide();
});
// TODO: EDIT RECIPE
htmlElem.find('.editList').css('cursor','pointer').one('click', editListEv => {
let accBody = $(editListEv.currentTarget).parent();
accBody.find('li .editing').show();
});
htmlElem.find(".ariaButton").off('keydown').on('keydown', function(e){
if(e.code === "Space" || e.code === "Enter"){
e.preventDefault();
$(this).trigger('click');
}
});
htmlElem.find('.editRow').on('click', ev=>{this.editRow(ev);});
htmlElem.find('.delRow').on('click', ev=>{this.editRow(ev, true);});
htmlElem.appendTo(appendElem);
}
}
editRow(event, doDelete){
doDelete = doDelete || false;
let eventElem = $(event.currentTarget);
let recipe_id = eventElem.parent().parent().parent().parent().attr('data-recipe-id');
let r_item_id = eventElem.parent().attr('data-itemid');
let item_name = eventElem.parent().parent().find('.itemName').html();
if(doDelete){
ajaxPost('/api/v1/recipe', { recipe_id: recipe_id, del_item_num: r_item_id, delName: item_name }).done(resp => {
// eventElem.remove();
eventElem.parent().parent().parent().hide();
});
}
else {
alert("Edit coming soon..."+"\nrecipeid: "+recipe_id+"\nitem_num: "+item_num);
}
}
saveToStore(storeName, recipeItems){
if(typeof Store !== "undefined" && typeof stores !== "undefined"){
if(!storeName){
storeName = 'Recipe';
}
else {
storeName = 'Recipe: '+storeName;
}
stores.push( new Store(storeName));
let storeKey = stores.length - 1;
return stores[storeKey].getStoreID().done(json => {
for (const recKey in recipeItems) {
const recItem = recipeItems[recKey];
stores[storeKey].addItem(recItem[0], recItem[1], recItem[2]);
}
});
}
console.error("Store class not initialized, or no stores-list found.");
return false;
}
}
function ajaxPost(url, params){
return $.post(url, params)
.done(resp => {
if(resp.status !== 0){
alert(resp.message);
return;
}
});
}