added bootstar, font awsome, and the project is in a useable state, bit needs some manual setup.

This commit is contained in:
2026-06-13 23:11:54 +02:00
parent 4cf12c378e
commit 9045841645
5886 changed files with 538083 additions and 99 deletions

382
ProjectKiln/api/user.php Normal file
View File

@@ -0,0 +1,382 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../db.php';
require_once __DIR__ . '/../auth.php';
header('Content-Type: application/json; charset=utf-8');
const VALID_THEMES = ['white', 'dark', 'purple', 'green', 'beige'];
function jsonResponse(array $data, int $status = 200): void
{
http_response_code($status);
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
function getInputData(): array
{
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if (str_contains($contentType, 'application/json')) {
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
return is_array($data) ? $data : [];
}
return $_POST;
}
function pictureDataUri(?string $picture): ?string
{
if ($picture === null || $picture === '') {
return null;
}
$info = @getimagesizefromstring($picture);
$mime = is_array($info) && isset($info['mime']) ? $info['mime'] : 'image/png';
return 'data:' . $mime . ';base64,' . base64_encode($picture);
}
function uploadedPicture(): ?string
{
if (empty($_FILES['picture']) || !is_array($_FILES['picture'])) {
return null;
}
$file = $_FILES['picture'];
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
return null;
}
if (($file['error'] ?? UPLOAD_ERR_OK) !== UPLOAD_ERR_OK) {
jsonResponse([
'success' => false,
'error' => 'Could not upload profile picture.'
], 400);
}
if (($file['size'] ?? 0) > 10 * 1024 * 1024) {
jsonResponse([
'success' => false,
'error' => 'Profile picture must be 10 MB or smaller.'
], 400);
}
$tmpName = (string)($file['tmp_name'] ?? '');
$picture = $tmpName !== '' ? file_get_contents($tmpName) : false;
if ($picture === false) {
jsonResponse([
'success' => false,
'error' => 'Could not read profile picture.'
], 400);
}
$info = @getimagesizefromstring($picture);
$allowedTypes = [
IMAGETYPE_PNG,
IMAGETYPE_JPEG,
IMAGETYPE_WEBP,
IMAGETYPE_GIF
];
if (!is_array($info) || !in_array($info[2] ?? null, $allowedTypes, true)) {
jsonResponse([
'success' => false,
'error' => 'Profile picture must be a PNG, JPG, WEBP, or GIF image.'
], 400);
}
return $picture;
}
function getUserSetting(int $userId, string $settingName): ?string
{
$stmt = db()->prepare(
'SELECT setting_value
FROM user_settings
WHERE user_id = ?
AND setting_name = ?
LIMIT 1'
);
$stmt->execute([$userId, $settingName]);
$value = $stmt->fetchColumn();
return $value !== false ? (string)$value : null;
}
function setUserSetting(int $userId, string $settingName, string $settingValue): void
{
$stmt = db()->prepare(
'SELECT id
FROM user_settings
WHERE user_id = ?
AND setting_name = ?
LIMIT 1'
);
$stmt->execute([$userId, $settingName]);
$settingId = $stmt->fetchColumn();
if ($settingId) {
$updateStmt = db()->prepare(
'UPDATE user_settings
SET setting_value = ?
WHERE id = ?'
);
$updateStmt->execute([$settingValue, $settingId]);
return;
}
$insertStmt = db()->prepare(
'INSERT INTO user_settings (user_id, setting_name, setting_value)
VALUES (?, ?, ?)'
);
$insertStmt->execute([$userId, $settingName, $settingValue]);
}
$user = requireApiAuth();
$api = $_GET['api'] ?? '';
switch ($api) {
case 'ListUsers': {
$stmt = db()->query(
'SELECT id, name, email, picture
FROM users
ORDER BY name ASC, email ASC'
);
$users = array_map(static function (array $targetUser): array {
$targetUser['picture'] = pictureDataUri($targetUser['picture']);
$targetUser['id'] = (int)$targetUser['id'];
return $targetUser;
}, $stmt->fetchAll());
jsonResponse([
'success' => true,
'users' => $users
]);
}
case 'UserInfo': {
$userId = (int)($_GET['user_id'] ?? 0);
if ($userId <= 0) {
jsonResponse([
'success' => false,
'error' => 'Missing or invalid user_id.'
], 400);
}
$stmt = db()->prepare(
'SELECT id, name, email, picture
FROM users
WHERE id = ?
LIMIT 1'
);
$stmt->execute([$userId]);
$targetUser = $stmt->fetch();
if (!$targetUser) {
jsonResponse([
'success' => false,
'error' => 'User not found.'
], 404);
}
$targetUser['picture'] = pictureDataUri($targetUser['picture']);
$targetUser['settings'] = [
'theme' => getUserSetting((int)$targetUser['id'], 'theme') ?? 'dark'
];
jsonResponse([
'success' => true,
'user' => $targetUser
]);
}
case 'Edit': {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse([
'success' => false,
'error' => 'Edit requires POST.'
], 405);
}
$userId = (int)($_GET['user_id'] ?? 0);
if ($userId <= 0) {
jsonResponse([
'success' => false,
'error' => 'Missing or invalid user_id.'
], 400);
}
if ($userId !== (int)$user['id']) {
jsonResponse([
'success' => false,
'error' => 'You can only edit your own user.'
], 403);
}
$data = getInputData();
$fields = [];
$values = [];
if (array_key_exists('name', $data)) {
$name = trim((string)$data['name']);
if ($name === '') {
jsonResponse([
'success' => false,
'error' => 'Name cannot be empty.'
], 400);
}
if (strlen($name) > 128) {
jsonResponse([
'success' => false,
'error' => 'Name is too long.'
], 400);
}
$fields[] = 'name = ?';
$values[] = $name;
}
if (array_key_exists('email', $data)) {
$email = strtolower(trim((string)$data['email']));
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
jsonResponse([
'success' => false,
'error' => 'Invalid email.'
], 400);
}
if (strlen($email) > 128) {
jsonResponse([
'success' => false,
'error' => 'Email is too long.'
], 400);
}
$emailStmt = db()->prepare(
'SELECT id
FROM users
WHERE email = ?
AND id <> ?
LIMIT 1'
);
$emailStmt->execute([$email, $userId]);
if ($emailStmt->fetch()) {
jsonResponse([
'success' => false,
'error' => 'Email is already in use.'
], 400);
}
$fields[] = 'email = ?';
$values[] = $email;
}
if (array_key_exists('password', $data)) {
$password = (string)$data['password'];
if (strlen($password) < 8) {
jsonResponse([
'success' => false,
'error' => 'Password must be at least 8 characters.'
], 400);
}
$fields[] = 'passwd = ?';
$values[] = password_hash($password, PASSWORD_ARGON2ID);
}
$removePicture = ($data['remove_picture'] ?? '') === '1';
$picture = $removePicture ? null : uploadedPicture();
if ($picture !== null) {
$fields[] = 'picture = ?';
$values[] = $picture;
}
if ($removePicture) {
$fields[] = 'picture = NULL';
}
$theme = null;
if (array_key_exists('theme', $data)) {
$theme = strtolower(trim((string)$data['theme']));
if (!in_array($theme, VALID_THEMES, true)) {
jsonResponse([
'success' => false,
'error' => 'Theme must be valid.'
], 400);
}
}
if (empty($fields) && $theme === null) {
jsonResponse([
'success' => false,
'error' => 'No editable fields provided.'
], 400);
}
try {
if (!empty($fields)) {
$values[] = $userId;
$stmt = db()->prepare(
'UPDATE users
SET ' . implode(', ', $fields) . '
WHERE id = ?'
);
$stmt->execute($values);
}
if ($theme !== null) {
setUserSetting($userId, 'theme', $theme);
}
jsonResponse([
'success' => true,
'user_id' => $userId
]);
} catch (PDOException $e) {
jsonResponse([
'success' => false,
'error' => 'Database error.'
], 500);
}
}
default: {
jsonResponse([
'success' => false,
'error' => 'Unknown API action.'
], 404);
}
}