added bootstar, font awsome, and the project is in a useable state, bit needs some manual setup.
This commit is contained in:
382
ProjectKiln/api/user.php
Normal file
382
ProjectKiln/api/user.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user