if($this->db->prepare("DELETE from newssync_users where id is ?", "str")->run($user)->changes() <1)thrownewUser\Exception("doesNotExist",["action"=> __FUNCTION__, "user" => $user]);
if($this->db->prepare("DELETE from arsse_users where id is ?", "str")->run($user)->changes() <1)thrownewUser\Exception("doesNotExist",["action"=> __FUNCTION__, "user" => $user]);
// check if a folder by the same name already exists, because nulls are wonky in SQL
// FIXME: How should folder name be compared? Should a Unicode normalization be applied before comparison and insertion?
if($this->db->prepare("SELECT count(*) from newssync_folders where owner is ? and parent is ? and name is ?", "str", "int", "str")->run($user, $parent, $data['name'])->getValue() > 0) {
if($this->db->prepare("SELECT count(*) from arsse_folders where owner is ? and parent is ? and name is ?", "str", "int", "str")->run($user, $parent, $data['name'])->getValue() > 0) {
throw new Db\ExceptionInput("constraintViolation"); // FIXME: There needs to be a practical message here
public function folderList(string $user, int $parent = null, bool $recursive = true): Db\Result {
@ -354,11 +354,11 @@ class Database {
}
// if we're not returning a recursive list we can use a simpler query
if(!$recursive) {
return $this->db->preparre("SELECT id,name,parent from newssync_folders where owner is ? and parent is ?", "str", "int")->run($user, $parent);
return $this->db->preparre("SELECT id,name,parent from arsse_folders where owner is ? and parent is ?", "str", "int")->run($user, $parent);
} else {
return $this->db->prepare(
"WITH RECURSIVE folders(id) as (SELECT id from newssync_folders where owner is ? and parent is ? union select newssync_folders.id from newssync_folders join folders on newssync_folders.parent=folders.id) ".
"SELECT id,name,parent from newssync_folders where id in(SELECT id from folders) order by name",
"WITH RECURSIVE folders(id) as (SELECT id from arsse_folders where owner is ? and parent is ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id) ".
"SELECT id,name,parent from arsse_folders where id in(SELECT id from folders) order by name",
"str", "int")->run($user, $parent);
}
}
@ -366,7 +366,7 @@ class Database {
public function articleAdd(PicoFeed\Parser\Item $article): int {
$this->db->begin();
$articleId = $this->db->prepare('INSERT INTO newssync_articles(feed,url,title,author,published,edited,guid,content,url_title_hash,url_content_hash,title_content_hash)
$articleId = $this->db->prepare('INSERT INTO arsse_articles(feed,url,title,author,published,edited,guid,content,url_title_hash,url_content_hash,title_content_hash)
$this->db->prepare('INSERT INTO newssync_tags(article,name) values(?,?)', 'int', 'str')->run($articleId, $c);
$this->db->prepare('INSERT INTO arsse_tags(article,name) values(?,?)', 'int', 'str')->run($articleId, $c);
}
}
@ -395,7 +395,7 @@ class Database {
}
public function updateFeeds(): int {
$feeds = $this->db->query('SELECT id, url, username, password, DATEFORMAT("http", modified) AS lastmodified, etag FROM newssync_feeds')->getAll();
$feeds = $this->db->query('SELECT id, url, username, password, DATEFORMAT("http", modified) AS lastmodified, etag FROM arsse_feeds')->getAll();
foreach ($feeds as $f) {
$feed = new Feed($f['url'], $f['lastmodified'], $f['etag'], $f['username'], $f['password']);
// FIXME: What to do if fails? It currently throws an exception which isn't ideal here.
@ -405,7 +405,7 @@ class Database {
$feed->parse();
$this->db->begin();
$articles = $this->db->prepare('SELECT id, url, title, author, DATEFORMAT("http", edited) AS edited_date, guid, content, url_title_hash, url_content_hash, title_content_hash FROM newssync_articles WHERE feed is ? ORDER BY id', 'int')->run($f['id'])->getAll();
$articles = $this->db->prepare('SELECT id, url, title, author, DATEFORMAT("http", edited) AS edited_date, guid, content, url_title_hash, url_content_hash, title_content_hash FROM arsse_articles WHERE feed is ? ORDER BY id', 'int')->run($f['id'])->getAll();
foreach ($feed->data->items as $i) {
// Iterate through the articles in the database to determine a match for the one
@ -449,7 +449,7 @@ class Database {
}
if ($update) {
$this->db->prepare('UPDATE newssync_articles SET url = ?, title = ?, author = ?, published = ?, edited = ?, modified = ?, guid = ?, content = ?, url_title_hash = ?, url_content_hash = ?, title_content_hash = ? WHERE id is ?', 'str', 'str', 'str', 'datetime', 'datetime', 'datetime', 'str', 'str', 'str', 'str', 'str', 'int')->run(
$this->db->prepare('UPDATE arsse_articles SET url = ?, title = ?, author = ?, published = ?, edited = ?, modified = ?, guid = ?, content = ?, url_title_hash = ?, url_content_hash = ?, title_content_hash = ? WHERE id is ?', 'str', 'str', 'str', 'datetime', 'datetime', 'datetime', 'str', 'str', 'str', 'str', 'str', 'int')->run(
$i->url,
$i->title,
$i->author,
@ -469,7 +469,7 @@ class Database {
}
// Lastly update the feed database itself with updated information.
$this->db->prepare('UPDATE newssync_feeds SET url = ?, title = ?, favicon = ?, source = ?, updated = ?, modified = ?, etag = ? WHERE id is ?', 'str', 'str', 'str', 'str', 'datetime', 'datetime', 'str', 'int')->run(
$this->db->prepare('UPDATE arsse_feeds SET url = ?, title = ?, favicon = ?, source = ?, updated = ?, modified = ?, etag = ? WHERE id is ?', 'str', 'str', 'str', 'str', 'datetime', 'datetime', 'str', 'int')->run(
$feed->feedUrl,
$feed->title,
$feed->favicon,
@ -489,13 +489,13 @@ class Database {
public function folderRemove(string $user, int $id): bool {
// common table expression to list all descendant folders of the target folder
$cte = "RECURSIVE folders(id) as (SELECT id from newssync_folders where owner is ? and id is ? union select newssync_folders.id from newssync_folders join folders on newssync_folders.parent=folders.id) ";
$cte = "RECURSIVE folders(id) as (SELECT id from arsse_folders where owner is ? and id is ? union select arsse_folders.id from arsse_folders join folders on arsse_folders.parent=folders.id) ";
$changes = 0;
$this->db->begin();
// first delete any feed subscriptions contained within the folder tree (this may not be necesary because of foreign keys)
$changes += $this->db->prepare("WITH $cte"."DELETE FROM newssync_subscriptions where folder in(select id from folders)", "str", "int")->run($user, $id)->changes();
$changes += $this->db->prepare("WITH $cte"."DELETE FROM arsse_subscriptions where folder in(select id from folders)", "str", "int")->run($user, $id)->changes();
// next delete the folders themselves
$changes += $this->db->prepare("WITH $cte"."DELETE FROM newssync_folders where id in(select id from folders)", "str", "int")->run($user, $id)->changes();
$changes += $this->db->prepare("WITH $cte"."DELETE FROM arsse_folders where id in(select id from folders)", "str", "int")->run($user, $id)->changes();
const RIGHTS_GLOBAL_ADMIN = 100; // is completely unrestricted
// returns an instance of a class implementing this interface. Implemented as a static method for consistency with database classes
function __construct(\JKingWeb\NewsSync\RuntimeData $data);
function __construct(\JKingWeb\Arsse\RuntimeData $data);
// returns a human-friendly name for the driver (for display in installer, for example)
static function driverName(): string;
// returns an array (or single queried member of same) of methods defined by this interface and whether the class implements the internal function or a custom version
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/Exception.updateFileMissing' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available',
'Exception.JKingWeb/NewsSync/Db/Exception.updateFileUnreadable' => 'Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}',
'Exception.JKingWeb/NewsSync/Db/Exception.updateFileUnusable' => 'Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}',
'Exception.JKingWeb/NewsSync/Db/Exception.updateFileError' => 'Automatic updating of the {driver_name} database failed updating from version {current} with the following error: "{message}"',
'Exception.JKingWeb/NewsSync/Db/Exception.updateFileIncomplete' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} being incomplete',
'Exception.JKingWeb/Arsse/Db/Exception.updateFileMissing' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available',
'Exception.JKingWeb/Arsse/Db/Exception.updateFileUnreadable' => 'Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}',
'Exception.JKingWeb/Arsse/Db/Exception.updateFileUnusable' => 'Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}',
'Exception.JKingWeb/Arsse/Db/Exception.updateFileError' => 'Automatic updating of the {driver_name} database failed updating from version {current} with the following error: "{message}"',
'Exception.JKingWeb/Arsse/Db/Exception.updateFileIncomplete' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} being incomplete',
global {Authenticated user is not authorized to view the global user list}
@ -70,15 +70,15 @@ return [
}}
other {Authenticated user is not authorized to perform the action "{action}" on behalf of {user}}
}',
'Exception.JKingWeb/NewsSync/Feed/Exception.invalidCertificate' => 'Could not download feed "{url}" because its server is serving an invalid SSL certificate',
'Exception.JKingWeb/NewsSync/Feed/Exception.invalidURL' => 'Feed URL "{url}" is invalid',
'Exception.JKingWeb/NewsSync/Feed/Exception.maxRedirect' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections',
'Exception.JKingWeb/NewsSync/Feed/Exception.maxSize' => 'Could not download feed "{url}" because its size exceeds the maximum allowed on its server',
'Exception.JKingWeb/NewsSync/Feed/Exception.timeout' => 'Could not download feed "{url}" because its server timed out',
'Exception.JKingWeb/NewsSync/Feed/Exception.forbidden' => 'Could not download feed "{url}" because you do not have permission to access it',
'Exception.JKingWeb/NewsSync/Feed/Exception.unauthorized' => 'Could not download feed "{url}" because you provided insufficient or invalid credentials',
'Exception.JKingWeb/NewsSync/Feed/Exception.malformed' => 'Could not parse feed "{url}" because it is malformed',
'Exception.JKingWeb/NewsSync/Feed/Exception.xmlEntity' => 'Refused to parse feed "{url}" because it contains an XXE attack',
'Exception.JKingWeb/NewsSync/Feed/Exception.subscriptionNotFound' => 'Unable to find a feed at location "{url}"',
'Exception.JKingWeb/NewsSync/Feed/Exception.unsupportedFormat' => 'Feed "{url}" is of an unsupported format'
'Exception.JKingWeb/Arsse/Feed/Exception.invalidCertificate' => 'Could not download feed "{url}" because its server is serving an invalid SSL certificate',
'Exception.JKingWeb/Arsse/Feed/Exception.invalidURL' => 'Feed URL "{url}" is invalid',
'Exception.JKingWeb/Arsse/Feed/Exception.maxRedirect' => 'Could not download feed "{url}" because its server reached its maximum number of HTTP redirections',
'Exception.JKingWeb/Arsse/Feed/Exception.maxSize' => 'Could not download feed "{url}" because its size exceeds the maximum allowed on its server',
'Exception.JKingWeb/Arsse/Feed/Exception.timeout' => 'Could not download feed "{url}" because its server timed out',
'Exception.JKingWeb/Arsse/Feed/Exception.forbidden' => 'Could not download feed "{url}" because you do not have permission to access it',
'Exception.JKingWeb/Arsse/Feed/Exception.unauthorized' => 'Could not download feed "{url}" because you provided insufficient or invalid credentials',
'Exception.JKingWeb/Arsse/Feed/Exception.malformed' => 'Could not parse feed "{url}" because it is malformed',
'Exception.JKingWeb/Arsse/Feed/Exception.xmlEntity' => 'Refused to parse feed "{url}" because it contains an XXE attack',
'Exception.JKingWeb/Arsse/Feed/Exception.subscriptionNotFound' => 'Unable to find a feed at location "{url}"',
'Exception.JKingWeb/Arsse/Feed/Exception.unsupportedFormat' => 'Feed "{url}" is of an unsupported format'
valuevarchar(255),-- setting value, serialized as a string
typevarchar(255)notnullcheck(
@ -8,7 +8,7 @@ create table newssync_settings(
)withoutrowid;
-- users
createtablenewssync_users(
createtablearsse_users(
idTEXTprimarykeynotnull,-- user id
passwordTEXT,-- password, salted and hashed; if using external authentication this would be blank
nameTEXT,-- display name
@ -19,7 +19,7 @@ create table newssync_users(
)withoutrowid;
-- newsfeeds, deduplicated
createtablenewssync_feeds(
createtablearsse_feeds(
idintegerprimarykeynotnull,-- sequence number
urlTEXTnotnull,-- URL of feed
titleTEXT,-- default title of feed
@ -36,23 +36,23 @@ create table newssync_feeds(
);
-- users' subscriptions to newsfeeds, with settings
createtablenewssync_subscriptions(
createtablearsse_subscriptions(
idintegerprimarykeynotnull,-- sequence number
ownerTEXTnotnullreferencesnewssync_users(id)ondeletecascadeonupdatecascade,-- owner of subscription
feedintegernotnullreferencesnewssync_feeds(id)ondeletecascade,-- feed for the subscription
ownerTEXTnotnullreferencesarsse_users(id)ondeletecascadeonupdatecascade,-- owner of subscription
feedintegernotnullreferencesarsse_feeds(id)ondeletecascade,-- feed for the subscription
addeddatetimenotnulldefaultCURRENT_TIMESTAMP,-- time at which feed was added
modifieddatetimenotnulldefaultCURRENT_TIMESTAMP,-- date at which subscription properties were last modified
titleTEXT,-- user-supplied title
order_typeintnotnulldefault0,-- NextCloud sort order
pinnedbooleannotnulldefault0,-- whether feed is pinned (always sorts at top)
folderintegerreferencesnewssync_folders(id)ondeletecascade,-- TT-RSS category (nestable); the first-level category (which acts as NextCloud folder) is joined in when needed
folderintegerreferencesarsse_folders(id)ondeletecascade,-- TT-RSS category (nestable); the first-level category (which acts as NextCloud folder) is joined in when needed
unique(owner,feed)-- a given feed should only appear once for a given owner
);
-- TT-RSS categories and NextCloud folders
createtablenewssync_folders(
createtablearsse_folders(
idintegerprimarykeynotnull,-- sequence number
ownerTEXTnotnullreferencesnewssync_users(id)ondeletecascadeonupdatecascade,-- owner of folder
ownerTEXTnotnullreferencesarsse_users(id)ondeletecascadeonupdatecascade,-- owner of folder