Add user registration/login, persistent likes, category hiding, and contact email

- User auth: register (nickname + password + email), login, logout with PHP sessions
- Persistent likes: logged-in users' likes saved to users.json, anonymous via session
- "Tykkäämäni" filter button next to search — filter liked posts, combinable with search
- Hide empty/sparse categories from filter buttons until posts exist
- Replace broken contact form with simple mailto link (info@tykkaa.fi)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 11:08:22 +02:00
parent f14913cb4b
commit 5dfbbacf39
4 changed files with 443 additions and 70 deletions

134
api.php
View File

@@ -52,6 +52,19 @@ function isAdmin(): bool {
return !empty($_SESSION['tykkaafi_admin']);
}
function isLoggedIn(): bool {
return !empty($_SESSION['tykkaafi_user_id']);
}
function getLoggedInUser(): ?array {
if (!isLoggedIn()) return null;
$users = readData('users.json', []);
foreach ($users as $u) {
if ($u['id'] === $_SESSION['tykkaafi_user_id']) return $u;
}
return null;
}
// ─── Oletusdata ────────────────────────────────────────────────
// Kategoriat jotka on poistettu — poistetaan automaattisesti live-serveriltä
const REMOVED_CATEGORIES = ['knitting', 'kasvit', 'matkustus', 'tips'];
@@ -691,26 +704,55 @@ switch ($action) {
ok(['categories' => getOrInitCategories()]);
case 'likes':
$likes = readData('likes.json', new stdClass());
$userLikes = $_SESSION['user_likes'] ?? [];
$likes = readData('likes.json', new stdClass());
$user = getLoggedInUser();
if ($user) {
$userLikes = $user['likes'] ?? [];
} else {
$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;
$likes = readData('likes.json', []);
$user = getLoggedInUser();
if ($user) {
$users = readData('users.json', []);
$userLikes = $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;
}
foreach ($users as &$u) {
if ($u['id'] === $user['id']) { $u['likes'] = array_values($userLikes); break; }
}
unset($u);
writeData('users.json', $users);
$_SESSION['user_likes'] = array_values($userLikes);
} else {
$likes[$postId] = max(0, ($likes[$postId] ?? 1) - 1);
array_splice($userLikes, $idx, 1);
$liked = false;
$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);
}
$_SESSION['user_likes'] = array_values($userLikes);
writeData('likes.json', $likes);
ok(['liked' => $liked, 'count' => $likes[$postId] ?? 0]);
@@ -801,6 +843,72 @@ switch ($action) {
case 'admin_check':
ok(['loggedIn' => isAdmin()]);
// ─── Käyttäjätunnukset ─────────────────────────────────────
case 'user_register':
$nickname = trim($body['nickname'] ?? '');
$email = trim($body['email'] ?? '');
$password = $body['password'] ?? '';
if (!$nickname || !$password) err('Nimimerkki ja salasana vaaditaan.');
if (mb_strlen($nickname) < 2 || mb_strlen($nickname) > 30) err('Nimimerkin tulee olla 230 merkkiä.');
if (mb_strlen($password) < 6) err('Salasanan tulee olla vähintään 6 merkkiä.');
if ($email && !filter_var($email, FILTER_VALIDATE_EMAIL)) err('Sähköpostiosoite ei kelpaa.');
$users = readData('users.json', []);
foreach ($users as $u) {
if (mb_strtolower($u['nickname']) === mb_strtolower($nickname)) {
err('Nimimerkki on jo käytössä.');
}
}
$user = [
'id' => 'user_' . time() . '_' . random_int(1000, 9999),
'nickname' => htmlspecialchars($nickname, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
'email' => htmlspecialchars($email, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
'password' => password_hash($password, PASSWORD_DEFAULT),
'likes' => $_SESSION['user_likes'] ?? [],
'created' => date('Y-m-d'),
];
$users[] = $user;
writeData('users.json', $users);
$_SESSION['tykkaafi_user_id'] = $user['id'];
ok(['user' => ['id' => $user['id'], 'nickname' => $user['nickname'], 'likes' => $user['likes']]]);
case 'user_login':
$nickname = trim($body['nickname'] ?? '');
$password = $body['password'] ?? '';
if (!$nickname || !$password) err('Nimimerkki ja salasana vaaditaan.');
$users = readData('users.json', []);
foreach ($users as $u) {
if (mb_strtolower($u['nickname']) === mb_strtolower($nickname)) {
if (password_verify($password, $u['password'])) {
$_SESSION['tykkaafi_user_id'] = $u['id'];
$_SESSION['user_likes'] = $u['likes'] ?? [];
ok(['user' => ['id' => $u['id'], 'nickname' => $u['nickname'], 'likes' => $u['likes'] ?? []]]);
}
err('Väärä salasana.');
}
}
err('Nimimerkkiä ei löydy.');
case 'user_logout':
unset($_SESSION['tykkaafi_user_id']);
ok();
case 'user_check':
$user = getLoggedInUser();
if ($user) {
ok(['loggedIn' => true, 'user' => [
'id' => $user['id'],
'nickname' => $user['nickname'],
'likes' => $user['likes'] ?? [],
]]);
}
ok(['loggedIn' => false]);
default:
err('Unknown action');
}