5.4.0
Restructure New Journal entries Colour tabs update with wind
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// POST { id } -> deletes an entry the caller owns.
|
||||
require __DIR__ . '/../lib/bootstrap.php';
|
||||
require_method('POST');
|
||||
|
||||
$user = require_user();
|
||||
$body = json_body();
|
||||
$id = (int)($body['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) json_error('Invalid id.');
|
||||
|
||||
$stmt = db()->prepare('DELETE FROM journal_entries WHERE id = ? AND user_id = ?');
|
||||
$stmt->execute([$id, $user['id']]);
|
||||
if ($stmt->rowCount() === 0) {
|
||||
json_error('Entry not found.', 404);
|
||||
}
|
||||
|
||||
json_out(['ok' => true]);
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// GET -> { ok:true, entries: [{ id, date, endDate, note, profile, solarEnv, reading, locationName, lat, lon, createdAt }, ...] }, newest first.
|
||||
require __DIR__ . '/../lib/bootstrap.php';
|
||||
|
||||
$user = require_user();
|
||||
|
||||
$stmt = db()->prepare(
|
||||
'SELECT id, entry_date, end_date, note, profile, solar_env, reading, location_name, lat, lon, created_at
|
||||
FROM journal_entries
|
||||
WHERE user_id = ?
|
||||
ORDER BY entry_date DESC, id DESC'
|
||||
);
|
||||
$stmt->execute([$user['id']]);
|
||||
|
||||
$entries = array_map(fn($r) => [
|
||||
'id' => (int)$r['id'],
|
||||
'date' => $r['entry_date'],
|
||||
'endDate' => $r['end_date'],
|
||||
'note' => $r['note'],
|
||||
'profile' => $r['profile'],
|
||||
'solarEnv' => $r['solar_env'],
|
||||
'reading' => $r['reading'] !== null ? (float)$r['reading'] : null,
|
||||
'locationName' => $r['location_name'],
|
||||
'lat' => $r['lat'] !== null ? (float)$r['lat'] : null,
|
||||
'lon' => $r['lon'] !== null ? (float)$r['lon'] : null,
|
||||
'createdAt' => $r['created_at'],
|
||||
], $stmt->fetchAll());
|
||||
|
||||
json_out(['ok' => true, 'entries' => $entries]);
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
// POST { date, endDate?, note, id?, profile?, solarEnv?, reading?, locationName?, lat?, lon? }
|
||||
// -> creates a new entry, or updates one the caller owns when id is given.
|
||||
// { ok:true, id }.
|
||||
//
|
||||
// endDate is optional - a note can span a period (e.g. a holiday week)
|
||||
// rather than a single day. Omitted/equal-to-date means a single-day entry.
|
||||
//
|
||||
// profile/solarEnv/reading/locationName/lat/lon are an optional attached
|
||||
// "reading" - a computed felt-temperature number the browser derives
|
||||
// client-side from the live 14-day forecast (same compute.js the main app
|
||||
// uses, same peak-of-day rule the day tabs use, taken across the whole
|
||||
// range for a multi-day entry). The server never touches the physics; it
|
||||
// just stores whatever number the browser already computed, the same way
|
||||
// it stores the note text. profile AND solarEnv are both stored because
|
||||
// both change the number - the dashboard filters its entry list and graph
|
||||
// down to whichever combination is currently selected, so a Desert reading
|
||||
// never turns up mixed into a Beach trend.
|
||||
require __DIR__ . '/../lib/bootstrap.php';
|
||||
require_method('POST');
|
||||
|
||||
$user = require_user();
|
||||
$body = json_body();
|
||||
|
||||
$date = (string)($body['date'] ?? '');
|
||||
$note = trim((string)($body['note'] ?? ''));
|
||||
$id = isset($body['id']) ? (int)$body['id'] : null;
|
||||
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
||||
json_error('Invalid date.');
|
||||
}
|
||||
|
||||
$endDate = null;
|
||||
if (isset($body['endDate']) && $body['endDate'] !== null && $body['endDate'] !== '') {
|
||||
$endDate = (string)$body['endDate'];
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
|
||||
json_error('Invalid end date.');
|
||||
}
|
||||
if ($endDate < $date) json_error('End date must be on or after the start date.');
|
||||
if ($endDate === $date) $endDate = null; // same day - store as a single-day entry
|
||||
}
|
||||
|
||||
if ($note === '') {
|
||||
json_error('Note cannot be empty.');
|
||||
}
|
||||
if (strlen($note) > 4000) {
|
||||
json_error('Note is too long.');
|
||||
}
|
||||
|
||||
$profile = null;
|
||||
$solarEnv = null;
|
||||
$reading = null;
|
||||
$locationName = null;
|
||||
$lat = null;
|
||||
$lon = null;
|
||||
|
||||
if (isset($body['reading']) && $body['reading'] !== null && $body['reading'] !== '') {
|
||||
if (!is_numeric($body['reading'])) json_error('Invalid reading.');
|
||||
$reading = round((float)$body['reading'], 1);
|
||||
if ($reading < -100 || $reading > 150) json_error('Reading out of range.');
|
||||
|
||||
$profile = (string)($body['profile'] ?? '');
|
||||
if (!in_array($profile, ['basic', 'home', 'vehicle', 'pets'], true)) {
|
||||
json_error('Invalid profile.');
|
||||
}
|
||||
|
||||
$solarEnv = (string)($body['solarEnv'] ?? '');
|
||||
if (!in_array($solarEnv, ['open', 'urban', 'beach', 'river', 'forest', 'openwater', 'alpine', 'desert'], true)) {
|
||||
json_error('Invalid solar model.');
|
||||
}
|
||||
|
||||
$locationName = trim((string)($body['locationName'] ?? ''));
|
||||
if ($locationName === '' || strlen($locationName) > 255) json_error('Invalid location.');
|
||||
|
||||
if (!is_numeric($body['lat'] ?? null) || !is_numeric($body['lon'] ?? null)) {
|
||||
json_error('Invalid coordinates.');
|
||||
}
|
||||
$lat = round((float)$body['lat'], 5);
|
||||
$lon = round((float)$body['lon'], 5);
|
||||
if ($lat < -90 || $lat > 90 || $lon < -180 || $lon > 180) json_error('Invalid coordinates.');
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
// Ownership enforced in the WHERE clause, not a prior SELECT.
|
||||
$stmt = db()->prepare(
|
||||
'UPDATE journal_entries
|
||||
SET entry_date = ?, end_date = ?, note = ?, profile = ?, solar_env = ?, reading = ?, location_name = ?, lat = ?, lon = ?
|
||||
WHERE id = ? AND user_id = ?'
|
||||
);
|
||||
$stmt->execute([$date, $endDate, $note, $profile, $solarEnv, $reading, $locationName, $lat, $lon, $id, $user['id']]);
|
||||
if ($stmt->rowCount() === 0) {
|
||||
json_error('Entry not found.', 404);
|
||||
}
|
||||
json_out(['ok' => true, 'id' => $id]);
|
||||
}
|
||||
|
||||
$stmt = db()->prepare(
|
||||
'INSERT INTO journal_entries (user_id, entry_date, end_date, note, profile, solar_env, reading, location_name, lat, lon)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
);
|
||||
$stmt->execute([$user['id'], $date, $endDate, $note, $profile, $solarEnv, $reading, $locationName, $lat, $lon]);
|
||||
json_out(['ok' => true, 'id' => (int)db()->lastInsertId()]);
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// POST { email, password } -> signs the user in.
|
||||
require __DIR__ . '/../lib/bootstrap.php';
|
||||
require_method('POST');
|
||||
|
||||
$body = json_body();
|
||||
$email = strtolower(trim($body['email'] ?? ''));
|
||||
$password = (string)($body['password'] ?? '');
|
||||
|
||||
$stmt = db()->prepare('SELECT id, password_hash FROM users WHERE email = ?');
|
||||
$stmt->execute([$email]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
// Same message whether the email is unknown or the password is wrong -
|
||||
// don't let a login form confirm which emails have accounts.
|
||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
json_error('Incorrect email or password.', 401);
|
||||
}
|
||||
|
||||
issue_session((int)$user['id']);
|
||||
json_out(['ok' => true, 'email' => $email]);
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
require __DIR__ . '/../lib/bootstrap.php';
|
||||
require_method('POST');
|
||||
|
||||
clear_session();
|
||||
json_out(['ok' => true]);
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
// GET -> { ok:true, user: { email } } if signed in, else { ok:true, user:null }.
|
||||
// Always 200 - this is a status check, not an auth gate.
|
||||
require __DIR__ . '/../lib/bootstrap.php';
|
||||
|
||||
$user = current_user();
|
||||
json_out(['ok' => true, 'user' => $user ? ['email' => $user['email']] : null]);
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// POST { email, password } -> creates an account, signs the user in.
|
||||
require __DIR__ . '/../lib/bootstrap.php';
|
||||
require_method('POST');
|
||||
|
||||
$body = json_body();
|
||||
$email = strtolower(trim($body['email'] ?? ''));
|
||||
$password = (string)($body['password'] ?? '');
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
json_error('Enter a valid email address.');
|
||||
}
|
||||
if (strlen($password) < 8) {
|
||||
json_error('Password must be at least 8 characters.');
|
||||
}
|
||||
|
||||
$stmt = db()->prepare('SELECT id FROM users WHERE email = ?');
|
||||
$stmt->execute([$email]);
|
||||
if ($stmt->fetch()) {
|
||||
json_error('An account with that email already exists.', 409);
|
||||
}
|
||||
|
||||
$hash = password_hash($password, PASSWORD_ARGON2ID);
|
||||
$stmt = db()->prepare(
|
||||
'INSERT INTO users (public_id, email, password_hash) VALUES (?, ?, ?)'
|
||||
);
|
||||
$stmt->execute([new_public_id(), $email, $hash]);
|
||||
|
||||
issue_session((int)db()->lastInsertId());
|
||||
json_out(['ok' => true, 'email' => $email]);
|
||||
Reference in New Issue
Block a user