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,93 +1,96 @@
/*
@BLOG
============================================================
Stats
============================================================
*/
DROP TABLE IF EXISTS 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`);
CREATE TABLE stats (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`luck` INTEGER NOT NULL DEFAULT 0,
`armor` INTEGER NOT NULL DEFAULT 0,
`focus` INTEGER NOT NULL DEFAULT 0,
`power` INTEGER NOT NULL DEFAULT 0,
`resist` INTEGER NOT NULL DEFAULT 0,
`accuracy` INTEGER NOT NULL DEFAULT 0,
`ferocity` INTEGER NOT NULL DEFAULT 0,
`precision` INTEGER NOT NULL DEFAULT 0,
`toughness` INTEGER NOT NULL DEFAULT 0,
`penetration` INTEGER NOT NULL DEFAULT 0,
) STRICT;
/*
@CHARS
============================================================
Characters
============================================================
*/
DROP TABLE IF EXISTS characters;
CREATE TABLE characters (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`user_id` INTEGER NOT NULL,
`name` TEXT NOT NULL UNIQUE,
`title_id` INTEGER NOT NULL DEFAULT 1,
`level` INTEGER NOT NULL DEFAULT 1,
`xp` INTEGER NOT NULL DEFAULT 0,
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`user_id` INTEGER NOT NULL,
`name` TEXT NOT NULL UNIQUE,
`title_id` INTEGER NOT NULL DEFAULT 1,
`level` INTEGER NOT NULL DEFAULT 1,
`xp` INTEGER NOT NULL DEFAULT 0,
`xp_to_level` INTEGER NOT NULL DEFAULT 100,
`hp` INTEGER NOT NULL DEFAULT 20,
`m_hp` INTEGER NOT NULL DEFAULT 20,
`mp` INTEGER NOT NULL DEFAULT 10,
`m_mp` INTEGER NOT NULL DEFAULT 10,
`tp` INTEGER NOT NULL DEFAULT 1,
`m_tp` INTEGER NOT NULL DEFAULT 1,
`pow` INTEGER NOT NULL DEFAULT 0, -- Power
`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,
`att_points` INTEGER NOT NULL DEFAULT 0,
`bio` TEXT DEFAULT ''
`hp` INTEGER NOT NULL DEFAULT 20,
`m_hp` INTEGER NOT NULL DEFAULT 20,
`mp` INTEGER NOT NULL DEFAULT 10,
`m_mp` INTEGER NOT NULL DEFAULT 10,
`tp` INTEGER NOT NULL DEFAULT 1,
`m_tp` INTEGER NOT NULL DEFAULT 1,
`stats_id` INTEGER NOT NULL,
`inv_slots` INTEGER NOT NULL DEFAULT 10,
`att_points` INTEGER NOT NULL DEFAULT 0,
`bio` TEXT DEFAULT ''
);
CREATE INDEX idx_characters_user_id ON characters (`user_id`);
/*
@CHARGEAR
*/
DROP TABLE IF EXISTS char_gear;
CREATE TABLE char_gear (
`char_id` INTEGER NOT NULL,
`head` INTEGER NOT NULL DEFAULT 0,
`chest` INTEGER NOT NULL DEFAULT 0,
`boots` INTEGER NOT NULL DEFAULT 0,
`hands` INTEGER NOT NULL DEFAULT 0,
`m_hand` INTEGER NOT NULL DEFAULT 0,
`o_hand` INTEGER NOT NULL DEFAULT 0,
`rune` INTEGER NOT NULL DEFAULT 0,
`ring` INTEGER NOT NULL DEFAULT 0,
`amulet` INTEGER NOT NULL DEFAULT 0,
`pow` INTEGER NOT NULL DEFAULT 0, -- Power
`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_mp` INTEGER NOT NULL DEFAULT 0
CREATE TABLE equipped_items (
`char_id` INTEGER NOT NULL,
`head` INTEGER NOT NULL DEFAULT 0,
`chest` INTEGER NOT NULL DEFAULT 0,
`boots` INTEGER NOT NULL DEFAULT 0,
`hands` INTEGER NOT NULL DEFAULT 0,
`m_hand` INTEGER NOT NULL DEFAULT 0,
`o_hand` INTEGER NOT NULL DEFAULT 0,
`rune` INTEGER NOT NULL DEFAULT 0,
`ring` INTEGER NOT NULL DEFAULT 0,
`amulet` INTEGER NOT NULL DEFAULT 0,
`stats_id` INTEGER NOT NULL,
`max_hp` INTEGER NOT NULL DEFAULT 0,
`max_mp` INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX idx_char_gear_char_id ON char_gear (`char_id`);
/*
@CHARINV
*/
DROP TABLE IF EXISTS char_inventory;
CREATE TABLE char_inventory (
CREATE TABLE inventory_items (
`char_id` INTEGER NOT NULL,
`item_id` INTEGER NOT NULL
);
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
*/

View File

@ -1,7 +1,11 @@
<?php
use Models\Character;
/*
Setup
============================================================
Bootstrapping
============================================================
*/
define('SRC', __DIR__ . '/../src');
define('DATABASE_PATH', __DIR__ . '/../database');
@ -10,7 +14,9 @@ require_once SRC . '/bootstrap.php';
$r = new Router;
/*
============================================================
Home
============================================================
*/
$r->get('/', function() {
if (user()) redirect('/world');
@ -18,73 +24,23 @@ $r->get('/', function() {
});
/*
============================================================
Auth
============================================================
*/
$r->get('/register', 'Actions\Auth::register_get')->middleware('guest_only');
$r->post('/register', 'Actions\Auth::register_post')->middleware('guest_only');
$r->get('/login', function() {
echo render('layouts/basic', ['view' => 'pages/auth/login']);
})->middleware('guest_only');
$r->get('/login', 'Actions\Auth::login_get')->middleware('guest_only');
$r->post('/login', 'Actions\Auth::login_post')->middleware('guest_only');
$r->post('/login', function() {
$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('/');
})->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('/');
});
$r->post('/logout', 'Actions\Auth::logout')->middleware('auth_only');
if (env('debug', false)) $r->get('/debug/logout', 'Actions\Auth::logout');
/*
============================================================
Characters
============================================================
*/
$r->get('/characters', function() {
//echo page('chars/list', ['chars' => user()->char_list()]);
@ -220,7 +176,9 @@ $r->post('/character/delete', function() {
})->middleware('must_have_character');
/*
============================================================
World
============================================================
*/
$r->get('/world', function() {
echo render('layouts/game');
@ -270,14 +228,18 @@ $r->post('/move', function() {
})->middleware('ajax_only')->middleware('must_have_character');
/*
UI
============================================================
UI Components
============================================================
*/
$r->get('/ui/stats', function() {
echo c_profile_stats(char());
})->middleware('ajax_only')->middleware('must_have_character');
/*
============================================================
Router
============================================================
*/
// [code, handler, params, middleware]
$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'] ?? []);
/*
============================================================
Cleanup
============================================================
*/
clear_flashes();

View File

@ -2,10 +2,17 @@
namespace Actions;
use \User;
use Models\Session;
use Models\User;
use Models\Wallet;
class Auth
{
/*
============================================================
Registration
============================================================
*/
public static function register_get(): void
{
echo render('layouts/basic', ['view' => 'pages/auth/register']);
@ -67,10 +74,76 @@ class Auth
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));
\Wallet::create(user()->id);
$_SESSION['user'] = serialize(User::find($u));
Wallet::create(user()->id);
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

@ -24,13 +24,13 @@ session_start();
============================================================
*/
define('CLASS_MAP', [
'Database' => '/database.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',
'Database' => '/database.php',
'Router' => '/router.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) {
@ -58,12 +58,11 @@ if (env('debug', false)) {
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'])) {
$csrf = $_POST['csrf'] ?? $_SERVER['HTTP_X_CSRF'] ?? '';
if (!hash_equals($_SESSION['csrf'] ?? '', $csrf)) error_response(418);
$csrf = $_POST['csrf'] ?? $_SERVER['HTTP_X_CSRF'] ?? ''; // look for CSRF in AJAX requests
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
============================================================
*/
$GLOBALS['databases'] = []; // database interfaces
// all relevant state to handling requests
$GLOBALS['databases'] = [];
$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
use Models\Character;
/**
* Render the logout button's form.
*/

View File

@ -23,11 +23,7 @@ class Database extends SQLite3
$p = strpos($query, '?') !== false;
$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);
$r = $stmt->execute();
@ -61,9 +57,7 @@ class Database extends SQLite3
$this->count++;
$this->query_time += $time_taken;
if (env('debug', false)) {
$this->log[] = [$query, $time_taken];
}
if (env('debug', false)) $this->log[] = [$query, $time_taken];
}
private function getSQLiteType(mixed $value): int

View File

@ -1,5 +1,7 @@
<?php
use Models\Character;
/**
* 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.
*/
function user(): User|false
function user(): Models\User|false
{
if (empty($_SESSION['user'])) return false;
return $GLOBALS['state']['user'] ??= unserialize($_SESSION['user']);
@ -195,8 +197,7 @@ function modify_user_session(string $field, mixed $value): bool
function char(): Character|false
{
if (empty($_SESSION['user'])) return false;
if (empty($GLOBALS['char'])) $GLOBALS['char'] = user()->current_char();
return $GLOBALS['char'];
return $GLOBALS['state']['char'] ??= Character::find(user()->char_id);
}
/**
@ -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
* 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 (empty($GLOBALS['wallet'])) $w = user()->wallet();
if ($w === false) return false;
return $GLOBALS['wallet'] = $w;
return $GLOBALS['state']['wallet'] = Models\Wallet::find(user()->id);
}
/**
@ -396,7 +395,7 @@ function db_fetch_array(SQLite3Result $result, int $mode = SQLITE3_ASSOC): array
function validate_session(): bool
{
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;
}

View File

@ -1,5 +1,7 @@
<?php
namespace Models;
/*
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
@ -74,7 +76,7 @@ class Character
"SELECT * FROM characters WHERE id = :id OR name = :id COLLATE NOCASE",
[':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);
}
@ -99,7 +101,7 @@ class Character
// Create the character!
if (live_db()->query("INSERT INTO characters ($f) VALUES ($v)", $data) === false) {
// @TODO: Log this error
throw new Exception('Failed to create character. (cc)');
throw new \Exception('Failed to create character. (cc)');
}
// Get the character ID
@ -153,7 +155,7 @@ class Character
"SELECT 1 FROM characters WHERE id = :i AND user_id = :u LIMIT 1",
[':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;
}
@ -186,7 +188,7 @@ class Character
{
// Delete the character
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',
[$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);
if ($a === false) return false;

View File

@ -1,5 +1,7 @@
<?php
namespace Models;
class Session
{
public function __construct(
@ -29,7 +31,7 @@ class Session
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]);
}

View File

@ -1,5 +1,7 @@
<?php
namespace Models;
/**
* 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).
*/
public DateTime $created;
public int $created;
/**
* 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.
@ -57,7 +59,7 @@ class User
{
foreach ($data as $k => $v) {
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",
[':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);
if ($u === false) return false; // no user found
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
* 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)", [
':u' => $username,
@ -120,7 +122,7 @@ class User
/**
* 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(
"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",
[':u' => $this->id]
)->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];
}
@ -148,7 +150,7 @@ class User
public function char_list(): array|false
{
$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 = [];
while ($row = $q->fetchArray(SQLITE3_ASSOC)) {
@ -158,20 +160,4 @@ class User
// return false if no characters
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
namespace Models;
class Wallet
{
public function __construct(
@ -11,13 +13,13 @@ class Wallet
public static function find(int $user_id): Wallet|false
{
$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);
if ($w === false) return false; // no wallet found
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(
"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.
*/
public function give(Currency $c, int $amt): SQLite3Result|false
public function give(\Currency $c, int $amt): \SQLite3Result|false
{
$cs = $c->string(true);
$new = $this->{$cs} + $amt;
@ -42,7 +44,7 @@ class 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);
$new = $this->{$cs} - $amt;