1, 'name' => 'Bulgur', 'container' => 'glass-flip-med-tall', 'outOfStock' => false], ['id' => 2, 'name' => 'Wild Rice Flour', 'container' => 'glass-flip-med-squat', 'outOfStock' => false], ['id' => 3, 'name' => 'Self Rising Flour', 'container' => 'plastic-oxo-large', 'outOfStock' => false], ['id' => 4, 'name' => 'Whole Wheat Flour', 'container' => 'plastic-oxo-large', 'outOfStock' => false], ['id' => 5, 'name' => 'Cake Flour', 'container' => 'plastic-clamp-large', 'outOfStock' => false], ['id' => 6, 'name' => 'GF Pancake Mix', 'container' => 'original-package', 'outOfStock' => false], ['id' => 7, 'name' => 'Coconut Flour', 'container' => 'glass-flip-small', 'outOfStock' => false], ['id' => 8, 'name' => 'All Purpose Flour', 'container' => 'plastic-oxo-large', 'outOfStock' => false], ['id' => 9, 'name' => 'Bread Flour', 'container' => 'plastic-clamp-tall', 'outOfStock' => false], ['id' => 10, 'name' => 'Powdered Sugar', 'container' => 'plastic-flip', 'outOfStock' => false], ['id' => 11, 'name' => 'Linguine', 'container' => 'original-package', 'outOfStock' => false], ['id' => 12, 'name' => 'Fettuccine', 'container' => 'original-package', 'outOfStock' => false], ['id' => 13, 'name' => 'Penne Rigate', 'container' => 'original-package', 'outOfStock' => false], ['id' => 14, 'name' => 'Ancho Chile', 'container' => 'glass-flip-large', 'outOfStock' => false], ['id' => 15, 'name' => 'Chocolate Chips', 'container' => 'glass-canning-quart', 'outOfStock' => false], ['id' => 16, 'name' => 'Dried Mushrooms', 'container' => 'glass-canning-pint', 'outOfStock' => false], ['id' => 17, 'name' => 'Bay Leaves', 'container' => 'spice-hex-large', 'outOfStock' => false], ['id' => 18, 'name' => 'Dates', 'container' => 'glass-canning-quart', 'outOfStock' => false], ['id' => 19, 'name' => 'Xanthan Gum', 'container' => 'spice-flip-tiny', 'outOfStock' => false], ['id' => 20, 'name' => 'M.S.G.', 'container' => 'spice-hex-small', 'outOfStock' => false], ['id' => 21, 'name' => 'Potato Starch', 'container' => 'glass-flip-small', 'outOfStock' => false], ['id' => 22, 'name' => 'Potato Flour', 'container' => 'glass-flip-small', 'outOfStock' => false], ['id' => 23, 'name' => 'Garbanzo Flour', 'container' => 'glass-flip-med-squat', 'outOfStock' => false], ['id' => 24, 'name' => 'Mochi Rice Flour', 'container' => 'glass-flip-small', 'outOfStock' => false], ['id' => 25, 'name' => 'Couscous', 'container' => 'original-package', 'outOfStock' => false], ['id' => 26, 'name' => 'Granola', 'container' => 'original-package', 'outOfStock' => false], ['id' => 27, 'name' => 'Cheerios', 'container' => 'original-package', 'outOfStock' => false], ['id' => 28, 'name' => 'Baking Powder', 'container' => 'spice-hex-large', 'outOfStock' => false], ['id' => 29, 'name' => 'Corn Starch', 'container' => 'glass-canning-small', 'outOfStock' => false], ['id' => 30, 'name' => 'Cocoa Powder', 'container' => 'spice-hex-large', 'outOfStock' => false], ]; /** * Load inventory from file */ function loadInventory() { global $defaultInventory; if (file_exists(DATA_FILE)) { $content = file_get_contents(DATA_FILE); $data = json_decode($content, true); if ($data !== null) { return $data; } } // Initialize with default inventory saveInventory($defaultInventory); return $defaultInventory; } /** * Save inventory to file */ function saveInventory($inventory) { file_put_contents(DATA_FILE, json_encode($inventory, JSON_PRETTY_PRINT)); } /** * Check if PIN is set */ function hasPinSet() { return file_exists(PIN_FILE) && strlen(trim(file_get_contents(PIN_FILE))) === 4; } /** * Get stored PIN */ function getPin() { if (file_exists(PIN_FILE)) { return trim(file_get_contents(PIN_FILE)); } return null; } /** * Set PIN */ function setPin($pin) { file_put_contents(PIN_FILE, $pin); // Secure the file chmod(PIN_FILE, 0600); } /** * Verify PIN */ function verifyPin($pin) { $storedPin = getPin(); return $storedPin !== null && $storedPin === $pin; } /** * Get next available ID */ function getNextId($inventory) { if (empty($inventory)) { return 1; } $maxId = max(array_column($inventory, 'id')); return $maxId + 1; } /** * Sanitize string input */ function sanitize($str, $maxLength = 200) { $str = trim($str); if (strlen($str) > $maxLength) { $str = substr($str, 0, $maxLength); } return $str; } /** * Check if PIN is required and valid for write operations */ function requirePin($input) { // If no PIN is set yet, allow writes (first-time setup) if (!hasPinSet()) { return true; } // PIN is set, so require it for write operations $pin = $input['pin'] ?? ''; return verifyPin($pin); } // Main request handling $input = json_decode(file_get_contents('php://input'), true); if (!$input || !isset($input['action'])) { // Allow GET requests to view inventory if ($_SERVER['REQUEST_METHOD'] === 'GET') { $inventory = loadInventory(); echo json_encode([ 'success' => true, 'inventory' => $inventory, 'hasPin' => hasPinSet() ]); exit(); } echo json_encode(['error' => 'Invalid request']); exit(); } $action = $input['action']; switch ($action) { case 'get': $inventory = loadInventory(); echo json_encode([ 'success' => true, 'inventory' => $inventory, 'hasPin' => hasPinSet() ]); break; case 'setPin': $pin = $input['pin'] ?? ''; if (strlen($pin) !== 4 || !ctype_digit($pin)) { echo json_encode(['success' => false, 'error' => 'PIN must be 4 digits']); break; } // Only allow setting PIN if not already set if (hasPinSet()) { echo json_encode(['success' => false, 'error' => 'PIN already set']); break; } setPin($pin); echo json_encode(['success' => true]); break; case 'verifyPin': $pin = $input['pin'] ?? ''; $valid = verifyPin($pin); echo json_encode(['success' => $valid]); break; case 'add': if (!requirePin($input)) { echo json_encode(['success' => false, 'error' => 'Invalid PIN']); break; } $name = sanitize($input['name'] ?? ''); $container = sanitize($input['container'] ?? 'glass-bail-medium', 50); $category = sanitize($input['category'] ?? 'other', 50); $outOfStock = (bool)($input['outOfStock'] ?? false); if (empty($name)) { echo json_encode(['success' => false, 'error' => 'Name required']); break; } $inventory = loadInventory(); $newItem = [ 'id' => getNextId($inventory), 'name' => $name, 'container' => $container, 'category' => $category, 'outOfStock' => $outOfStock ]; $inventory[] = $newItem; saveInventory($inventory); echo json_encode(['success' => true, 'item' => $newItem]); break; case 'update': if (!requirePin($input)) { echo json_encode(['success' => false, 'error' => 'Invalid PIN']); break; } $id = (int)($input['id'] ?? 0); $name = sanitize($input['name'] ?? ''); $container = sanitize($input['container'] ?? '', 50); $category = sanitize($input['category'] ?? '', 50); $outOfStock = (bool)($input['outOfStock'] ?? false); if ($id <= 0) { echo json_encode(['success' => false, 'error' => 'Invalid ID']); break; } $inventory = loadInventory(); $found = false; foreach ($inventory as &$item) { if ($item['id'] === $id) { if (!empty($name)) $item['name'] = $name; if (!empty($container)) $item['container'] = $container; if (!empty($category)) $item['category'] = $category; $item['outOfStock'] = $outOfStock; $found = true; break; } } if (!$found) { echo json_encode(['success' => false, 'error' => 'Item not found']); break; } saveInventory($inventory); echo json_encode(['success' => true]); break; case 'delete': if (!requirePin($input)) { echo json_encode(['success' => false, 'error' => 'Invalid PIN']); break; } $id = (int)($input['id'] ?? 0); if ($id <= 0) { echo json_encode(['success' => false, 'error' => 'Invalid ID']); break; } $inventory = loadInventory(); $originalCount = count($inventory); $inventory = array_values(array_filter($inventory, fn($item) => $item['id'] !== $id)); if (count($inventory) === $originalCount) { echo json_encode(['success' => false, 'error' => 'Item not found']); break; } saveInventory($inventory); echo json_encode(['success' => true]); break; default: echo json_encode(['error' => 'Unknown action']); }