~skye/poker/src/db.php
view raw
<?php
/* represent a user from the database */
class User {
public $id;
public $name;
public $hash;
public $info;
public function __construct($i, $n, $h, $in) {
$this->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 = <<<SQL
create table if not exists users (
id integer primary key autoincrement,
name text not null,
hash text not null,
info integer
);
create table if not exists info (
id integer primary key autoincrement,
uid integer not null,
about text,
games integer,
wins integer,
losses integer,
highest integer
);
SQL;
eputs($q);
$this->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(<<<SQL
insert into users (name, hash)
values ('$n', '$h');
SQL);
}
/* add info for a user
*
* accepts a dict of info $d */
public function new_info($d) {
/* names */
$n = array();
/* values */
$v = array();
foreach ($d as $q => $p) {
$t = gettype($p);
switch ($t) {
case "string": {
$_p = SQLite3::escapeString(
$p
);
$p = "'$_p',";
break;
}
case "integer": {
$p = "$p,";
break;
}
default: {
throw new Exception(
<<<STR
value with type $t
invalid
STR
);
}
}
array_push($n, "$q,");
array_push($v, $p);
}
/* raze to str */
$j = fn($x) => join("\n", $x);
$n = rtrim($j($n), ",");
$v = rtrim($j($v), ",");
$this->exec(<<<SQL
insert into info (
$n
) values (
$v
);
SQL);
}
/* find a user by name
*
* accepts a name $n */
public function get_user($n) {
/* sanitize */
$n = SQLite3::escapeString($n);
/* query and convert to user */
$a = $this->query(<<<SQL
select * from users
where name = '$n';
SQL);
$a = array_map(fn($x) => 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(<<<SQL
select * from users
where id = $i;
SQL);
return count($a) > 0
? User::from_sql($a[0])
: throw new Exception("user not found");
}
/* accepts uid $i */
public function get_info($i) {
$a = $this->query(<<<SQL
select * from info
where uid = $i;
SQL);
return count($a) > 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(<<<SQL
update info
set
games = $a
where uid = $i;
SQL);
}
}
/* collect rows from an sql query into an array
*
* accepts a "result" from a query $r */
function collect_rows($r) {
$a = array();
while ($x = $r->fetchArray()) {
array_push($a, $x);
}
return $a;
}
?>