refactor namespaces, start work on sql

This commit is contained in:
Sky Johnson 2024-12-05 18:34:12 -06:00
parent 50b78f8131
commit 8b03b209dc
11 changed files with 228 additions and 201 deletions

View File

@ -1,23 +1,27 @@
/* /*
@BLOG ============================================================
Stats
============================================================
*/ */
DROP TABLE IF EXISTS blog; CREATE TABLE stats (
CREATE TABLE blog (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`author_id` INTEGER NOT NULL, `luck` INTEGER NOT NULL DEFAULT 0,
`title` TEXT NOT NULL, `armor` INTEGER NOT NULL DEFAULT 0,
`slug` TEXT NOT NULL UNIQUE, `focus` INTEGER NOT NULL DEFAULT 0,
`content` TEXT NOT NULL, `power` INTEGER NOT NULL DEFAULT 0,
`created` DATETIME DEFAULT CURRENT_TIMESTAMP, `resist` INTEGER NOT NULL DEFAULT 0,
`updated` DATETIME DEFAULT CURRENT_TIMESTAMP `accuracy` INTEGER NOT NULL DEFAULT 0,
); `ferocity` INTEGER NOT NULL DEFAULT 0,
CREATE INDEX idx_blog_author_id ON blog (`author_id`); `precision` INTEGER NOT NULL DEFAULT 0,
CREATE INDEX idx_blog_slug ON blog (`slug`); `toughness` INTEGER NOT NULL DEFAULT 0,
`penetration` INTEGER NOT NULL DEFAULT 0,
) STRICT;
/* /*
@CHARS ============================================================
Characters
============================================================
*/ */
DROP TABLE IF EXISTS characters;
CREATE TABLE characters ( CREATE TABLE characters (
`id` INTEGER PRIMARY KEY AUTOINCREMENT, `id` INTEGER PRIMARY KEY AUTOINCREMENT,
`user_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL,
@ -32,27 +36,14 @@ CREATE TABLE characters (
`m_mp` INTEGER NOT NULL DEFAULT 10, `m_mp` INTEGER NOT NULL DEFAULT 10,
`tp` INTEGER NOT NULL DEFAULT 1, `tp` INTEGER NOT NULL DEFAULT 1,
`m_tp` INTEGER NOT NULL DEFAULT 1, `m_tp` INTEGER NOT NULL DEFAULT 1,
`pow` INTEGER NOT NULL DEFAULT 0, -- Power `stats_id` INTEGER NOT NULL,
`acc` INTEGER NOT NULL DEFAULT 0, -- Accuracy
`pen` INTEGER NOT NULL DEFAULT 0, -- Penetration
`foc` INTEGER NOT NULL DEFAULT 0, -- Focus
`tou` INTEGER NOT NULL DEFAULT 0, -- Toughness
`arm` INTEGER NOT NULL DEFAULT 0, -- Armor
`res` INTEGER NOT NULL DEFAULT 0, -- Resist
`pre` INTEGER NOT NULL DEFAULT 0, -- Precision
`fer` INTEGER NOT NULL DEFAULT 0, -- Ferocity
`luck` INTEGER NOT NULL DEFAULT 0, -- Luck
`inv_slots` INTEGER NOT NULL DEFAULT 10, `inv_slots` INTEGER NOT NULL DEFAULT 10,
`att_points` INTEGER NOT NULL DEFAULT 0, `att_points` INTEGER NOT NULL DEFAULT 0,
`bio` TEXT DEFAULT '' `bio` TEXT DEFAULT ''
); );
CREATE INDEX idx_characters_user_id ON characters (`user_id`); CREATE INDEX idx_characters_user_id ON characters (`user_id`);
/* CREATE TABLE equipped_items (
@CHARGEAR
*/
DROP TABLE IF EXISTS char_gear;
CREATE TABLE char_gear (
`char_id` INTEGER NOT NULL, `char_id` INTEGER NOT NULL,
`head` INTEGER NOT NULL DEFAULT 0, `head` INTEGER NOT NULL DEFAULT 0,
`chest` INTEGER NOT NULL DEFAULT 0, `chest` INTEGER NOT NULL DEFAULT 0,
@ -63,31 +54,43 @@ CREATE TABLE char_gear (
`rune` INTEGER NOT NULL DEFAULT 0, `rune` INTEGER NOT NULL DEFAULT 0,
`ring` INTEGER NOT NULL DEFAULT 0, `ring` INTEGER NOT NULL DEFAULT 0,
`amulet` INTEGER NOT NULL DEFAULT 0, `amulet` INTEGER NOT NULL DEFAULT 0,
`pow` INTEGER NOT NULL DEFAULT 0, -- Power `stats_id` INTEGER NOT NULL,
`acc` INTEGER NOT NULL DEFAULT 0, -- Accuracy
`pen` INTEGER NOT NULL DEFAULT 0, -- Penetration
`foc` INTEGER NOT NULL DEFAULT 0, -- Focus
`tou` INTEGER NOT NULL DEFAULT 0, -- Toughness
`arm` INTEGER NOT NULL DEFAULT 0, -- Armor
`res` INTEGER NOT NULL DEFAULT 0, -- Resist
`pre` INTEGER NOT NULL DEFAULT 0, -- Precision
`fer` INTEGER NOT NULL DEFAULT 0, -- Ferocity
`luck` INTEGER NOT NULL DEFAULT 0, -- Luck
`max_hp` INTEGER NOT NULL DEFAULT 0, `max_hp` INTEGER NOT NULL DEFAULT 0,
`max_mp` INTEGER NOT NULL DEFAULT 0 `max_mp` INTEGER NOT NULL DEFAULT 0
); );
CREATE INDEX idx_char_gear_char_id ON char_gear (`char_id`); CREATE INDEX idx_char_gear_char_id ON char_gear (`char_id`);
/* CREATE TABLE inventory_items (
@CHARINV
*/
DROP TABLE IF EXISTS char_inventory;
CREATE TABLE char_inventory (
`char_id` INTEGER NOT NULL, `char_id` INTEGER NOT NULL,
`item_id` INTEGER NOT NULL `item_id` INTEGER NOT NULL
); );
CREATE INDEX idx_inventory_char_id ON char_inventory (`char_id`); CREATE INDEX idx_inventory_char_id ON char_inventory (`char_id`);
-- Wallets are account-bound rather than character-bound. Should I move this to auth?
CREATE TABLE wallets (
`user_id` INTEGER NOT NULL,
`silver` INTEGER NOT NULL DEFAULT 10,
`stargem` INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX idx_wallets_user_id ON wallets (`user_id`);
/*
============================================================
Blog
============================================================
*/
create table blog (
`id` integer primary KEY AUTOINCREMENT,
`author_id` integer not null,
`title` TEXT not null,
`slug` TEXT not null unique,
`content` TEXT not null,
`created` DATETIME default current_timestamp,
`updated` DATETIME default current_timestamp
);
CREATE INDEX idx_blog_author_id ON blog (`author_id`);
CREATE INDEX idx_blog_slug ON blog (`slug`);
/* /*
@WALLETS @WALLETS
*/ */

View File

@ -1,7 +1,11 @@
<?php <?php
use Models\Character;
/* /*
Setup ============================================================
Bootstrapping
============================================================
*/ */
define('SRC', __DIR__ . '/../src'); define('SRC', __DIR__ . '/../src');
define('DATABASE_PATH', __DIR__ . '/../database'); define('DATABASE_PATH', __DIR__ . '/../database');
@ -10,7 +14,9 @@ require_once SRC . '/bootstrap.php';
$r = new Router; $r = new Router;
/* /*
============================================================
Home Home
============================================================
*/ */
$r->get('/', function() { $r->get('/', function() {
if (user()) redirect('/world'); if (user()) redirect('/world');
@ -18,73 +24,23 @@ $r->get('/', function() {
}); });
/* /*
============================================================
Auth Auth
============================================================
*/ */
$r->get('/register', 'Actions\Auth::register_get')->middleware('guest_only'); $r->get('/register', 'Actions\Auth::register_get')->middleware('guest_only');
$r->post('/register', 'Actions\Auth::register_post')->middleware('guest_only'); $r->post('/register', 'Actions\Auth::register_post')->middleware('guest_only');
$r->get('/login', function() { $r->get('/login', 'Actions\Auth::login_get')->middleware('guest_only');
echo render('layouts/basic', ['view' => 'pages/auth/login']); $r->post('/login', 'Actions\Auth::login_post')->middleware('guest_only');
})->middleware('guest_only');
$r->post('/login', function() { $r->post('/logout', 'Actions\Auth::logout')->middleware('auth_only');
$errors = []; if (env('debug', false)) $r->get('/debug/logout', 'Actions\Auth::logout');
$u = trim($_POST['u'] ?? '');
$p = $_POST['p'] ?? '';
if (empty($u)) $errors['u'][] = 'Username is required.';
if (empty($p)) $errors['p'][] = 'Password is required.';
// If there are errors at this point, send them to the page with errors flashed.
if (!empty($errors)) {
$GLOBALS['form-errors'] = $errors;
echo render('layouts/basic', ['view' => 'pages/auth/login']);
exit;
}
$user = User::find($u);
if ($user === false || !$user->check_password($p)) {
$errors['x'][] = 'Invalid username or password.';
$GLOBALS['form-errors'] = $errors;
echo render('layouts/basic', ['view' => 'pages/auth/login']);
exit;
}
$_SESSION['user'] = serialize($user);
if ($_POST['remember'] ?? false) {
$session = Session::create($user->id, strtotime('+30 days'));
if ($session === false) error_response(400);
set_cookie('remember_me', $session->token, $session->expires);
}
if ($user->char_count() === 0) {
redirect('/character/create-first');
} elseif (!change_user_character($user->char_id)) {
echo "failed to change user character (aclp)";
error_response(999);
}
redirect('/');
})->middleware('guest_only');
$r->post('/logout', function() {
Session::delete(user()->id);
unset($_SESSION['user']);
set_cookie('remember_me', '', 1);
redirect('/');
});
$r->get('/debug/logout', function() {
Session::delete(user()->id);
unset($_SESSION['user']);
set_cookie('remember_me', '', 1);
redirect('/');
});
/* /*
============================================================
Characters Characters
============================================================
*/ */
$r->get('/characters', function() { $r->get('/characters', function() {
//echo page('chars/list', ['chars' => user()->char_list()]); //echo page('chars/list', ['chars' => user()->char_list()]);
@ -220,7 +176,9 @@ $r->post('/character/delete', function() {
})->middleware('must_have_character'); })->middleware('must_have_character');
/* /*
============================================================
World World
============================================================
*/ */
$r->get('/world', function() { $r->get('/world', function() {
echo render('layouts/game'); echo render('layouts/game');
@ -270,14 +228,18 @@ $r->post('/move', function() {
})->middleware('ajax_only')->middleware('must_have_character'); })->middleware('ajax_only')->middleware('must_have_character');
/* /*
UI ============================================================
UI Components
============================================================
*/ */
$r->get('/ui/stats', function() { $r->get('/ui/stats', function() {
echo c_profile_stats(char()); echo c_profile_stats(char());
})->middleware('ajax_only')->middleware('must_have_character'); })->middleware('ajax_only')->middleware('must_have_character');
/* /*
============================================================
Router Router
============================================================
*/ */
// [code, handler, params, middleware] // [code, handler, params, middleware]
$l = $r->lookup($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']); $l = $r->lookup($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
@ -287,6 +249,8 @@ if (!empty($l['middleware'])) foreach ($l['middleware'] as $middleware) $middlew
$l['handler'](...$l['params'] ?? []); $l['handler'](...$l['params'] ?? []);
/* /*
============================================================
Cleanup Cleanup
============================================================
*/ */
clear_flashes(); clear_flashes();

View File

@ -2,10 +2,17 @@
namespace Actions; namespace Actions;
use \User; use Models\Session;
use Models\User;
use Models\Wallet;
class Auth class Auth
{ {
/*
============================================================
Registration
============================================================
*/
public static function register_get(): void public static function register_get(): void
{ {
echo render('layouts/basic', ['view' => 'pages/auth/register']); echo render('layouts/basic', ['view' => 'pages/auth/register']);
@ -67,10 +74,76 @@ class Auth
exit; exit;
} }
if (\User::create($u, $e, $p) === false) error_response(400); if (User::create($u, $e, $p) === false) error_response(400);
$_SESSION['user'] = serialize(\User::find($u)); $_SESSION['user'] = serialize(User::find($u));
\Wallet::create(user()->id); Wallet::create(user()->id);
redirect('/character/create-first'); redirect('/character/create-first');
} }
/*
============================================================
Login
============================================================
*/
public static function login_get(): void
{
echo render('layouts/basic', ['view' => 'pages/auth/login']);
}
public static function login_post(): void
{
$errors = [];
$u = trim($_POST['u'] ?? '');
$p = $_POST['p'] ?? '';
if (empty($u)) $errors['u'][] = 'Username is required.';
if (empty($p)) $errors['p'][] = 'Password is required.';
// If there are errors at this point, send them to the page with errors flashed.
if (!empty($errors)) {
$GLOBALS['form-errors'] = $errors;
echo render('layouts/basic', ['view' => 'pages/auth/login']);
exit;
}
$user = User::find($u);
if ($user === false || !$user->check_password($p)) {
$errors['x'][] = 'Invalid username or password.';
$GLOBALS['form-errors'] = $errors;
echo render('layouts/basic', ['view' => 'pages/auth/login']);
exit;
}
$_SESSION['user'] = serialize($user);
if ($_POST['remember'] ?? false) {
$session = Session::create($user->id, strtotime('+30 days'));
if ($session === false) error_response(400);
set_cookie('remember_me', $session->token, $session->expires);
}
if ($user->char_count() === 0) {
redirect('/character/create-first');
} elseif (!change_user_character($user->char_id)) {
echo "failed to change user character (aclp)";
error_response(999);
}
redirect('/');
}
/*
============================================================
Logout
============================================================
*/
public static function logout(): void
{
Session::delete(user()->id);
unset($_SESSION['user']);
set_cookie('remember_me', '', 1);
redirect('/');
}
} }

View File

@ -26,11 +26,11 @@ session_start();
define('CLASS_MAP', [ define('CLASS_MAP', [
'Database' => '/database.php', 'Database' => '/database.php',
'Router' => '/router.php', 'Router' => '/router.php',
'User' => '/models/user.php',
'Character' => '/models/character.php',
'Wallet' => '/models/wallet.php',
'Session' => '/models/session.php',
'Actions\Auth' => '/actions/auth.php', 'Actions\Auth' => '/actions/auth.php',
'Models\User' => '/models/user.php',
'Models\Character' => '/models/character.php',
'Models\Wallet' => '/models/wallet.php',
'Models\Session' => '/models/session.php',
]); ]);
spl_autoload_register(function (string $class) { spl_autoload_register(function (string $class) {
@ -58,12 +58,11 @@ if (env('debug', false)) {
CSRF CSRF
============================================================ ============================================================
*/ */
csrf(); // generate a CSRF token, or retrieve the current token csrf();
// error any request that fails CSRF on these methods
if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])) { if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])) {
$csrf = $_POST['csrf'] ?? $_SERVER['HTTP_X_CSRF'] ?? ''; $csrf = $_POST['csrf'] ?? $_SERVER['HTTP_X_CSRF'] ?? ''; // look for CSRF in AJAX requests
if (!hash_equals($_SESSION['csrf'] ?? '', $csrf)) error_response(418); if (!hash_equals($_SESSION['csrf'] ?? '', $csrf)) error_response(418); // I'm a Teapot
} }
/* /*
@ -71,9 +70,10 @@ if (in_array($_SERVER['REQUEST_METHOD'], ['POST', 'PUT', 'PATCH', 'DELETE'])) {
Global State Global State
============================================================ ============================================================
*/ */
$GLOBALS['databases'] = []; // database interfaces $GLOBALS['databases'] = [];
// all relevant state to handling requests
$GLOBALS['state'] = [ $GLOBALS['state'] = [
'logged_in' => isset($_SESSION['user']) || validate_session() 'logged_in' => isset($_SESSION['user']) || validate_session(),
'user' => null, // populated by user()
'char' => null, // user's selected character, populated by char()
'wallet' => null, // populated by wallet()
]; ];

View File

@ -1,5 +1,7 @@
<?php <?php
use Models\Character;
/** /**
* Render the logout button's form. * Render the logout button's form.
*/ */

View File

@ -23,11 +23,7 @@ class Database extends SQLite3
$p = strpos($query, '?') !== false; $p = strpos($query, '?') !== false;
$stmt = $this->prepare($query); $stmt = $this->prepare($query);
if (!empty($params)) { foreach ($params ?? [] as $k => $v) $stmt->bindValue($p ? $k + 1 : $k, $v, $this->getSQLiteType($v));
foreach ($params as $k => $v) {
$stmt->bindValue($p ? $k + 1 : $k, $v, $this->getSQLiteType($v));
}
}
$start = microtime(true); $start = microtime(true);
$r = $stmt->execute(); $r = $stmt->execute();
@ -61,9 +57,7 @@ class Database extends SQLite3
$this->count++; $this->count++;
$this->query_time += $time_taken; $this->query_time += $time_taken;
if (env('debug', false)) { if (env('debug', false)) $this->log[] = [$query, $time_taken];
$this->log[] = [$query, $time_taken];
}
} }
private function getSQLiteType(mixed $value): int private function getSQLiteType(mixed $value): int

View File

@ -1,5 +1,7 @@
<?php <?php
use Models\Character;
/** /**
* Load the environment variables from the .env file. * Load the environment variables from the .env file.
*/ */
@ -170,7 +172,7 @@ function set_cookie($name, $value, $expires)
/** /**
* Get the current user's object from SESSION if it exists. * Get the current user's object from SESSION if it exists.
*/ */
function user(): User|false function user(): Models\User|false
{ {
if (empty($_SESSION['user'])) return false; if (empty($_SESSION['user'])) return false;
return $GLOBALS['state']['user'] ??= unserialize($_SESSION['user']); return $GLOBALS['state']['user'] ??= unserialize($_SESSION['user']);
@ -195,8 +197,7 @@ function modify_user_session(string $field, mixed $value): bool
function char(): Character|false function char(): Character|false
{ {
if (empty($_SESSION['user'])) return false; if (empty($_SESSION['user'])) return false;
if (empty($GLOBALS['char'])) $GLOBALS['char'] = user()->current_char(); return $GLOBALS['state']['char'] ??= Character::find(user()->char_id);
return $GLOBALS['char'];
} }
/** /**
@ -233,12 +234,10 @@ function percent(float $num, float $denom, int $precision = 4): float
* the data is up to date with every request without having to query the database every use within, for example, a * the data is up to date with every request without having to query the database every use within, for example, a
* template. Will return false if the user or wallet does not exist. * template. Will return false if the user or wallet does not exist.
*/ */
function wallet(): Wallet|false function wallet(): Models\Wallet|false
{ {
if (user() === false) return false; if (user() === false) return false;
if (empty($GLOBALS['wallet'])) $w = user()->wallet(); return $GLOBALS['state']['wallet'] = Models\Wallet::find(user()->id);
if ($w === false) return false;
return $GLOBALS['wallet'] = $w;
} }
/** /**
@ -396,7 +395,7 @@ function db_fetch_array(SQLite3Result $result, int $mode = SQLITE3_ASSOC): array
function validate_session(): bool function validate_session(): bool
{ {
if (!isset($_COOKIE['remember_me'])) return false; if (!isset($_COOKIE['remember_me'])) return false;
if (($session = Session::find($_COOKIE['remember_me'])) && $session->validate()) return true; if (($session = Models\Session::find($_COOKIE['remember_me'])) && $session->validate()) return true;
return false; return false;
} }

View File

@ -1,5 +1,7 @@
<?php <?php
namespace Models;
/* /*
Characters are the living, breathing entities that interact with the game world. They are inextricably linked to their Characters are the living, breathing entities that interact with the game world. They are inextricably linked to their
accounts, and are the primary means by which the character interacts with the game world. Separating the character from accounts, and are the primary means by which the character interacts with the game world. Separating the character from
@ -74,7 +76,7 @@ class Character
"SELECT * FROM characters WHERE id = :id OR name = :id COLLATE NOCASE", "SELECT * FROM characters WHERE id = :id OR name = :id COLLATE NOCASE",
[':id' => $id] [':id' => $id]
); );
if ($q === false) throw new Exception('Failed to query character. (C::f)'); // badly formed query if ($q === false) throw new \Exception('Failed to query character. (C::f)'); // badly formed query
return ($c = $q->fetchArray(SQLITE3_ASSOC)) === false ? false : new Character($c); return ($c = $q->fetchArray(SQLITE3_ASSOC)) === false ? false : new Character($c);
} }
@ -99,7 +101,7 @@ class Character
// Create the character! // Create the character!
if (live_db()->query("INSERT INTO characters ($f) VALUES ($v)", $data) === false) { if (live_db()->query("INSERT INTO characters ($f) VALUES ($v)", $data) === false) {
// @TODO: Log this error // @TODO: Log this error
throw new Exception('Failed to create character. (cc)'); throw new \Exception('Failed to create character. (cc)');
} }
// Get the character ID // Get the character ID
@ -153,7 +155,7 @@ class Character
"SELECT 1 FROM characters WHERE id = :i AND user_id = :u LIMIT 1", "SELECT 1 FROM characters WHERE id = :i AND user_id = :u LIMIT 1",
[':i' => $id, ':u' => $user_id] [':i' => $id, ':u' => $user_id]
); );
if ($q === false) throw new Exception('Failed to query char ownership. (C::bt)'); if ($q === false) throw new \Exception('Failed to query char ownership. (C::bt)');
return $q->fetchArray(SQLITE3_ASSOC) !== false; return $q->fetchArray(SQLITE3_ASSOC) !== false;
} }
@ -186,7 +188,7 @@ class Character
{ {
// Delete the character // Delete the character
if (live_db()->query("DELETE FROM characters WHERE id = :p", [':p' => $id]) === false) { if (live_db()->query("DELETE FROM characters WHERE id = :p", [':p' => $id]) === false) {
throw new Exception('Failed to delete character. (C::d)'); throw new \Exception('Failed to delete character. (C::d)');
} }
} }
@ -203,7 +205,7 @@ class Character
'SELECT awarded FROM owned_titles WHERE char_id = ? AND title_id = ? LIMIT 1', 'SELECT awarded FROM owned_titles WHERE char_id = ? AND title_id = ? LIMIT 1',
[$this->id, $this->title_id] [$this->id, $this->title_id]
); );
if ($q === false) throw new Exception('Failed to query title. (C::t)'); if ($q === false) throw new \Exception('Failed to query title. (C::t)');
$a = $q->fetchArray(SQLITE3_ASSOC); $a = $q->fetchArray(SQLITE3_ASSOC);
if ($a === false) return false; if ($a === false) return false;

View File

@ -1,5 +1,7 @@
<?php <?php
namespace Models;
class Session class Session
{ {
public function __construct( public function __construct(
@ -29,7 +31,7 @@ class Session
return new Session($session['user_id'], $session['token'], $session['expires']); return new Session($session['user_id'], $session['token'], $session['expires']);
} }
public static function delete(int $user_id): SQLite3Result|false public static function delete(int $user_id): \SQLite3Result|false
{ {
return auth_db()->query("DELETE FROM sessions WHERE user_id = :u", [':u' => $user_id]); return auth_db()->query("DELETE FROM sessions WHERE user_id = :u", [':u' => $user_id]);
} }

View File

@ -1,5 +1,7 @@
<?php <?php
namespace Models;
/** /**
* Representation of a User from the auth database. Contains auth-related info and handles meta-level state. * Representation of a User from the auth database. Contains auth-related info and handles meta-level state.
*/ */
@ -43,12 +45,12 @@ class User
/** /**
* When this account was created (registered date). * When this account was created (registered date).
*/ */
public DateTime $created; public int $created;
/** /**
* When the account was last logged in to. * When the account was last logged in to.
*/ */
public DateTime $last_login; public int $last_login;
/** /**
* Populate a User object with data; assumes you are passing the associatve array from SQLite directly. * Populate a User object with data; assumes you are passing the associatve array from SQLite directly.
@ -57,7 +59,7 @@ class User
{ {
foreach ($data as $k => $v) { foreach ($data as $k => $v) {
if (property_exists($this, $k)) { if (property_exists($this, $k)) {
$this->$k = in_array($k, ['created', 'last_login']) ? new DateTime($v) : $v; $this->$k = in_array($k, ['created', 'last_login']) ? strtotime($v) : $v;
} }
} }
} }
@ -72,7 +74,7 @@ class User
"SELECT * FROM users WHERE username = :i COLLATE NOCASE OR email = :i COLLATE NOCASE OR id = :i LIMIT 1", "SELECT * FROM users WHERE username = :i COLLATE NOCASE OR email = :i COLLATE NOCASE OR id = :i LIMIT 1",
[':i' => $identifier] [':i' => $identifier]
); );
if ($r === false) throw new Exception("Failed to query user. (U::f)"); // badly formed query if ($r === false) throw new \Exception("Failed to query user. (U::f)"); // badly formed query
$u = $r->fetchArray(SQLITE3_ASSOC); $u = $r->fetchArray(SQLITE3_ASSOC);
if ($u === false) return false; // no user found if ($u === false) return false; // no user found
return new User($u); return new User($u);
@ -83,7 +85,7 @@ class User
* of the username or password passed to it; that is the responsibility of the caller. Returns false on * of the username or password passed to it; that is the responsibility of the caller. Returns false on
* failure. * failure.
*/ */
public static function create(string $username, string $email, string $password, int $auth = 0): SQLite3Result|false public static function create(string $username, string $email, string $password, int $auth = 0): \SQLite3Result|false
{ {
return auth_db()->query("INSERT INTO users (username, email, password, auth) VALUES (:u, :e, :p, :a)", [ return auth_db()->query("INSERT INTO users (username, email, password, auth) VALUES (:u, :e, :p, :a)", [
':u' => $username, ':u' => $username,
@ -120,7 +122,7 @@ class User
/** /**
* Delete a user by their username, email, or id. * Delete a user by their username, email, or id.
*/ */
public static function delete(string|int $identifier): SQLite3Result|false public static function delete(string|int $identifier): \SQLite3Result|false
{ {
return auth_db()->query( return auth_db()->query(
"DELETE FROM users WHERE username = :i OR email = :i OR id = :i", "DELETE FROM users WHERE username = :i OR email = :i OR id = :i",
@ -137,7 +139,7 @@ class User
"SELECT COUNT(*) FROM characters WHERE user_id = :u", "SELECT COUNT(*) FROM characters WHERE user_id = :u",
[':u' => $this->id] [':u' => $this->id]
)->fetchArray(SQLITE3_NUM); )->fetchArray(SQLITE3_NUM);
if ($c === false) throw new Exception('Failed to count characters. (U::cc)'); if ($c === false) throw new \Exception('Failed to count characters. (U::cc)');
return (int) $c[0]; return (int) $c[0];
} }
@ -148,7 +150,7 @@ class User
public function char_list(): array|false public function char_list(): array|false
{ {
$q = live_db()->query("SELECT id, name, level FROM characters WHERE user_id = ?", [$this->id]); $q = live_db()->query("SELECT id, name, level FROM characters WHERE user_id = ?", [$this->id]);
if ($q === false) throw new Exception('Failed to list characters. (U->cl)'); if ($q === false) throw new \Exception('Failed to list characters. (U->cl)');
$c = []; $c = [];
while ($row = $q->fetchArray(SQLITE3_ASSOC)) { while ($row = $q->fetchArray(SQLITE3_ASSOC)) {
@ -158,20 +160,4 @@ class User
// return false if no characters // return false if no characters
return empty($c) ? false : $c; return empty($c) ? false : $c;
} }
/**
* Get the user's current Character.
*/
public function current_char(): Character|false
{
return Character::find($this->char_id);
}
/**
* Get the user's wallet.
*/
public function wallet(): Wallet|false
{
return Wallet::find($this->id);
}
} }

View File

@ -1,5 +1,7 @@
<?php <?php
namespace Models;
class Wallet class Wallet
{ {
public function __construct( public function __construct(
@ -11,13 +13,13 @@ class Wallet
public static function find(int $user_id): Wallet|false public static function find(int $user_id): Wallet|false
{ {
$r = live_db()->query('SELECT * FROM wallets WHERE user_id = ?', [$user_id]); $r = live_db()->query('SELECT * FROM wallets WHERE user_id = ?', [$user_id]);
if ($r === false) throw new Exception('Failed to query wallet. (W::f)'); // badly formed query if ($r === false) throw new \Exception('Failed to query wallet. (W::f)'); // badly formed query
$w = $r->fetchArray(SQLITE3_ASSOC); $w = $r->fetchArray(SQLITE3_ASSOC);
if ($w === false) return false; // no wallet found if ($w === false) return false; // no wallet found
return new Wallet($user_id, $w['silver'], $w['stargem']); return new Wallet($user_id, $w['silver'], $w['stargem']);
} }
public static function create(int $user_id, int $silver = -1, int $starGems = -1): SQLite3Result|false public static function create(int $user_id, int $silver = -1, int $starGems = -1): \SQLite3Result|false
{ {
return live_db()->query( return live_db()->query(
"INSERT INTO wallets (user_id, silver, stargem) VALUES (:u, :s, :sg)", "INSERT INTO wallets (user_id, silver, stargem) VALUES (:u, :s, :sg)",
@ -32,7 +34,7 @@ class Wallet
/** /**
* Add a certain amount of currency to the user's wallet. * Add a certain amount of currency to the user's wallet.
*/ */
public function give(Currency $c, int $amt): SQLite3Result|false public function give(\Currency $c, int $amt): \SQLite3Result|false
{ {
$cs = $c->string(true); $cs = $c->string(true);
$new = $this->{$cs} + $amt; $new = $this->{$cs} + $amt;
@ -42,7 +44,7 @@ class Wallet
/** /**
* Remove a certain amount of currency from the user's wallet. * Remove a certain amount of currency from the user's wallet.
*/ */
public function take(Currency $c, int $amt): SQLite3Result|false public function take(\Currency $c, int $amt): \SQLite3Result|false
{ {
$cs = $c->string(true); $cs = $c->string(true);
$new = $this->{$cs} - $amt; $new = $this->{$cs} - $amt;