Browse Source

Reorganize Db namespace; alter User ns to match

microsub
J. King 7 years ago
parent
commit
7687109132
  1. 3
      bootstrap.php
  2. 2
      composer.json
  3. 6
      composer.lock
  4. 26
      lib/AbstractException.php
  5. 4
      lib/Conf.php
  6. 46
      lib/Database.php
  7. 6
      lib/Db/ExceptionInput.php
  8. 6
      lib/Db/ExceptionStartup.php
  9. 6
      lib/Db/ExceptionTimeout.php
  10. 6
      lib/Db/ExceptionUpdate.php
  11. 54
      lib/Db/SQLite3/Driver.php
  12. 6
      lib/Db/SQLite3/Result.php
  13. 9
      lib/Db/SQLite3/Statement.php
  14. 6
      lib/Db/Update/Exception.php
  15. 10
      lib/User.php
  16. 43
      lib/User/DriverInternal.php
  17. 43
      lib/User/Internal/Driver.php
  18. 2
      lib/User/Internal/InternalFunctions.php
  19. 26
      locale/en.php
  20. 102
      sql/SQLite3/0.sql
  21. 14
      tests/Db/SQLite3/TestDbResultSQLite3.php
  22. 4
      tests/Db/SQLite3/TestDbStatementSQLite3.php
  23. 2
      tests/User/TestUserInternalDriver.php

3
bootstrap.php

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

2
composer.json

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

6
composer.lock

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

26
lib/AbstractException.php

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

4
lib/Conf.php

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

46
lib/Database.php

@ -34,14 +34,10 @@ class Database {
$sep = \DIRECTORY_SEPARATOR;
$path = __DIR__.$sep."Db".$sep;
$classes = [];
foreach(glob($path."Driver?*.php") as $file) {
$name = basename($file, ".php");
if(substr($name,-3) != "PDO") {
$name = NS_BASE."Db\\$name";
if(class_exists($name)) {
$classes[$name] = $name::driverName();
}
}
foreach(glob($path."*".$sep."Driver.php") as $file) {
$name = basename(dirname($file));
$class = NS_BASE."Db\\$name\\Driver";
$classes[$class] = $class::driverName();
}
return $classes;
}
@ -328,4 +324,38 @@ class Database {
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'];
}
}
}
}

6
lib/Db/ExceptionInput.php

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

6
lib/Db/ExceptionStartup.php

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

6
lib/Db/ExceptionTimeout.php

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

6
lib/Db/ExceptionUpdate.php

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

54
lib/Db/DriverSQLite3.php → lib/Db/SQLite3/Driver.php

@ -1,14 +1,26 @@
<?php
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 $data;
public function __construct(\JKingWeb\NewsSync\RuntimeData $data, bool $install = false) {
// 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;
$file = $data->conf->dbSQLite3File;
// 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) {
// if opening the database doesn't work, check various pre-conditions to find out what the problem might be
if(!file_exists($file)) {
if($install && !is_writable(dirname($file))) throw new Exception("fileUncreatable", dirname($file));
throw new Exception("fileMissing", $file);
if($install && !is_writable(dirname($file))) throw new ExceptionStartup("fileUncreatable", dirname($file));
throw new ExceptionStartup("fileMissing", $file);
}
if(!is_readable($file) && !is_writable($file)) throw new Exception("fileUnusable", $file);
if(!is_readable($file)) throw new Exception("fileUnreadable", $file);
if(!is_writable($file)) throw new Exception("fileUnwritable", $file);
if(!is_readable($file) && !is_writable($file)) throw new ExceptionStartup("fileUnusable", $file);
if(!is_readable($file)) throw new ExceptionStartup("fileUnreadable", $file);
if(!is_writable($file)) throw new ExceptionStartup("fileUnwritable", $file);
// 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 {
$name = str_replace(Driver::class, "", static::class);
return Lang::msg("Driver.Db.$name.Name");
return Lang::msg("Driver.Db.SQLite3.Name");
}
public function schemaVersion(): int {
return $this->query("PRAGMA user_version")->getSingle();
return $this->query("PRAGMA user_version")->getValue();
}
public function schemaUpdate(int $to): bool {
$ver = $this->schemaVersion();
if(!$this->data->conf->dbSQLite3AutoUpd) throw new Update\Exception("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(!$this->data->conf->dbSQLite3AutoUpd) throw new ExceptionUpdate("manual", ['version' => $ver, '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;
$path = \JKingWeb\NewsSync\BASE."sql".$sep."SQLite3".$sep;
$this->lock();
@ -58,10 +69,10 @@ class DriverSQLite3 extends AbstractDriver {
$this->begin();
try {
$file = $path.$a.".sql";
if(!file_exists($file)) throw new Update\Exception("fileMissing", ['file' => $file, 'driver_name' => $this->driverName()]);
if(!is_readable($file)) throw new Update\Exception("fileUnreadable", ['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 ExceptionUpdate("fileUnreadable", ['file' => $file, 'driver_name' => $this->driverName()]);
$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);
} catch(\Throwable $e) {
// 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
$this->commit(true);
// throw the error received
// FIXME: This should create the relevant type of SQL exception
throw $e;
}
$this->commit();
@ -82,11 +94,11 @@ class DriverSQLite3 extends AbstractDriver {
return (bool) $this->db->exec($query);
}
public function query(string $query): Result {
return new ResultSQLite3($this->db->query($query), $this->db->changes());
public function query(string $query): \JKingWeb\NewsSync\Db\Result {
return new Result($this->db->query($query), $this->db->changes());
}
public function prepareArray(string $query, array $paramTypes): Statement {
return new StatementSQLite3($this->db, $this->db->prepare($query), $paramTypes);
public function prepareArray(string $query, array $paramTypes): \JKingWeb\NewsSync\Db\Statement {
return new Statement($this->db, $this->db->prepare($query), $paramTypes);
}
}

6
lib/Db/ResultSQLite3.php → lib/Db/SQLite3/Result.php

@ -1,8 +1,8 @@
<?php
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 $set;
protected $pos = 0;
@ -40,7 +40,7 @@ class ResultSQLite3 implements Result {
// 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->set = $result;
$this->rows = $changes;

9
lib/Db/StatementSQLite3.php → lib/Db/SQLite3/Statement.php

@ -1,8 +1,9 @@
<?php
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 = [
"null" => \SQLITE3_NULL,
"integer" => \SQLITE3_INTEGER,
@ -38,7 +39,7 @@ class StatementSQLite3 extends AbstractStatement {
])[$part];
}
public function runArray(array $values = null): Result {
public function runArray(array $values = null): \JKingWeb\NewsSync\Db\Result {
$this->st->clear();
$l = sizeof($values);
for($a = 0; $a < $l; $a++) {
@ -58,6 +59,6 @@ class StatementSQLite3 extends AbstractStatement {
// perform binding
$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);
}
}

6
lib/Db/Update/Exception.php

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

10
lib/User.php

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

43
lib/User/DriverInternal.php

@ -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
}

43
lib/User/Internal/Driver.php

@ -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
}

2
lib/User/InternalFunctions.php → lib/User/Internal/InternalFunctions.php

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

26
locale/en.php

@ -21,31 +21,31 @@ return [
'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/Db/Exception.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/Exception.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/Exception.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/Exception.fileCorrupt' => 'Database file "{0}" is corrupt or not a valid database',
'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.extMissing' => 'Required PHP extension for driver "{0}" not installed',
'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileMissing' => 'Database file "{0}" does not exist',
'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileUnreadable' => 'Insufficient permissions to open database file "{0}" for reading',
'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileUnwritable' => 'Insufficient permissions to open database file "{0}" for writing',
'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileUnusable' => 'Insufficient permissions to open database file "{0}" for reading or writing',
'Exception.JKingWeb/NewsSync/Db/ExceptionStartup.fileUncreatable' => 'Insufficient permissions to create new database file "{0}"',
'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.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/Update/Exception.manual' =>
'Exception.JKingWeb/NewsSync/Db/ExceptionUpdate.manual' =>
'{from_version, select,
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}}
}',
'Exception.JKingWeb/NewsSync/Db/Update/Exception.manualOnly' =>
'Exception.JKingWeb/NewsSync/Db/ExceptionUpdate.manualOnly' =>
'{from_version, select,
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}}
}',
'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/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/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/Update/Exception.tooNew' =>
'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/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/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/ExceptionUpdate.tooNew' =>
'{difference, select,
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}}

102
sql/SQLite3/0.sql

@ -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
create table newssync_feeds(
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
);
-- 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
create table newssync_articles(
id integer primary key not null, -- sequence number
@ -40,57 +85,6 @@ create table newssync_enclosures(
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
create table newssync_subscription_articles(
id integer primary key not null,
@ -108,6 +102,12 @@ create table newssync_labels(
);
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
pragma user_version = 1;
insert into newssync_settings values('schema_version',1,'int');

14
tests/Db/SQLite3/TestDbResultSQLite3.php

@ -21,20 +21,20 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
function testConstructResult() {
$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() {
$this->c->query("CREATE TABLE test(col)");
$set = $this->c->query("INSERT INTO test(col) values(1)");
$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() {
$set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col");
$rows = [];
foreach(new Db\ResultSQLite3($set) as $row) {
foreach(new Db\SQLite3\Result($set) as $row) {
$rows[] = $row['col'];
}
$this->assertEquals([1,2,3], $rows);
@ -43,7 +43,7 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
function testIterateOverResultsTwice() {
$set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col");
$rows = [];
$test = new Db\ResultSQLite3($set);
$test = new Db\SQLite3\Result($set);
foreach($test as $row) {
$rows[] = $row['col'];
}
@ -55,7 +55,7 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
function testGetSingleValues() {
$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(1970, $test->getValue());
$this->assertEquals(2112, $test->getValue());
@ -64,7 +64,7 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
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");
$test = new Db\ResultSQLite3($set);
$test = new Db\SQLite3\Result($set);
$this->assertEquals(1867, $test->getValue());
$this->assertEquals(1970, $test->getValue());
$this->assertEquals(2112, $test->getValue());
@ -77,7 +77,7 @@ class TestDbResultSQLite3 extends \PHPUnit\Framework\TestCase {
['album' => '2112', 'track' => '2112'],
['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[1], $test->get());
$this->assertSame(null, $test->get());

4
tests/Db/SQLite3/TestDbStatementSQLite3.php

@ -8,7 +8,7 @@ class TestDbStatementSQLite3 extends \PHPUnit\Framework\TestCase {
use Test\Tools, Test\Db\BindingTests;
protected $c;
static protected $imp = Db\StatementSQLite3::class;
static protected $imp = Db\SQLite3\Statement::class;
function setUp() {
date_default_timezone_set("UTC");
@ -36,7 +36,7 @@ class TestDbStatementSQLite3 extends \PHPUnit\Framework\TestCase {
function testConstructStatement() {
$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() {

2
tests/User/TestUserInternalDriver.php

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

Loading…
Cancel
Save