1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 13:12:41 +00:00

Reorganize Db namespace; alter User ns to match

This commit is contained in:
J. King 2017-03-07 18:01:13 -05:00
parent 37dad63dee
commit 7687109132
23 changed files with 248 additions and 184 deletions

View file

@ -8,4 +8,5 @@ const NS_BASE = __NAMESPACE__."\\";
if(!defined(NS_BASE."INSTALL")) define(NS_BASE."INSTALL", false); if(!defined(NS_BASE."INSTALL")) define(NS_BASE."INSTALL", false);
require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php"; require_once BASE."vendor".DIRECTORY_SEPARATOR."autoload.php";
ignore_user_abort(true); ignore_user_abort(true);
iconv_set_encoding("internal_encoding", "UTF-8");

View file

@ -19,6 +19,8 @@
], ],
"require": { "require": {
"php": "^7.0.0", "php": "^7.0.0",
"ext-intl": "*",
"ext-iconv": "*",
"jkingweb/druuid": "^3.0.0", "jkingweb/druuid": "^3.0.0",
"phpseclib/phpseclib": "^2.0.4", "phpseclib/phpseclib": "^2.0.4",
"webmozart/glob": "^4.1.0", "webmozart/glob": "^4.1.0",

6
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "264437f06f643a1413d45660c2a32124", "content-hash": "8260cf555776b4ffaef7fca3ca891311",
"packages": [ "packages": [
{ {
"name": "fguillot/picofeed", "name": "fguillot/picofeed",
@ -479,7 +479,9 @@
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
"php": "^7.0.0" "php": "^7.0.0",
"ext-intl": "*",
"ext-iconv": "*"
}, },
"platform-dev": [] "platform-dev": []
} }

View file

@ -14,22 +14,22 @@ abstract class AbstractException extends \Exception {
"Lang/Exception.fileCorrupt" => 10104, "Lang/Exception.fileCorrupt" => 10104,
"Lang/Exception.stringMissing" => 10105, "Lang/Exception.stringMissing" => 10105,
"Lang/Exception.stringInvalid" => 10106, "Lang/Exception.stringInvalid" => 10106,
"Db/Exception.extMissing" => 10201, "Db/ExceptionStartup.extMissing" => 10201,
"Db/Exception.fileMissing" => 10202, "Db/ExceptionStartup.fileMissing" => 10202,
"Db/Exception.fileUnusable" => 10203, "Db/ExceptionStartup.fileUnusable" => 10203,
"Db/Exception.fileUnreadable" => 10204, "Db/ExceptionStartup.fileUnreadable" => 10204,
"Db/Exception.fileUnwritable" => 10205, "Db/ExceptionStartup.fileUnwritable" => 10205,
"Db/Exception.fileUncreatable" => 10206, "Db/ExceptionStartup.fileUncreatable" => 10206,
"Db/Exception.fileCorrupt" => 10207, "Db/ExceptionStartup.fileCorrupt" => 10207,
"Db/Exception.paramTypeInvalid" => 10401, "Db/Exception.paramTypeInvalid" => 10401,
"Db/Exception.paramTypeUnknown" => 10402, "Db/Exception.paramTypeUnknown" => 10402,
"Db/Exception.paramTypeMissing" => 10403, "Db/Exception.paramTypeMissing" => 10403,
"Db/Update/Exception.tooNew" => 10211, "Db/ExceptionUpdate.tooNew" => 10211,
"Db/Update/Exception.fileMissing" => 10212, "Db/ExceptionUpdate.fileMissing" => 10212,
"Db/Update/Exception.fileUnusable" => 10213, "Db/ExceptionUpdate.fileUnusable" => 10213,
"Db/Update/Exception.fileUnreadable" => 10214, "Db/ExceptionUpdate.fileUnreadable" => 10214,
"Db/Update/Exception.manual" => 10215, "Db/ExceptionUpdate.manual" => 10215,
"Db/Update/Exception.manualOnly" => 10216, "Db/ExceptionUpdate.manualOnly" => 10216,
"Conf/Exception.fileMissing" => 10302, "Conf/Exception.fileMissing" => 10302,
"Conf/Exception.fileUnusable" => 10303, "Conf/Exception.fileUnusable" => 10303,
"Conf/Exception.fileUnreadable" => 10304, "Conf/Exception.fileUnreadable" => 10304,

View file

@ -5,7 +5,7 @@ namespace JKingWeb\NewsSync;
class Conf { class Conf {
public $lang = "en"; public $lang = "en";
public $dbDriver = Db\DriverSQLite3::class; public $dbDriver = Db\SQLite3\Driver::class;
public $dbSQLite3File = BASE."newssync.db"; public $dbSQLite3File = BASE."newssync.db";
public $dbSQLite3Key = ""; public $dbSQLite3Key = "";
public $dbSQLite3AutoUpd = true; public $dbSQLite3AutoUpd = true;
@ -23,7 +23,7 @@ class Conf {
public $dbMySQLDb = "newssync"; public $dbMySQLDb = "newssync";
public $dbMySQLAutoUpd = false; public $dbMySQLAutoUpd = false;
public $userDriver = User\DriverInternal::class; public $userDriver = User\Internal\Driver::class;
public $userAuthPreferHTTP = false; public $userAuthPreferHTTP = false;
public $userComposeNames = true; public $userComposeNames = true;
public $userTempPasswordLength = 20; public $userTempPasswordLength = 20;

View file

@ -34,14 +34,10 @@ class Database {
$sep = \DIRECTORY_SEPARATOR; $sep = \DIRECTORY_SEPARATOR;
$path = __DIR__.$sep."Db".$sep; $path = __DIR__.$sep."Db".$sep;
$classes = []; $classes = [];
foreach(glob($path."Driver?*.php") as $file) { foreach(glob($path."*".$sep."Driver.php") as $file) {
$name = basename($file, ".php"); $name = basename(dirname($file));
if(substr($name,-3) != "PDO") { $class = NS_BASE."Db\\$name\\Driver";
$name = NS_BASE."Db\\$name"; $classes[$class] = $class::driverName();
if(class_exists($name)) {
$classes[$name] = $name::driverName();
}
}
} }
return $classes; return $classes;
} }
@ -328,4 +324,38 @@ class Database {
return (bool) $this->db->prepare("DELETE from newssync_subscriptions where id is ?", "int")->run($id)->changes(); return (bool) $this->db->prepare("DELETE from newssync_subscriptions where id is ?", "int")->run($id)->changes();
} }
public function folderAdd(string $user, array $data): int {
// If the user isn't authorized to perform this action then throw an exception.
if (!$this->data->user->authorize($user, __FUNCTION__)) {
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
}
// If the user doesn't exist throw an exception.
if (!$this->userExists($user)) {
throw new User\Exception("doesNotExist", ["user" => $user, "action" => __FUNCTION__]);
}
// if the desired folder name is missing or invalid, throw an exception
if(!array_key_exists("name", $data)) {
throw new Db\ExceptionInput("missing", ["action" => __FUNCTION__, "field" => "name"]);
} else if(!strlen(trim($data['name']))) {
throw new Db\ExceptionInput("whitespace", ["action" => __FUNCTION__, "field" => "name"]);
} else if(iconv_strlen($data['name']) > 100) {
throw new Db\ExceptionInput("tooLong", ["action" => __FUNCTION__, "field" => "name", 'max' => 100]);
}
// normalize folder's parent, if there is one
$parent = array_key_exists("parent", $data) ? (int) $data['parent'] : 0;
if($parent===0) {
// if no parent is specified, do nothing
$parent = null;
$root = null;
} else {
// if a parent is specified, make sure it exists and belongs to the user; get its root (first-level) folder if it's a nested folder
$p = $this->db->prepare("SELECT id,root from newssync_folders where owner is ? and id is ?", "str", "int")->run($user, $parent)->get();
if($p===null) {
throw new Db\ExceptionInput("idMissing", ["action" => __FUNCTION__, "field" => "parent", 'id' => $parent]);
} else {
// if the parent does not have a root specified (because it is a first-level folder) use the parent ID as the root ID
$root = $p['root']===null ? $parent : $p['root'];
}
}
}
} }

View file

@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync\Db;
class ExceptionInput extends Exception {
}

View file

@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync\Db;
class ExceptionInput extends Exception {
}

View file

@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync\Db;
class ExceptionTimeout extends Exception {
}

View file

@ -0,0 +1,6 @@
<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync\Db;
class ExceptionUpdate extends Exception {
}

View file

@ -1,14 +1,26 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\NewsSync\Db; namespace JKingWeb\NewsSync\Db\SQLite3;
use JKingWeb\NewsSync\Lang;
use JKingWeb\NewsSync\Db\Exception;
use JKingWeb\NewsSync\Db\ExceptionStartup;
use JKingWeb\NewsSync\Db\ExceptionUpdate;
use JKingWeb\NewsSync\Db\ExceptionInput;
use JKingWeb\NewsSync\Db\ExceptionTimeout;
class DriverSQLite3 extends AbstractDriver {
class Driver extends \JKingWeb\NewsSync\Db\AbstractDriver {
const SQLITE_ERROR = 1;
const SQLITE_BUSY = 5;
const SQLITE_CONSTRAINT = 19;
const SQLITE_MISMATCH = 20;
protected $db; protected $db;
protected $data; protected $data;
public function __construct(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false) { public function __construct(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false) {
// check to make sure required extension is loaded // check to make sure required extension is loaded
if(!class_exists("SQLite3")) throw new Exception("extMissing", self::driverName()); if(!class_exists("SQLite3")) throw new ExceptionStartup("extMissing", self::driverName());
$this->data = $data; $this->data = $data;
$file = $data->conf->dbSQLite3File; $file = $data->conf->dbSQLite3File;
// if the file exists (or we're initializing the database), try to open it and set initial options // if the file exists (or we're initializing the database), try to open it and set initial options
@ -20,14 +32,14 @@ class DriverSQLite3 extends AbstractDriver {
} catch(\Throwable $e) { } catch(\Throwable $e) {
// if opening the database doesn't work, check various pre-conditions to find out what the problem might be // if opening the database doesn't work, check various pre-conditions to find out what the problem might be
if(!file_exists($file)) { if(!file_exists($file)) {
if($install && !is_writable(dirname($file))) throw new Exception("fileUncreatable", dirname($file)); if($install && !is_writable(dirname($file))) throw new ExceptionStartup("fileUncreatable", dirname($file));
throw new Exception("fileMissing", $file); throw new ExceptionStartup("fileMissing", $file);
} }
if(!is_readable($file) && !is_writable($file)) throw new Exception("fileUnusable", $file); if(!is_readable($file) && !is_writable($file)) throw new ExceptionStartup("fileUnusable", $file);
if(!is_readable($file)) throw new Exception("fileUnreadable", $file); if(!is_readable($file)) throw new ExceptionStartup("fileUnreadable", $file);
if(!is_writable($file)) throw new Exception("fileUnwritable", $file); if(!is_writable($file)) throw new ExceptionStartup("fileUnwritable", $file);
// otherwise the database is probably corrupt // otherwise the database is probably corrupt
throw new Exception("fileCorrupt", $mainfile); throw new ExceptionStartup("fileCorrupt", $mainfile);
} }
} }
@ -38,18 +50,17 @@ class DriverSQLite3 extends AbstractDriver {
static public function driverName(): string { static public function driverName(): string {
$name = str_replace(Driver::class, "", static::class); return Lang::msg("Driver.Db.SQLite3.Name");
return Lang::msg("Driver.Db.$name.Name");
} }
public function schemaVersion(): int { public function schemaVersion(): int {
return $this->query("PRAGMA user_version")->getSingle(); return $this->query("PRAGMA user_version")->getValue();
} }
public function schemaUpdate(int $to): bool { public function schemaUpdate(int $to): bool {
$ver = $this->schemaVersion(); $ver = $this->schemaVersion();
if(!$this->data->conf->dbSQLite3AutoUpd) throw new Update\Exception("manual", ['version' => $ver, 'driver_name' => $this->driverName()]); if(!$this->data->conf->dbSQLite3AutoUpd) throw new ExceptionUpdate("manual", ['version' => $ver, 'driver_name' => $this->driverName()]);
if($ver >= $to) throw new Update\Exception("tooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]); if($ver >= $to) throw new ExceptionUpdate("tooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]);
$sep = \DIRECTORY_SEPARATOR; $sep = \DIRECTORY_SEPARATOR;
$path = \JKingWeb\NewsSync\BASE."sql".$sep."SQLite3".$sep; $path = \JKingWeb\NewsSync\BASE."sql".$sep."SQLite3".$sep;
$this->lock(); $this->lock();
@ -58,10 +69,10 @@ class DriverSQLite3 extends AbstractDriver {
$this->begin(); $this->begin();
try { try {
$file = $path.$a.".sql"; $file = $path.$a.".sql";
if(!file_exists($file)) throw new Update\Exception("fileMissing", ['file' => $file, 'driver_name' => $this->driverName()]); if(!file_exists($file)) throw new ExceptionUpdate("fileMissing", ['file' => $file, 'driver_name' => $this->driverName()]);
if(!is_readable($file)) throw new Update\Exception("fileUnreadable", ['file' => $file, 'driver_name' => $this->driverName()]); if(!is_readable($file)) throw new ExceptionUpdate("fileUnreadable", ['file' => $file, 'driver_name' => $this->driverName()]);
$sql = @file_get_contents($file); $sql = @file_get_contents($file);
if($sql===false) throw new Update\Exception("fileUnusable", ['file' => $file, 'driver_name' => $this->driverName()]); if($sql===false) throw new ExceptionUpdate("fileUnusable", ['file' => $file, 'driver_name' => $this->driverName()]);
$this->exec($sql); $this->exec($sql);
} catch(\Throwable $e) { } catch(\Throwable $e) {
// undo any partial changes from the failed update // undo any partial changes from the failed update
@ -69,6 +80,7 @@ class DriverSQLite3 extends AbstractDriver {
// commit any successful updates if updating by more than one version // commit any successful updates if updating by more than one version
$this->commit(true); $this->commit(true);
// throw the error received // throw the error received
// FIXME: This should create the relevant type of SQL exception
throw $e; throw $e;
} }
$this->commit(); $this->commit();
@ -82,11 +94,11 @@ class DriverSQLite3 extends AbstractDriver {
return (bool) $this->db->exec($query); return (bool) $this->db->exec($query);
} }
public function query(string $query): Result { public function query(string $query): \JKingWeb\NewsSync\Db\Result {
return new ResultSQLite3($this->db->query($query), $this->db->changes()); return new Result($this->db->query($query), $this->db->changes());
} }
public function prepareArray(string $query, array $paramTypes): Statement { public function prepareArray(string $query, array $paramTypes): \JKingWeb\NewsSync\Db\Statement {
return new StatementSQLite3($this->db, $this->db->prepare($query), $paramTypes); return new Statement($this->db, $this->db->prepare($query), $paramTypes);
} }
} }

View file

@ -1,8 +1,8 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\NewsSync\Db; namespace JKingWeb\NewsSync\Db\SQLite3;
class ResultSQLite3 implements Result { class Result implements \JKingWeb\NewsSync\Db\Result {
protected $st; protected $st;
protected $set; protected $set;
protected $pos = 0; protected $pos = 0;
@ -40,7 +40,7 @@ class ResultSQLite3 implements Result {
// constructor/destructor // constructor/destructor
public function __construct(\SQLite3Result $result, int $changes = 0, StatementSQLite3 $statement = null) { public function __construct(\SQLite3Result $result, int $changes = 0, Statement $statement = null) {
$this->st = $statement; //keeps the statement from being destroyed, invalidating the result set $this->st = $statement; //keeps the statement from being destroyed, invalidating the result set
$this->set = $result; $this->set = $result;
$this->rows = $changes; $this->rows = $changes;

View file

@ -1,8 +1,9 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\NewsSync\Db; namespace JKingWeb\NewsSync\Db\SQLite3;
use JKingWeb\NewsSync\Db\Exception;
class StatementSQLite3 extends AbstractStatement { class Statement extends \JKingWeb\NewsSync\Db\AbstractStatement {
const BINDINGS = [ const BINDINGS = [
"null" => \SQLITE3_NULL, "null" => \SQLITE3_NULL,
"integer" => \SQLITE3_INTEGER, "integer" => \SQLITE3_INTEGER,
@ -38,7 +39,7 @@ class StatementSQLite3 extends AbstractStatement {
])[$part]; ])[$part];
} }
public function runArray(array $values = null): Result { public function runArray(array $values = null): \JKingWeb\NewsSync\Db\Result {
$this->st->clear(); $this->st->clear();
$l = sizeof($values); $l = sizeof($values);
for($a = 0; $a < $l; $a++) { for($a = 0; $a < $l; $a++) {
@ -58,6 +59,6 @@ class StatementSQLite3 extends AbstractStatement {
// perform binding // perform binding
$this->st->bindValue($a+1, $values[$a], $type); $this->st->bindValue($a+1, $values[$a], $type);
} }
return new ResultSQLite3($this->st->execute(), $this->db->changes(), $this); return new Result($this->st->execute(), $this->db->changes(), $this);
} }
} }

View file

@ -1,6 +0,0 @@
<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync\Db\Update;
class Exception extends \JKingWeb\NewsSync\Db\Exception {
}

View file

@ -15,12 +15,10 @@ class User {
$sep = \DIRECTORY_SEPARATOR; $sep = \DIRECTORY_SEPARATOR;
$path = __DIR__.$sep."User".$sep; $path = __DIR__.$sep."User".$sep;
$classes = []; $classes = [];
foreach(glob($path."Driver?*.php") as $file) { foreach(glob($path."*".$sep."Driver.php") as $file) {
$drv = basename($file, ".php"); $name = basename(dirname($file));
$drv = NS_BASE."Db\\$drv"; $class = NS_BASE."User\\$name\\Driver";
if(class_exists($drv)) { $classes[$class] = $class::driverName();
$classes[$drv] = $drv::driverName();
}
} }
return $classes; return $classes;
} }

View file

@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync\User;
use JKingWeb\NewsSync\Lang;
final class DriverInternal implements Driver {
use InternalFunctions;
protected $data;
protected $db;
protected $functions = [
"auth" => Driver::FUNC_INTERNAL,
"userList" => Driver::FUNC_INTERNAL,
"userExists" => Driver::FUNC_INTERNAL,
"userAdd" => Driver::FUNC_INTERNAL,
"userRemove" => Driver::FUNC_INTERNAL,
"userPasswordSet" => Driver::FUNC_INTERNAL,
"userPropertiesGet" => Driver::FUNC_INTERNAL,
"userPropertiesSet" => Driver::FUNC_INTERNAL,
"userRightsGet" => Driver::FUNC_INTERNAL,
"userRightsSet" => Driver::FUNC_INTERNAL,
];
static public function create(\JKingWeb\NewsSync\RuntimeData $data): Driver {
return new static($data);
}
static public function driverName(): string {
$name = str_replace(Driver::class, "", static::class);
return Lang::msg("Driver.User.$name.Name");
}
public function driverFunctions(string $function = null) {
if($function===null) return $this->functions;
if(array_key_exists($function, $this->functions)) {
return $this->functions[$function];
} else {
return Driver::FUNC_NOT_IMPLEMENTED;
}
}
// see InternalFunctions.php for bulk of methods
}

View file

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace JKingWeb\NewsSync\User\Internal;
use JKingWeb\NewsSync\Lang;
use JKingWeb\NewsSync\User\Driver as Iface;
final class Driver implements Iface {
use InternalFunctions;
protected $data;
protected $db;
protected $functions = [
"auth" => Iface::FUNC_INTERNAL,
"userList" => Iface::FUNC_INTERNAL,
"userExists" => Iface::FUNC_INTERNAL,
"userAdd" => Iface::FUNC_INTERNAL,
"userRemove" => Iface::FUNC_INTERNAL,
"userPasswordSet" => Iface::FUNC_INTERNAL,
"userPropertiesGet" => Iface::FUNC_INTERNAL,
"userPropertiesSet" => Iface::FUNC_INTERNAL,
"userRightsGet" => Iface::FUNC_INTERNAL,
"userRightsSet" => Iface::FUNC_INTERNAL,
];
static public function create(\JKingWeb\NewsSync\RuntimeData $data): Driver {
return new static($data);
}
static public function driverName(): string {
return Lang::msg("Driver.User.Internal.Name");
}
public function driverFunctions(string $function = null) {
if($function===null) return $this->functions;
if(array_key_exists($function, $this->functions)) {
return $this->functions[$function];
} else {
return Iface::FUNC_NOT_IMPLEMENTED;
}
}
// see InternalFunctions.php for bulk of methods
}

View file

@ -1,6 +1,6 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\NewsSync\User; namespace JKingWeb\NewsSync\User\Internal;
trait InternalFunctions { trait InternalFunctions {
protected $actor = []; protected $actor = [];

View file

@ -21,31 +21,31 @@ return [
'Exception.JKingWeb/NewsSync/Conf/Exception.fileUnwritable' => 'Insufficient permissions to overwrite configuration file "{0}"', 'Exception.JKingWeb/NewsSync/Conf/Exception.fileUnwritable' => 'Insufficient permissions to overwrite configuration file "{0}"',
'Exception.JKingWeb/NewsSync/Conf/Exception.fileCorrupt' => 'Configuration file "{0}" is corrupt or does not conform to expected format', 'Exception.JKingWeb/NewsSync/Conf/Exception.fileCorrupt' => 'Configuration file "{0}" is corrupt or does not conform to expected format',
'Exception.JKingWeb/NewsSync/Db/Exception.extMissing' => 'Required PHP extension for driver "{0}" not installed', 'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.extMissing' => 'Required PHP extension for driver "{0}" not installed',
'Exception.JKingWeb/NewsSync/Db/Exception.fileMissing' => 'Database file "{0}" does not exist', 'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileMissing' => 'Database file "{0}" does not exist',
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading', 'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading',
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnwritable' => 'Insufficient permissions to open database file "{0}" for writing', 'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileUnwritable' => 'Insufficient permissions to open database file "{0}" for writing',
'Exception.JKingWeb/NewsSync/Db/Exception.fileUnusable' => 'Insufficient permissions to open database file "{0}" for reading or writing', 'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileUnusable' => 'Insufficient permissions to open database file "{0}" for reading or writing',
'Exception.JKingWeb/NewsSync/Db/Exception.fileUncreatable' => 'Insufficient permissions to create new database file "{0}"', 'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileUncreatable' => 'Insufficient permissions to create new database file "{0}"',
'Exception.JKingWeb/NewsSync/Db/Exception.fileCorrupt' => 'Database file "{0}" is corrupt or not a valid database', 'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileCorrupt' => 'Database file "{0}" is corrupt or not a valid database',
'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeInvalid' => 'Prepared statement parameter type "{0}" is invalid', 'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeInvalid' => 'Prepared statement parameter type "{0}" is invalid',
'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeUnknown' => 'Prepared statement parameter type "{0}" is valid, but not implemented', 'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeUnknown' => 'Prepared statement parameter type "{0}" is valid, but not implemented',
'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeMissing' => 'Prepared statement parameter type for parameter #{0} was not specified', 'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeMissing' => 'Prepared statement parameter type for parameter #{0} was not specified',
'Exception.JKingWeb/NewsSync/Db/Update/Exception.manual' => 'Exception.JKingWeb/NewsSync/Db/ExceptionUpdate.manual' =>
'{from_version, select, '{from_version, select,
0 {{driver_name} database is configured for manual updates and is not initialized; please populate the database with the base schema} 0 {{driver_name} database is configured for manual updates and is not initialized; please populate the database with the base schema}
other {{driver_name} database is configured for manual updates; please update from schema version {current} to version {target}} other {{driver_name} database is configured for manual updates; please update from schema version {current} to version {target}}
}', }',
'Exception.JKingWeb/NewsSync/Db/Update/Exception.manualOnly' => 'Exception.JKingWeb/NewsSync/Db/ExceptionUpdate.manualOnly' =>
'{from_version, select, '{from_version, select,
0 {{driver_name} database must be updated manually and is not initialized; please populate the database with the base schema} 0 {{driver_name} database must be updated manually and is not initialized; please populate the database with the base schema}
other {{driver_name} database must be updated manually; please update from schema version {current} to version {target}} other {{driver_name} database must be updated manually; please update from schema version {current} to version {target}}
}', }',
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileMissing' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available', 'Exception.JKingWeb/NewsSync/Db/ExceptionUpdate.fileMissing' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available',
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileUnreadable' => 'Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}', 'Exception.JKingWeb/NewsSync/Db/ExceptionUpdate.fileUnreadable' => 'Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}',
'Exception.JKingWeb/NewsSync/Db/Update/Exception.fileUnusable' => 'Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}', 'Exception.JKingWeb/NewsSync/Db/ExceptionUpdate.fileUnusable' => 'Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}',
'Exception.JKingWeb/NewsSync/Db/Update/Exception.tooNew' => 'Exception.JKingWeb/NewsSync/Db/ExceptionUpdate.tooNew' =>
'{difference, select, '{difference, select,
0 {Automatic updating of the {driver_name} database failed because it is already up to date with the requested version, {target}} 0 {Automatic updating of the {driver_name} database failed because it is already up to date with the requested version, {target}}
other {Automatic updating of the {driver_name} database failed because its version, {current}, is newer than the requested version, {target}} other {Automatic updating of the {driver_name} database failed because its version, {current}, is newer than the requested version, {target}}

View file

@ -1,3 +1,23 @@
-- settings
create table newssync_settings(
key varchar(255) primary key not null, -- setting key
value varchar(255), -- setting value, serialized as a string
type varchar(255) not null check(
type in('int','numeric','text','timestamp','date','time','bool','null','json')
) default 'text' -- the deserialized type of the value
);
-- users
create table newssync_users(
id TEXT primary key not null, -- user id
password TEXT, -- password, salted and hashed; if using external authentication this would be blank
name TEXT, -- display name
avatar_url TEXT, -- external URL to avatar
avatar_type TEXT, -- internal avatar image's MIME content type
avatar_data BLOB, -- internal avatar image's binary data
rights integer not null default 0 -- any administrative rights the user may have
);
-- newsfeeds, deduplicated -- newsfeeds, deduplicated
create table newssync_feeds( create table newssync_feeds(
id integer primary key not null, -- sequence number id integer primary key not null, -- sequence number
@ -15,6 +35,31 @@ create table newssync_feeds(
unique(url,username,password) -- a URL with particular credentials should only appear once unique(url,username,password) -- a URL with particular credentials should only appear once
); );
-- users' subscriptions to newsfeeds, with settings
create table newssync_subscriptions(
id integer primary key not null, -- sequence number
owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of subscription
feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription
added datetime not null default CURRENT_TIMESTAMP, -- time at which feed was added
modified datetime not null default CURRENT_TIMESTAMP, -- date at which subscription properties were last modified
title TEXT, -- user-supplied title
order_type int not null default 0, -- ownCloud sort order
pinned boolean not null default 0, -- whether feed is pinned (always sorts at top)
folder integer references newssync_folders(id) on delete set null, -- TT-RSS category (nestable); the first-level category (which acts as ownCloud folder) is joined in when needed
unique(owner,feed) -- a given feed should only appear once for a given owner
);
-- TT-RSS categories and ownCloud folders
create table newssync_folders(
id integer primary key not null, -- sequence number
owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of folder
parent integer not null default 0, -- parent folder id
root integer not null default 0, -- first-level folder (ownCloud folder)
name TEXT not null, -- folder name
modified datetime not null default CURRENT_TIMESTAMP, --
unique(owner,name,parent) -- cannot have multiple folders with the same name under the same parent for the same owner
);
-- entries in newsfeeds -- entries in newsfeeds
create table newssync_articles( create table newssync_articles(
id integer primary key not null, -- sequence number id integer primary key not null, -- sequence number
@ -40,57 +85,6 @@ create table newssync_enclosures(
type varchar(255) type varchar(255)
); );
-- author labels ("categories" in RSS/Atom parlance) associated with newsfeed entries
create table newssync_tags(
article integer not null references newssync_articles(id) on delete cascade,
name TEXT
);
-- settings
create table newssync_settings(
key varchar(255) primary key not null, --
value varchar(255), --
type varchar(255) not null check(
type in('int','numeric','text','timestamp','date','time','bool','null','json')
) default 'text' --
);
-- users
create table newssync_users(
id TEXT primary key not null, -- user id
password TEXT, -- password, salted and hashed; if using external authentication this would be blank
name TEXT, -- display name
avatar_url TEXT, -- external URL to avatar
avatar_type TEXT, -- internal avatar image's MIME content type
avatar_data BLOB, -- internal avatar image's binary data
rights integer not null default 0 -- any administrative rights the user may have
);
-- TT-RSS categories and ownCloud folders
create table newssync_categories(
id integer primary key not null, -- sequence number
owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of category
parent integer, -- parent category id
folder integer not null, -- first-level category (ownCloud folder)
name TEXT not null, -- category name
modified datetime not null default CURRENT_TIMESTAMP, --
unique(owner,name,parent) -- cannot have multiple categories with the same name under the same parent for the same owner
);
-- users' subscriptions to newsfeeds, with settings
create table newssync_subscriptions(
id integer primary key not null, -- sequence number
owner TEXT not null references newssync_users(id) on delete cascade on update cascade, -- owner of subscription
feed integer not null references newssync_feeds(id) on delete cascade, -- feed for the subscription
added datetime not null default CURRENT_TIMESTAMP, -- time at which feed was added
modified datetime not null default CURRENT_TIMESTAMP, -- date at which subscription properties were last modified
title TEXT, -- user-supplied title
order_type int not null default 0, -- ownCloud sort order
pinned boolean not null default 0, -- whether feed is pinned (always sorts at top)
category integer references newssync_categories(id) on delete set null, -- TT-RSS category (nestable); the first-level category (which acts as ownCloud folder) is joined in when needed
unique(owner,feed) -- a given feed should only appear once for a given owner
);
-- users' actions on newsfeed entries -- users' actions on newsfeed entries
create table newssync_subscription_articles( create table newssync_subscription_articles(
id integer primary key not null, id integer primary key not null,
@ -108,6 +102,12 @@ create table newssync_labels(
); );
create index newssync_label_names on newssync_labels(name); create index newssync_label_names on newssync_labels(name);
-- author labels ("categories" in RSS/Atom parlance) associated with newsfeed entries
create table newssync_tags(
article integer not null references newssync_articles(id) on delete cascade,
name TEXT
);
-- set version marker -- set version marker
pragma user_version = 1; pragma user_version = 1;
insert into newssync_settings values('schema_version',1,'int'); insert into newssync_settings values('schema_version',1,'int');

View file

@ -21,20 +21,20 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
function testConstructResult() { function testConstructResult() {
$set = $this->c->query("SELECT 1"); $set = $this->c->query("SELECT 1");
$this->assertInstanceOf(Db\ResultSQLite3::class, new Db\ResultSQLite3($set)); $this->assertInstanceOf(Db\Result::class, new Db\SQLite3\Result($set));
} }
function testGetChangeCount() { function testGetChangeCount() {
$this->c->query("CREATE TABLE test(col)"); $this->c->query("CREATE TABLE test(col)");
$set = $this->c->query("INSERT INTO test(col) values(1)"); $set = $this->c->query("INSERT INTO test(col) values(1)");
$rows = $this->c->changes(); $rows = $this->c->changes();
$this->assertEquals($rows, (new Db\ResultSQLite3($set,$rows))->changes()); $this->assertEquals($rows, (new Db\SQLite3\Result($set,$rows))->changes());
} }
function testIterateOverResults() { function testIterateOverResults() {
$set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col"); $set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col");
$rows = []; $rows = [];
foreach(new Db\ResultSQLite3($set) as $row) { foreach(new Db\SQLite3\Result($set) as $row) {
$rows[] = $row['col']; $rows[] = $row['col'];
} }
$this->assertEquals([1,2,3], $rows); $this->assertEquals([1,2,3], $rows);
@ -43,7 +43,7 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
function testIterateOverResultsTwice() { function testIterateOverResultsTwice() {
$set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col"); $set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col");
$rows = []; $rows = [];
$test = new Db\ResultSQLite3($set); $test = new Db\SQLite3\Result($set);
foreach($test as $row) { foreach($test as $row) {
$rows[] = $row['col']; $rows[] = $row['col'];
} }
@ -55,7 +55,7 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
function testGetSingleValues() { function testGetSingleValues() {
$set = $this->c->query("SELECT 1867 as year union select 1970 as year union select 2112 as year"); $set = $this->c->query("SELECT 1867 as year union select 1970 as year union select 2112 as year");
$test = new Db\ResultSQLite3($set); $test = new Db\SQLite3\Result($set);
$this->assertEquals(1867, $test->getValue()); $this->assertEquals(1867, $test->getValue());
$this->assertEquals(1970, $test->getValue()); $this->assertEquals(1970, $test->getValue());
$this->assertEquals(2112, $test->getValue()); $this->assertEquals(2112, $test->getValue());
@ -64,7 +64,7 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
function testGetFirstValuesOnly() { function testGetFirstValuesOnly() {
$set = $this->c->query("SELECT 1867 as year, 19 as century union select 1970 as year, 20 as century union select 2112 as year, 22 as century"); $set = $this->c->query("SELECT 1867 as year, 19 as century union select 1970 as year, 20 as century union select 2112 as year, 22 as century");
$test = new Db\ResultSQLite3($set); $test = new Db\SQLite3\Result($set);
$this->assertEquals(1867, $test->getValue()); $this->assertEquals(1867, $test->getValue());
$this->assertEquals(1970, $test->getValue()); $this->assertEquals(1970, $test->getValue());
$this->assertEquals(2112, $test->getValue()); $this->assertEquals(2112, $test->getValue());
@ -77,7 +77,7 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
['album' => '2112', 'track' => '2112'], ['album' => '2112', 'track' => '2112'],
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'], ['album' => 'Clockwork Angels', 'track' => 'The Wreckers'],
]; ];
$test = new Db\ResultSQLite3($set); $test = new Db\SQLite3\Result($set);
$this->assertEquals($rows[0], $test->get()); $this->assertEquals($rows[0], $test->get());
$this->assertEquals($rows[1], $test->get()); $this->assertEquals($rows[1], $test->get());
$this->assertSame(null, $test->get()); $this->assertSame(null, $test->get());

View file

@ -8,7 +8,7 @@ class TestDbStatementSQLite3 extends \PHPUnit\Framework\TestCase {
use Test\Tools, Test\Db\BindingTests; use Test\Tools, Test\Db\BindingTests;
protected $c; protected $c;
static protected $imp = Db\StatementSQLite3::class; static protected $imp = Db\SQLite3\Statement::class;
function setUp() { function setUp() {
date_default_timezone_set("UTC"); date_default_timezone_set("UTC");
@ -36,7 +36,7 @@ class TestDbStatementSQLite3 extends \PHPUnit\Framework\TestCase {
function testConstructStatement() { function testConstructStatement() {
$nativeStatement = $this->c->prepare("SELECT ? as value"); $nativeStatement = $this->c->prepare("SELECT ? as value");
$this->assertInstanceOf(Db\StatementSQLite3::class, new Db\StatementSQLite3($this->c, $nativeStatement)); $this->assertInstanceOf(Statement::class, new Db\SQLite3\Statement($this->c, $nativeStatement));
} }
function testBindMissingValue() { function testBindMissingValue() {

View file

@ -12,7 +12,7 @@ class TestUserInternalDriver extends \PHPUnit\Framework\TestCase {
protected $data; protected $data;
function setUp() { function setUp() {
$drv = User\DriverInternal::class; $drv = User\Internal\Driver::class;
$conf = new Conf(); $conf = new Conf();
$conf->userDriver = $drv; $conf->userDriver = $drv;
$conf->userAuthPreferHTTP = true; $conf->userAuthPreferHTTP = true;