mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Preliminary implementation of TTRSS sessions (needs tests; may not work)
This commit is contained in:
parent
c393dfc42b
commit
474d32e54f
9 changed files with 327 additions and 23 deletions
|
@ -24,7 +24,9 @@
|
|||
"ext-hash": "*",
|
||||
"fguillot/picofeed": ">=0.1.31",
|
||||
"hosteurope/password-generator": "^1.0",
|
||||
"docopt/docopt": "^1.0"
|
||||
"docopt/docopt": "^1.0",
|
||||
"jkingweb/druuid": "^3.0",
|
||||
"phpseclib/phpseclib": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mikey179/vfsStream": "^1.6",
|
||||
|
|
240
composer.lock
generated
240
composer.lock
generated
|
@ -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": "125797db6f29f530c2f89209cc4f462d",
|
||||
"content-hash": "d00fd63e825db5ce16878c1639f362f3",
|
||||
"packages": [
|
||||
{
|
||||
"name": "docopt/docopt",
|
||||
|
@ -145,6 +145,143 @@
|
|||
"description": "Password generator for generating policy-compliant passwords.",
|
||||
"time": "2016-12-08T09:32:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jkingweb/druuid",
|
||||
"version": "3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/JKingweb/DrUUID.git",
|
||||
"reference": "ca88019069f03ee9c0b1bb6b0200f421bbc9607e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/JKingweb/DrUUID/zipball/ca88019069f03ee9c0b1bb6b0200f421bbc9607e",
|
||||
"reference": "ca88019069f03ee9c0b1bb6b0200f421bbc9607e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Supported alternative to GMP on 32-bit systems",
|
||||
"ext-gmp": "Recommended on 32-bit installations for time-base UUIDs",
|
||||
"phpseclib/phpseclib": "Supported alternative to GMP or BC Math on 32-bit systems (either v1.x or v2.x)"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JKingWeb\\DrUUID\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "J. King",
|
||||
"email": "jking@jkingweb.ca",
|
||||
"homepage": "https://jkingweb.ca/"
|
||||
}
|
||||
],
|
||||
"description": "DrUUID RFC 4122 library for PHP",
|
||||
"keywords": [
|
||||
"uuid"
|
||||
],
|
||||
"time": "2017-02-09T14:17:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "2.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "34a7699e6f31b1ef4035ee36444407cecf9f56aa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/34a7699e6f31b1ef4035ee36444407cecf9f56aa",
|
||||
"reference": "34a7699e6f31b1ef4035ee36444407cecf9f56aa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phing/phing": "~2.7",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"sami/sami": "~2.0",
|
||||
"squizlabs/php_codesniffer": "~2.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
|
||||
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
|
||||
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
|
||||
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"phpseclib/bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"phpseclib\\": "phpseclib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Wigginton",
|
||||
"email": "terrafrost@php.net",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Patrick Monnerat",
|
||||
"email": "pm@datasphere.ch",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Andreas Fischer",
|
||||
"email": "bantu@phpbb.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Hans-Jürgen Petrich",
|
||||
"email": "petrich@tronic-media.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "graham@alt-three.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
|
||||
"homepage": "http://phpseclib.sourceforge.net",
|
||||
"keywords": [
|
||||
"BigInteger",
|
||||
"aes",
|
||||
"asn.1",
|
||||
"asn1",
|
||||
"blowfish",
|
||||
"crypto",
|
||||
"cryptography",
|
||||
"encryption",
|
||||
"rsa",
|
||||
"security",
|
||||
"sftp",
|
||||
"signature",
|
||||
"signing",
|
||||
"ssh",
|
||||
"twofish",
|
||||
"x.509",
|
||||
"x509"
|
||||
],
|
||||
"time": "2017-06-05T06:31:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "zendframework/zendxml",
|
||||
"version": "1.0.2",
|
||||
|
@ -310,6 +447,68 @@
|
|||
],
|
||||
"time": "2012-12-19T10:50:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "1.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "c7cb9a2095a074d131b65a8a0cd294479d785573"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573",
|
||||
"reference": "c7cb9a2095a074d131b65a8a0cd294479d785573",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.2 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.5 || ^5.0.5",
|
||||
"phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Composer\\Semver\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nils Adermann",
|
||||
"email": "naderman@naderman.de",
|
||||
"homepage": "http://www.naderman.de"
|
||||
},
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
},
|
||||
{
|
||||
"name": "Rob Bast",
|
||||
"email": "rob.bast@gmail.com",
|
||||
"homepage": "http://robbast.nl"
|
||||
}
|
||||
],
|
||||
"description": "Semver library that offers utilities, version constraint parsing and validation.",
|
||||
"keywords": [
|
||||
"semantic",
|
||||
"semver",
|
||||
"validation",
|
||||
"versioning"
|
||||
],
|
||||
"time": "2016-08-30T16:08:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "container-interop/container-interop",
|
||||
"version": "1.2.0",
|
||||
|
@ -561,19 +760,20 @@
|
|||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v2.2.6",
|
||||
"version": "v2.2.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
|
||||
"reference": "c1cc52c242f17c4d52d9601159631da488fac7a4"
|
||||
"reference": "b6202ccad4c00778887e7e8282d52f854802b59a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c1cc52c242f17c4d52d9601159631da488fac7a4",
|
||||
"reference": "c1cc52c242f17c4d52d9601159631da488fac7a4",
|
||||
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/b6202ccad4c00778887e7e8282d52f854802b59a",
|
||||
"reference": "b6202ccad4c00778887e7e8282d52f854802b59a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer/semver": "^1.4",
|
||||
"doctrine/annotations": "^1.2",
|
||||
"ext-json": "*",
|
||||
"ext-tokenizer": "*",
|
||||
|
@ -641,7 +841,7 @@
|
|||
}
|
||||
],
|
||||
"description": "A tool to automatically fix PHP code style",
|
||||
"time": "2017-08-22T14:08:16+00:00"
|
||||
"time": "2017-09-11T14:27:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "gecko-packages/gecko-php-unit",
|
||||
|
@ -1599,16 +1799,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phake/phake",
|
||||
"version": "v3.0.0",
|
||||
"version": "v3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mlively/Phake.git",
|
||||
"reference": "c242d6a8376bd3280d903d95725d3e1e2f9efadc"
|
||||
"reference": "949340efc3cd99b401a0dd1a5ffeac690a3c3967"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mlively/Phake/zipball/c242d6a8376bd3280d903d95725d3e1e2f9efadc",
|
||||
"reference": "c242d6a8376bd3280d903d95725d3e1e2f9efadc",
|
||||
"url": "https://api.github.com/repos/mlively/Phake/zipball/949340efc3cd99b401a0dd1a5ffeac690a3c3967",
|
||||
"reference": "949340efc3cd99b401a0dd1a5ffeac690a3c3967",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1653,7 +1853,7 @@
|
|||
"mock",
|
||||
"testing"
|
||||
],
|
||||
"time": "2017-07-04T20:09:48+00:00"
|
||||
"time": "2017-09-06T12:09:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
|
@ -2226,22 +2426,22 @@
|
|||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
"version": "v1.7.0",
|
||||
"version": "v1.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpspec/prophecy.git",
|
||||
"reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
|
||||
"reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
|
||||
"reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
|
||||
"reference": "c9b8c6088acd19d769d4cc0ffa60a9fe34344bd6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/instantiator": "^1.0.2",
|
||||
"php": "^5.3|^7.0",
|
||||
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
|
||||
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
|
||||
"sebastian/comparator": "^1.1|^2.0",
|
||||
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
|
||||
},
|
||||
|
@ -2252,7 +2452,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.6.x-dev"
|
||||
"dev-master": "1.7.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -2285,7 +2485,7 @@
|
|||
"spy",
|
||||
"stub"
|
||||
],
|
||||
"time": "2017-03-02T20:05:34+00:00"
|
||||
"time": "2017-09-04T11:05:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
|
@ -3764,7 +3964,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
"version": "v3.3.8",
|
||||
"version": "v3.3.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/options-resolver.git",
|
||||
|
@ -4340,7 +4540,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v3.3.8",
|
||||
"version": "v3.3.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
|
|
|
@ -56,6 +56,7 @@ abstract class AbstractException extends \Exception {
|
|||
"User/Exception.authMissing" => 10411,
|
||||
"User/Exception.authFailed" => 10412,
|
||||
"User/ExceptionAuthz.notAuthorized" => 10421,
|
||||
"User/ExceptionSession.invalid" => 10431,
|
||||
"Feed/Exception.invalidCertificate" => 10501,
|
||||
"Feed/Exception.invalidUrl" => 10502,
|
||||
"Feed/Exception.maxRedirect" => 10503,
|
||||
|
|
|
@ -28,10 +28,16 @@ class Conf {
|
|||
public $userPreAuth = false;
|
||||
/** @var integer Desired length of temporary user passwords */
|
||||
public $userTempPasswordLength = 20;
|
||||
/** @var string Period of inactivity after which log-in sessions should be considered invalid, as an ISO 8601 duration (default: 1 hour)
|
||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||
public $userSessionTimeout = "PT1H";
|
||||
/** @var string Maximum lifetime of log-in sessions regardless of activity, as an ISO 8601 duration (default: 24 hours);
|
||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||
public $userSessionLifetime = "PT24H";
|
||||
|
||||
/** @var string Class of the background feed update service driver in use (Forking by default) */
|
||||
public $serviceDriver = Service\Forking\Driver::class;
|
||||
/** @var string The interval between checks for new feeds, as an ISO 8601 duration
|
||||
/** @var string The interval between checks for new articles, as an ISO 8601 duration
|
||||
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations */
|
||||
public $serviceFrequency = "PT2M";
|
||||
/** @var integer Number of concurrent feed updates to perform */
|
||||
|
|
|
@ -3,6 +3,7 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\Arsse;
|
||||
|
||||
use PasswordGenerator\Generator as PassGen;
|
||||
use JKingWeb\DrUUID\UUID;
|
||||
use JKingWeb\Arsse\Misc\Query;
|
||||
use JKingWeb\Arsse\Misc\Context;
|
||||
use JKingWeb\Arsse\Misc\Date;
|
||||
|
@ -223,6 +224,61 @@ class Database {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function sessionCreate(string $user): string {
|
||||
// If the user isn't authorized to perform this action then throw an exception.
|
||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||
}
|
||||
// generate a new session ID and expiry date
|
||||
$id = UUID::mint()->hex;
|
||||
$expires = Date::add(Arsse::$conf->userSessionTimeout);
|
||||
// save the session to the database
|
||||
$this->db->prepare("INSERT INTO arsse_sessions(id,expires,user) values(?,?,?)", "str", "datetime", "str")->run($id, $expires, $user);
|
||||
// return the ID
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function sessionDestroy(string $user, string $id): bool {
|
||||
// If the user isn't authorized to perform this action then throw an exception.
|
||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||
throw new User\ExceptionAuthz("notAuthorized", ["action" => __FUNCTION__, "user" => $user]);
|
||||
}
|
||||
// delete the session and report success.
|
||||
return (bool) $this->db->prepare("DELETE FROM arsse_sessions where id is ? and user is ?", "str", "str")->run($id, $user)->changes();
|
||||
}
|
||||
|
||||
public function sessionResume(string $id): array {
|
||||
$maxage = Date::sub(Arsse::$conf->userSessionLifetime);
|
||||
$out = $this->db->prepare("SELECT * from arsse_sessions where id is ? and expires > CURRENT_TIMESTAMP and created > ?", "str", "datetime")->run($id, $maxage)->getRow();
|
||||
// if the session does not exist or is expired, throw an exception
|
||||
if (!$out) {
|
||||
throw new User\ExceptionSession("invalid", $id);
|
||||
}
|
||||
// otherwise populate the session user when appropriate
|
||||
if (Arsse::$user) {
|
||||
Arsse::$user->id = $out['user'];
|
||||
}
|
||||
// if we're more than half-way from the session expiring, renew it
|
||||
if ($this->sessionExpiringSoon(Date::normalize($out['expires'], "sql"))) {
|
||||
$expires = Date::add(Arsse::$conf->userSessionTimeout);
|
||||
$this->db->prepare("UPDATE arsse_sessions set expires = ? where id is ?", "datetime", "str")->run($expires, $id);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function sessionCleanup(): int {
|
||||
return $this->db->query("DELETE FROM arsse_sessions where expires < CURRENT_TIMESTAMP")->changes();
|
||||
}
|
||||
|
||||
protected function sessionExpiringSoon(DateTimeInterface $expiry): bool {
|
||||
// calculate half the session timeout as a number of seconds
|
||||
$now = time();
|
||||
$max = Date::add(Arsse::$conf->userSessionTimeout, $now)->getTimestamp();
|
||||
$diff = intdiv($max - $now, 2);
|
||||
// determine if the expiry time is less than half the session timeout into the future
|
||||
return (($now + $diff) >= $expiry->getTimestamp());
|
||||
}
|
||||
|
||||
public function folderAdd(string $user, array $data): int {
|
||||
// If the user isn't authorized to perform this action then throw an exception.
|
||||
if (!Arsse::$user->authorize($user, __FUNCTION__)) {
|
||||
|
|
|
@ -84,7 +84,10 @@ class Service {
|
|||
|
||||
public static function cleanupPre(): bool {
|
||||
// mark unsubscribed feeds as orphaned and delete orphaned feeds that are beyond their retention period
|
||||
return Arsse::$db->feedCleanup();
|
||||
Arsse::$db->feedCleanup();
|
||||
// delete expired log-in sessions
|
||||
Arsse::$db->sessionCleanup();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function cleanupPost(): bool {
|
||||
|
|
6
lib/User/ExceptionSession.php
Normal file
6
lib/User/ExceptionSession.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\User;
|
||||
|
||||
class ExceptionSession extends Exception {
|
||||
}
|
|
@ -137,6 +137,7 @@ return [
|
|||
}}
|
||||
other {Authenticated user is not authorized to perform the action "{action}" on behalf of {user}}
|
||||
}',
|
||||
'Exception.JKingWeb/Arsse/User/ExceptionSession.invalid' => 'Session with ID {0} does not exist',
|
||||
'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',
|
||||
|
|
29
sql/SQLite3/1.sql
Normal file
29
sql/SQLite3/1.sql
Normal file
|
@ -0,0 +1,29 @@
|
|||
-- Sessions for Tiny Tiny RSS (and possibly others)
|
||||
create table arsse_sessions (
|
||||
id text primary key, -- UUID of session
|
||||
created datetime not null default CURRENT_TIMESTAMP, -- Session start timestamp
|
||||
expires datetime not null, -- Time at which session is no longer valid
|
||||
user text not null references arsse_users(id) on delete cascade on update cascade, -- user associated with the session
|
||||
) without rowid;
|
||||
|
||||
-- User-defined article labels for Tiny Tiny RSS
|
||||
create table arsse_labels (
|
||||
id integer primary key, -- numeric ID
|
||||
owner text not null references arsse_users(id) on delete cascade on update cascade, -- owning user
|
||||
name text not null, -- label text
|
||||
foreground text, -- foreground (text) colour in hexdecimal RGB
|
||||
background text, -- background colour in hexadecimal RGB
|
||||
unique(owner,name)
|
||||
);
|
||||
|
||||
-- Labels assignments for articles
|
||||
create table arsse_label_members (
|
||||
label integer not null references arsse_labels(id) on delete cascade,
|
||||
article integer not null references arsse_articles(id) on delete cascade,
|
||||
subscription integer not null references arsse_subscriptions(id) on delete cascade, -- Subscription is included so that records are deleted when a subscription is removed
|
||||
primary key(label,article)
|
||||
) without rowid;
|
||||
|
||||
-- set version marker
|
||||
pragma user_version = 2;
|
||||
insert into arsse_meta(key,value) values('schema_version','2');
|
Loading…
Reference in a new issue