true], $data)); exit; } function err(string $msg, int $code = 400): void { http_response_code($code); echo json_encode(['error' => $msg]); exit; } function isAdmin(): bool { return !empty($_SESSION['tykkaafi_admin']); } // ─── Oletusdata ──────────────────────────────────────────────── function defaultCategories(): array { return [ ['id' => 'recipes', 'fi' => 'Reseptit', 'en' => 'Recipes', 'emoji' => '🍳'], ['id' => 'knitting', 'fi' => 'Neulominen', 'en' => 'Knitting', 'emoji' => '🧶'], ['id' => 'tips', 'fi' => 'Vinkit', 'en' => 'Tips', 'emoji' => '💡'], ]; } function defaultPosts(): array { return [ [ 'id' => 'pancakes', 'emoji' => '🥞', 'title' => 'Kuohkeat letut', 'category' => 'recipes', 'author' => 'Admin', 'time' => '20 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Kultaisia, voisia lettuja, jotka sulavat suuhun. Täydellisiä laiskaan sunnuntaiaamuun.', 'ingredients' => ['1½ dl vehnäjauhoja','3½ tl leivinjauhetta','1 tl suolaa','1 rkl sokeria','3 dl maitoa','1 muna','3 rkl sulatettua voita','Voita tai öljyä paistamiseen'], 'steps' => ['Sekoita kulhossa jauhot, leivinjauhe, suola ja sokeri.','Tee keskelle kuoppa ja kaada sekaan maito, muna ja sulatettu voi.','Sekoita tasaiseksi — pienet paakut ovat ok.','Kuumenna pannua keskilämmöllä.','Kaada noin ¼ dl taikinaa per lettu.','Paista kunnes pintaan nousee kuplia, käännä ja paista kullanruskeaksi.','Tarjoile vaahterasiirapilla ja marjoilla.'], ], [ 'id' => 'bolognese', 'emoji' => '🍝', 'title' => 'Klassinen spagetti bolognese', 'category' => 'recipes', 'author' => 'Admin', 'time' => '1 t 20 min', 'servings' => '6 annosta', 'type' => 'recipe', 'desc' => 'Runsas, hitaasti haudutettu lihamauste al dente -spagetin päällä. Ajaton italialainen klassikko.', 'ingredients' => ['500 g jauhelihaa','400 g spagettia','1 sipuli, hienonnettuna','3 valkosipulinkynttä','800 g murskattuja tomaatteja','2 dl punaviiniä','2 rkl tomaattipyrettä','Suolaa, pippuria, basilikaa, oreganoa','Parmesaanijuustoa tarjoiluun'], 'steps' => ['Kuullota sipuli ja valkosipuli oliiviöljyssä.','Ruskista jauheliha.','Lisää viini ja anna pelkistyä (5 min).','Lisää tomaatit ja mausteet.','Hauduta miedolla lämmöllä 1 tunti.','Keitä spagetti al denteksi.','Tarjoile parmesaanin kera.'], ], [ 'id' => 'cookies', 'emoji' => '🍪', 'title' => 'Suklaahippukeksit', 'category' => 'recipes', 'author' => 'Admin', 'time' => '30 min', 'servings' => '24 keksiä', 'type' => 'recipe', 'desc' => 'Sitkeä sisältä, rapea reunoilta — täydellinen kotitekoinen keksi.', 'ingredients' => ['5½ dl vehnäjauhoja','1 tl ruokasoodaa','1 tl suolaa','225 g pehmeää voita','1½ dl sokeria','1½ dl ruskeaa sokeria','2 munaa','2 tl vaniljauutetta','4 dl suklaahippuja'], 'steps' => ['Kuumenna uuni 190°C.','Sekoita jauhot, sooda ja suola.','Vatkaa voi ja sokerit kuohkeaksi.','Lisää munat ja vanilja.','Yhdistä aineet ja lisää suklaa.','Lusikoi pellille.','Paista 9–11 min kullanruskeaksi.'], ], [ 'id' => 'soup', 'emoji' => '🍲', 'title' => 'Täyttävä kasviskeitto', 'category' => 'recipes', 'author' => 'Admin', 'time' => '45 min', 'servings' => '4 annosta', 'type' => 'recipe', 'desc' => 'Lämmittävä kulhollinen paksuja kasviksia rikkaassa yrttiliemessä.', 'ingredients' => ['2 rkl oliiviöljyä','1 sipuli','3 valkosipulia','3 porkkanaa, siivuina','2 perunaa, kuutioina','1 kesäkurpitsa','400 g tomaattimurskaa','1½ l kasvislientä','Timjami, rosmariini, suola, pippuri'], 'steps' => ['Kuullota sipuli ja valkosipuli.','Lisää kasvikset ja sekoittele 5 min.','Kaada liemi ja tomaatit, lisää yrtit.','Hauduta 25 min.','Lisää kesäkurpitsa, keitä 10 min.','Mausta ja tarjoile.'], ], [ 'id' => 'knitting_scarf', 'emoji' => '🧶', 'title' => 'Helppo huivi aloittelijalle', 'category' => 'knitting', 'author' => 'Admin', 'type' => 'post', 'desc' => 'Neulo kaunis huivi muutamassa tunnissa — täydellinen ensimmäinen projekti!', 'body' => '

Tarvitset:

Ohje: Luo 20 silmukkaa. Neulo suoraan (edestakaisin oikein silmukoin) kunnes huivi on noin 150 cm pitkä. Päättele silmukat ja viimeistele päät. Valmis! 🎉

Vinkki: Paksuilla langoilla ja suurilla neuloilla saat huivista nopeasti valmiin, vaikka olisit aloittelija.

', ], [ 'id' => 'tip_morning', 'emoji' => '💡', 'title' => 'Rauhallinen aamurutiini', 'category' => 'tips', 'author' => 'Admin', 'type' => 'post', 'desc' => 'Pienet muutokset aamurutiiniin tekevät koko päivästä paremman.', 'body' => '

Kokeile näitä vinkkejä parempaan aamuun:

Pienet muutokset, iso vaikutus!

', ], ]; } function getOrInitPosts(): array { if (!file_exists(DATA_DIR . 'posts.json')) { $posts = defaultPosts(); writeData('posts.json', $posts); return $posts; } return readData('posts.json', []); } function getOrInitCategories(): array { if (!file_exists(DATA_DIR . 'categories.json')) { $cats = defaultCategories(); writeData('categories.json', $cats); return $cats; } return readData('categories.json', []); } // ─── Routing ─────────────────────────────────────────────────── switch ($action) { case 'posts': ok(['posts' => getOrInitPosts()]); case 'categories': ok(['categories' => getOrInitCategories()]); case 'likes': $likes = readData('likes.json', new stdClass()); $userLikes = $_SESSION['user_likes'] ?? []; ok(['likes' => $likes, 'userLikes' => $userLikes]); case 'toggle_like': $postId = $body['postId'] ?? ''; if (!$postId) err('Missing postId'); $likes = readData('likes.json', []); $userLikes = $_SESSION['user_likes'] ?? []; $idx = array_search($postId, $userLikes, true); if ($idx === false) { $likes[$postId] = ($likes[$postId] ?? 0) + 1; $userLikes[] = $postId; $liked = true; } else { $likes[$postId] = max(0, ($likes[$postId] ?? 1) - 1); array_splice($userLikes, $idx, 1); $liked = false; } $_SESSION['user_likes'] = array_values($userLikes); writeData('likes.json', $likes); ok(['liked' => $liked, 'count' => $likes[$postId] ?? 0]); case 'comments': $postId = $_GET['postId'] ?? ''; $comments = readData('comments.json', []); ok(['comments' => $comments[$postId] ?? []]); case 'add_comment': $postId = $body['postId'] ?? ''; $name = trim($body['name'] ?? ''); $text = trim($body['text'] ?? ''); if (!$postId || !$name || !$text) err('Missing fields'); $now = time(); $win = 10 * 60; $times = array_values(array_filter($_SESSION['comment_times'] ?? [], fn($t) => $now - $t < $win)); if (count($times) >= 3) err('Liian monta kommenttia. Odota hetki.'); $times[] = $now; $_SESSION['comment_times'] = $times; $comments = readData('comments.json', []); if (!isset($comments[$postId])) $comments[$postId] = []; $comment = [ 'name' => htmlspecialchars($name, ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'text' => htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'), 'time' => date('d.m.Y'), ]; $comments[$postId][] = $comment; writeData('comments.json', $comments); ok(['comment' => $comment]); case 'add_post': $post = $body['post'] ?? []; if (empty($post['title'])) err('Missing title'); $post['id'] = preg_replace('/[^a-z0-9_]/', '', strtolower($post['id'] ?? '')) ?: 'post_' . time(); $posts = getOrInitPosts(); $posts[] = $post; writeData('posts.json', $posts); ok(); case 'update_post': if (!isAdmin()) err('Unauthorized', 403); $post = $body['post'] ?? []; if (empty($post['id'])) err('Missing id'); $posts = getOrInitPosts(); foreach ($posts as &$p) { if ($p['id'] === $post['id']) { $p = $post; break; } } unset($p); writeData('posts.json', $posts); ok(); case 'delete_post': if (!isAdmin()) err('Unauthorized', 403); $id = $body['id'] ?? ''; if (!$id) err('Missing id'); $posts = array_values(array_filter(getOrInitPosts(), fn($p) => ($p['id'] ?? '') !== $id)); writeData('posts.json', $posts); ok(); case 'save_categories': if (!isAdmin()) err('Unauthorized', 403); writeData('categories.json', $body['categories'] ?? []); ok(); case 'admin_login': if (($body['password'] ?? '') === ADMIN_PASSWORD) { $_SESSION['tykkaafi_admin'] = true; ok(); } err('Väärä salasana'); case 'admin_logout': $_SESSION['tykkaafi_admin'] = false; ok(); case 'admin_check': ok(['loggedIn' => isAdmin()]); default: err('Unknown action'); }