id = $i; $this->name = $n; $this->hash = $h; $this->info = $in; } /* decode an sql result into a user */ public static function from_sql($x): User { return new User( $x["id"], $x["name"], $x["hash"], $x["info"] ); } } class Info { public $id; public $uid; public $about; public $games; public $wins; public $losses; public $highest; public function __construct( $i, $u, $a, $g, $w, $l, $h ) { $this->id = (integer)$i; $this->uid = (integer)$u; $this->about = $a; $this->games = $g; $this->wins = $w; $this->losses = $l; $this->highest = $h; } public static function from_sql($x): Info { $g = fn($x, $k) => isset($x[$k]) ? $x[$k] : null; eputs("games: " . $g($x, "games")); return new Info( $g($x, "id"), $g($x, "uid"), $g($x, "about"), $g($x, "games"), $g($x, "wins"), $g($x, "losses"), $g($x, "highest") ); } public static function empty(): Info { return new Info( null, null, null, null, null, null, null ); } public function into_array(): array { return [ "id" => $this->id, "uid" => $this->uid, "about" => $this->about, "games" => $this->games, "wins" => $this->wins, "losses" => $this->losses, "highest" => $this->highest ]; } } class Database { public $db; /* constructor with the main db as the default file */ public function __construct($f = "run/poker.db") { $this->db = new SQLite3($f); $q = <<db->exec($q); } /* run a query on the database. * we wrap this function so that we can do things like sanitize. * * accepts a query $q */ public function exec($q) { eputs($q); $this->db->exec($q); } /* query the db and collect the results into an array. * * accepts a query $q */ public function query($q) { eputs($q); return collect_rows($this->db->query($q)); } /* make a new user * * accepts a name $n and a password $p */ public function new_user($n, $p) { $u = null; try { $u = $this->get_user($n); } catch (Exception $e) { } if ($u != null) { throw new Exception( "user " . $u->name . " already exists" ); } /* sanitize our variables */ $n = SQLite3::escapeString($n); $p = SQLite3::escapeString($p); /* hash the password */ $h = password_hash($p, PASSWORD_DEFAULT); if (!$h) { throw new Exception("failed to make password hash"); } $this->exec(<< $p) { $t = gettype($p); switch ($t) { case "string": { $_p = SQLite3::escapeString( $p ); $p = "'$_p',"; break; } case "integer": { $p = "$p,"; break; } default: { throw new Exception( << join("\n", $x); $n = rtrim($j($n), ","); $v = rtrim($j($v), ","); $this->exec(<<query(<< User::from_sql($x), $a); /* TODO: how should we handle users with the same name? * it should never happen in theory because we should make * the garuantee in new_user(), so would this work? */ return count($a) > 0 ? $a[0] : throw new Exception("user not found"); } public function get_user_by_id($i) { $a = $this->query(<< 0 ? User::from_sql($a[0]) : throw new Exception("user not found"); } /* accepts uid $i */ public function get_info($i) { $a = $this->query(<< 0 ? Info::from_sql($a[0]) : Info::empty(); } /* verify that a user matches the given login info * * accepts a name $n and a password $p */ public function login($n, $p) { $e = null; try { $u = $this->get_user($n); if (password_verify($p, $u->hash)) { $_SESSION["id"] = $u->id; return true; } else { throw new Exception("incorrect password"); } } catch (Exception $_e) { $e = $_e; } throw new Exception($e->getMessage()); } /* inc a user with id $i to have one additional game */ public function inc_games($i) { $u = null; try { $u = $this->get_user_by_id($i); } catch (Exception $e) { throw new Exception( "failed getting user " . $e->getMessage() ); } $f = null; try { $f = $this->get_info($i); } catch (Exception $e) { throw new Exception( "failed getting user info " . $e->getMessage() ); } eputs('$f->games: ' . $f->games); $a = ($f->games == null ? 0 : $f->games) + 1; $this->exec(<<fetchArray()) { array_push($a, $x); } return $a; } ?>