Add Postgres database support
This commit is contained in:
parent
c8234cc3c3
commit
fbe69b54dc
@ -16,4 +16,5 @@ before_script:
|
|||||||
- composer install
|
- composer install
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./vendor/bin/phpunit -c tests/phpunit.unit.xml
|
- ./vendor/bin/phpunit -c tests/phpunit.unit.sqlite.xml
|
||||||
|
- ./vendor/bin/phpunit -c tests/phpunit.unit.postgres.xml
|
||||||
|
7
Makefile
7
Makefile
@ -5,6 +5,7 @@
|
|||||||
.PHONY: docker-run
|
.PHONY: docker-run
|
||||||
.PHONY: js
|
.PHONY: js
|
||||||
.PHONY: unit-test-sqlite
|
.PHONY: unit-test-sqlite
|
||||||
|
.PHONY: unit-test-postgres
|
||||||
|
|
||||||
JS_FILE = assets/js/all.js
|
JS_FILE = assets/js/all.js
|
||||||
CONTAINER = miniflux
|
CONTAINER = miniflux
|
||||||
@ -39,4 +40,8 @@ archive:
|
|||||||
@ git archive --format=zip --prefix=miniflux/ v${version} -o ${dst}/miniflux-${version}.zip
|
@ git archive --format=zip --prefix=miniflux/ v${version} -o ${dst}/miniflux-${version}.zip
|
||||||
|
|
||||||
unit-test-sqlite:
|
unit-test-sqlite:
|
||||||
@ ./vendor/bin/phpunit -c tests/phpunit.unit.xml
|
@ ./vendor/bin/phpunit -c tests/phpunit.unit.sqlite.xml
|
||||||
|
|
||||||
|
unit-test-postgres:
|
||||||
|
@ ./vendor/bin/phpunit -c tests/phpunit.unit.postgres.xml
|
||||||
|
|
||||||
|
@ -11,29 +11,9 @@ require_once __DIR__.'/check_setup.php';
|
|||||||
require_once __DIR__.'/functions.php';
|
require_once __DIR__.'/functions.php';
|
||||||
|
|
||||||
PicoDb\Database::setInstance('db', function () {
|
PicoDb\Database::setInstance('db', function () {
|
||||||
$db = new PicoDb\Database(array(
|
try {
|
||||||
'driver' => 'sqlite',
|
return Miniflux\Database\get_connection();
|
||||||
'filename' => DB_FILENAME,
|
} catch (Exception $e) {
|
||||||
));
|
die($e->getMessage());
|
||||||
|
|
||||||
$db->getStatementHandler()->withLogging();
|
|
||||||
|
|
||||||
if ($db->schema('\Miniflux\Schema')->check(Miniflux\Schema\VERSION)) {
|
|
||||||
return $db;
|
|
||||||
} else {
|
|
||||||
$errors = $db->getLogMessages();
|
|
||||||
$nb_errors = count($errors);
|
|
||||||
|
|
||||||
$html = 'Unable to migrate the database schema, <strong>please copy and paste this message and create a bug report:</strong><hr/>';
|
|
||||||
$html .= '<pre><code>';
|
|
||||||
$html .= (isset($errors[$nb_errors - 1]) ? $errors[$nb_errors - 1] : 'Unknown SQL error').PHP_EOL.PHP_EOL;
|
|
||||||
$html .= '- PHP version: '.phpversion().PHP_EOL;
|
|
||||||
$html .= '- SAPI: '.php_sapi_name().PHP_EOL;
|
|
||||||
$html .= '- PDO Sqlite version: '.phpversion('pdo_sqlite').PHP_EOL;
|
|
||||||
$html .= '- Sqlite version: '.$db->getDriver()->getDatabaseVersion().PHP_EOL;
|
|
||||||
$html .= '- OS: '.php_uname();
|
|
||||||
$html .= '</code></pre>';
|
|
||||||
|
|
||||||
die($html);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,13 @@ defined('DATA_DIRECTORY') or define('DATA_DIRECTORY', ROOT_DIRECTORY.DIRECTORY_S
|
|||||||
defined('FAVICON_DIRECTORY') or define('FAVICON_DIRECTORY', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'favicons');
|
defined('FAVICON_DIRECTORY') or define('FAVICON_DIRECTORY', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'favicons');
|
||||||
defined('FAVICON_URL_PATH') or define('FAVICON_URL_PATH', 'data/favicons');
|
defined('FAVICON_URL_PATH') or define('FAVICON_URL_PATH', 'data/favicons');
|
||||||
|
|
||||||
|
defined('DB_DRIVER') or define('DB_DRIVER', 'sqlite');
|
||||||
defined('DB_FILENAME') or define('DB_FILENAME', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'db.sqlite');
|
defined('DB_FILENAME') or define('DB_FILENAME', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'db.sqlite');
|
||||||
|
defined('DB_HOSTNAME') or define('DB_HOSTNAME', 'localhost');
|
||||||
|
defined('DB_PORT') or define('DB_PORT', null);
|
||||||
|
defined('DB_NAME') or define('DB_NAME', 'miniflux');
|
||||||
|
defined('DB_USERNAME') or define('DB_USERNAME', '');
|
||||||
|
defined('DB_PASSWORD') or define('DB_PASSWORD', '');
|
||||||
|
|
||||||
defined('DEBUG_MODE') or define('DEBUG_MODE', false);
|
defined('DEBUG_MODE') or define('DEBUG_MODE', false);
|
||||||
defined('DEBUG_FILENAME') or define('DEBUG_FILENAME', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'debug.log');
|
defined('DEBUG_FILENAME') or define('DEBUG_FILENAME', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'debug.log');
|
||||||
|
@ -197,7 +197,7 @@ Router\action('subscribe', function () {
|
|||||||
$values['rtl'],
|
$values['rtl'],
|
||||||
$values['cloak_referrer'],
|
$values['cloak_referrer'],
|
||||||
$values['feed_group_ids'],
|
$values['feed_group_ids'],
|
||||||
$values['groups']
|
$values['group_name']
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($feed_id >= 1) {
|
if ($feed_id >= 1) {
|
||||||
|
@ -222,21 +222,13 @@ Router\get_action('mark-item-removed', function () {
|
|||||||
Response\redirect('?action='.$redirect.'&offset='.$offset.'&feed_id='.$feed_id);
|
Response\redirect('?action='.$redirect.'&offset='.$offset.'&feed_id='.$feed_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
Router\post_action('latest-feeds-items', function () {
|
Router\get_action('latest-feeds-items', function () {
|
||||||
$user_id = SessionStorage::getInstance()->getUserId();
|
$user_id = SessionStorage::getInstance()->getUserId();
|
||||||
$items = Model\Item\get_latest_feeds_items($user_id);
|
$items_timestamps = Model\Item\get_latest_unread_items_timestamps($user_id);
|
||||||
$nb_unread_items = Model\Item\count_by_status($user_id, 'unread');
|
$nb_unread_items = Model\Item\count_by_status($user_id, 'unread');
|
||||||
|
|
||||||
$feeds = array_reduce($items, function ($result, $item) {
|
|
||||||
$result[$item['id']] = array(
|
|
||||||
'time' => $item['updated'] ?: 0,
|
|
||||||
'status' => $item['status']
|
|
||||||
);
|
|
||||||
return $result;
|
|
||||||
}, array());
|
|
||||||
|
|
||||||
Response\json(array(
|
Response\json(array(
|
||||||
'feeds' => $feeds,
|
'last_items_timestamps' => $items_timestamps,
|
||||||
'nbUnread' => $nb_unread_items
|
'nb_unread_items' => $nb_unread_items
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
45
app/core/database.php
Normal file
45
app/core/database.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Miniflux\Database;
|
||||||
|
|
||||||
|
use Miniflux\Schema;
|
||||||
|
use RuntimeException;
|
||||||
|
use PicoDb;
|
||||||
|
|
||||||
|
function get_connection()
|
||||||
|
{
|
||||||
|
$db = new PicoDb\Database(get_connection_parameters());
|
||||||
|
$db->getStatementHandler()->withLogging();
|
||||||
|
|
||||||
|
if ($db->schema('\Miniflux\Schema')->check(Schema\VERSION)) {
|
||||||
|
return $db;
|
||||||
|
} else {
|
||||||
|
$errors = $db->getLogMessages();
|
||||||
|
$nb_errors = count($errors);
|
||||||
|
$last_error = isset($errors[$nb_errors - 1]) ? $errors[$nb_errors - 1] : 'Unknown SQL error';
|
||||||
|
throw new RuntimeException('Unable to migrate the database schema: '.$last_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_connection_parameters()
|
||||||
|
{
|
||||||
|
if (DB_DRIVER === 'postgres') {
|
||||||
|
require_once __DIR__.'/../schemas/postgres.php';
|
||||||
|
$params = array(
|
||||||
|
'driver' => 'postgres',
|
||||||
|
'hostname' => DB_HOSTNAME,
|
||||||
|
'username' => DB_USERNAME,
|
||||||
|
'password' => DB_PASSWORD,
|
||||||
|
'database' => DB_NAME,
|
||||||
|
'port' => DB_PORT,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
require_once __DIR__.'/../schemas/sqlite.php';
|
||||||
|
$params = array(
|
||||||
|
'driver' => 'sqlite',
|
||||||
|
'filename' => DB_FILENAME,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
@ -7,9 +7,11 @@ use Miniflux\Session\SessionStorage;
|
|||||||
|
|
||||||
function config($parameter, $default = null)
|
function config($parameter, $default = null)
|
||||||
{
|
{
|
||||||
$session = SessionStorage::getInstance();
|
|
||||||
$cache = $session->getConfig();
|
|
||||||
$value = null;
|
$value = null;
|
||||||
|
$session = SessionStorage::getInstance();
|
||||||
|
|
||||||
|
if ($session->isLogged()) {
|
||||||
|
$cache = $session->getConfig();
|
||||||
|
|
||||||
if (empty($cache)) {
|
if (empty($cache)) {
|
||||||
$cache = Model\Config\get_all($session->getUserId());
|
$cache = Model\Config\get_all($session->getUserId());
|
||||||
@ -19,6 +21,7 @@ function config($parameter, $default = null)
|
|||||||
if (array_key_exists($parameter, $cache)) {
|
if (array_key_exists($parameter, $cache)) {
|
||||||
$value = $cache[$parameter];
|
$value = $cache[$parameter];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($value === null) {
|
if ($value === null) {
|
||||||
$value = $default;
|
$value = $default;
|
||||||
|
@ -46,26 +46,42 @@ function get_feeds($user_id)
|
|||||||
return Database::getInstance('db')
|
return Database::getInstance('db')
|
||||||
->table(TABLE)
|
->table(TABLE)
|
||||||
->eq('user_id', $user_id)
|
->eq('user_id', $user_id)
|
||||||
|
->desc('parsing_error')
|
||||||
|
->desc('enabled')
|
||||||
->asc('title')
|
->asc('title')
|
||||||
->findAll();
|
->findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_feeds_with_items_count($user_id)
|
function get_feeds_with_items_count($user_id)
|
||||||
{
|
{
|
||||||
return Database::getInstance('db')
|
$feeds_count = array();
|
||||||
->table(TABLE)
|
$feeds = get_feeds($user_id);
|
||||||
->columns(
|
$feed_items = Database::getInstance('db')
|
||||||
'feeds.*',
|
->table(Item\TABLE)
|
||||||
'SUM(CASE WHEN items.status IN ("unread") THEN 1 ELSE 0 END) as "items_unread"',
|
->columns('feed_id', 'status', 'count(*) as nb')
|
||||||
'SUM(CASE WHEN items.status IN ("read", "unread") THEN 1 ELSE 0 END) as "items_total"'
|
->eq('user_id', $user_id)
|
||||||
)
|
->neq('status', Item\STATUS_REMOVED)
|
||||||
->join('items', 'feed_id', 'id')
|
->groupBy('feed_id', 'status')
|
||||||
->eq('feeds.user_id', $user_id)
|
|
||||||
->groupBy('feeds.id')
|
|
||||||
->desc('feeds.parsing_error')
|
|
||||||
->desc('feeds.enabled')
|
|
||||||
->asc('feeds.title')
|
|
||||||
->findAll();
|
->findAll();
|
||||||
|
|
||||||
|
foreach ($feed_items as $row) {
|
||||||
|
if (! isset($feeds_count[$row['feed_id']])) {
|
||||||
|
$feeds_count[$row['feed_id']] = array('unread' => 0, 'total' => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row['status'] === 'unread') {
|
||||||
|
$feeds_count[$row['feed_id']]['unread'] = $row['nb'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$feeds_count[$row['feed_id']]['total'] += $row['nb'];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($feeds as &$feed) {
|
||||||
|
$feed['items_unread'] = isset($feeds_count[$feed['id']]) ? $feeds_count[$feed['id']]['unread'] : 0;
|
||||||
|
$feed['items_total'] = isset($feeds_count[$feed['id']]) ? $feeds_count[$feed['id']]['total'] : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $feeds;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_feed_ids($user_id, $limit = null)
|
function get_feed_ids($user_id, $limit = null)
|
||||||
|
@ -297,19 +297,18 @@ function get_item_ids_by_status($user_id, $status)
|
|||||||
->findAllByColumn('id');
|
->findAllByColumn('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_latest_feeds_items($user_id)
|
function get_latest_unread_items_timestamps($user_id)
|
||||||
{
|
{
|
||||||
return Database::getInstance('db')
|
return Database::getInstance('db')
|
||||||
->table(Feed\TABLE)
|
->table(TABLE)
|
||||||
->columns(
|
->columns(
|
||||||
'feeds.id',
|
'feed_id',
|
||||||
'MAX(items.updated) as updated',
|
'MAX(updated) as updated'
|
||||||
'items.status'
|
|
||||||
)
|
)
|
||||||
->join(TABLE, 'feed_id', 'id')
|
->eq('user_id', $user_id)
|
||||||
->eq('feeds.user_id', $user_id)
|
->eq('status', STATUS_UNREAD)
|
||||||
->groupBy('feeds.id')
|
->groupBy('feed_id')
|
||||||
->orderBy('feeds.id')
|
->desc('updated')
|
||||||
->findAll();
|
->findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
133
app/schemas/postgres.php
Normal file
133
app/schemas/postgres.php
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Miniflux\Schema;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
use Miniflux\Helper;
|
||||||
|
|
||||||
|
const VERSION = 1;
|
||||||
|
|
||||||
|
function version_1(PDO $pdo)
|
||||||
|
{
|
||||||
|
$pdo->exec("CREATE TABLE users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
is_admin BOOLEAN DEFAULT FALSE,
|
||||||
|
last_login BIGINT,
|
||||||
|
api_token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
bookmarklet_token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
cronjob_token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
feed_token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
fever_token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
fever_api_key VARCHAR(255) NOT NULL UNIQUE
|
||||||
|
)");
|
||||||
|
|
||||||
|
$pdo->exec('CREATE TABLE user_settings (
|
||||||
|
"user_id" INTEGER NOT NULL,
|
||||||
|
"key" VARCHAR(255) NOT NULL,
|
||||||
|
"value" TEXT NOT NULL,
|
||||||
|
PRIMARY KEY("user_id", "key"),
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)');
|
||||||
|
|
||||||
|
$pdo->exec('CREATE TABLE feeds (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
feed_url VARCHAR(255) NOT NULL,
|
||||||
|
site_url VARCHAR(255),
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
last_checked BIGINT DEFAULT 0,
|
||||||
|
last_modified VARCHAR(255),
|
||||||
|
etag VARCHAR(255),
|
||||||
|
enabled BOOLEAN DEFAULT TRUE,
|
||||||
|
download_content BOOLEAN DEFAULT FALSE,
|
||||||
|
parsing_error BOOLEAN DEFAULT FALSE,
|
||||||
|
rtl BOOLEAN DEFAULT FALSE,
|
||||||
|
cloak_referrer BOOLEAN DEFAULT FALSE,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(user_id, feed_url)
|
||||||
|
)');
|
||||||
|
|
||||||
|
$pdo->exec('CREATE TABLE items (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
feed_id BIGINT NOT NULL,
|
||||||
|
checksum VARCHAR(255) NOT NULL,
|
||||||
|
status VARCHAR(10) NOT NULL,
|
||||||
|
bookmark INTEGER DEFAULT 0,
|
||||||
|
url VARCHAR(255) NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
author VARCHAR(255),
|
||||||
|
content TEXT,
|
||||||
|
updated BIGINT,
|
||||||
|
enclosure_url VARCHAR(255),
|
||||||
|
enclosure_type VARCHAR(50),
|
||||||
|
language VARCHAR(50),
|
||||||
|
rtl INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(feed_id) REFERENCES feeds(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(feed_id, checksum)
|
||||||
|
)');
|
||||||
|
|
||||||
|
$pdo->exec('CREATE TABLE "groups" (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(user_id, title)
|
||||||
|
)');
|
||||||
|
|
||||||
|
$pdo->exec('CREATE TABLE "feeds_groups" (
|
||||||
|
feed_id BIGINT NOT NULL,
|
||||||
|
group_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY(feed_id, group_id),
|
||||||
|
FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(feed_id) REFERENCES feeds(id) ON DELETE CASCADE
|
||||||
|
)');
|
||||||
|
|
||||||
|
$pdo->exec('CREATE TABLE favicons (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
hash VARCHAR(255) UNIQUE,
|
||||||
|
type VARCHAR(50)
|
||||||
|
)');
|
||||||
|
|
||||||
|
$pdo->exec('CREATE TABLE "favicons_feeds" (
|
||||||
|
feed_id BIGINT NOT NULL,
|
||||||
|
favicon_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY(feed_id, favicon_id),
|
||||||
|
FOREIGN KEY(favicon_id) REFERENCES favicons(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(feed_id) REFERENCES feeds(id) ON DELETE CASCADE
|
||||||
|
)');
|
||||||
|
|
||||||
|
$pdo->exec('CREATE TABLE remember_me (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
ip VARCHAR(255),
|
||||||
|
user_agent VARCHAR(255),
|
||||||
|
token VARCHAR(255),
|
||||||
|
sequence VARCHAR(255),
|
||||||
|
expiration BIGINT,
|
||||||
|
date_creation BIGINT,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
)');
|
||||||
|
|
||||||
|
$fever_token = Helper\generate_token();
|
||||||
|
$rq = $pdo->prepare('
|
||||||
|
INSERT INTO users
|
||||||
|
(username, password, is_admin, api_token, bookmarklet_token, cronjob_token, feed_token, fever_token, fever_api_key)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
');
|
||||||
|
|
||||||
|
$rq->execute(array(
|
||||||
|
'admin',
|
||||||
|
password_hash('admin', PASSWORD_BCRYPT),
|
||||||
|
'1',
|
||||||
|
Helper\generate_token(),
|
||||||
|
Helper\generate_token(),
|
||||||
|
Helper\generate_token(),
|
||||||
|
Helper\generate_token(),
|
||||||
|
$fever_token,
|
||||||
|
md5('admin:'.$fever_token),
|
||||||
|
));
|
||||||
|
}
|
@ -11,7 +11,7 @@ function version_1(PDO $pdo)
|
|||||||
{
|
{
|
||||||
$pdo->exec('CREATE TABLE users (
|
$pdo->exec('CREATE TABLE users (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
username TEXT UNIQUE,
|
username TEXT NOT NULL UNIQUE,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
is_admin INTEGER DEFAULT 0,
|
is_admin INTEGER DEFAULT 0,
|
||||||
last_login INTEGER,
|
last_login INTEGER,
|
||||||
@ -36,7 +36,7 @@ function version_1(PDO $pdo)
|
|||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
feed_url TEXT NOT NULL,
|
feed_url TEXT NOT NULL,
|
||||||
site_url TEXT,
|
site_url TEXT,
|
||||||
title TEXT COLLATE NOCASE,
|
title TEXT COLLATE NOCASE NOT NULL,
|
||||||
last_checked INTEGER DEFAULT 0,
|
last_checked INTEGER DEFAULT 0,
|
||||||
last_modified TEXT,
|
last_modified TEXT,
|
||||||
etag TEXT,
|
etag TEXT,
|
||||||
@ -54,10 +54,10 @@ function version_1(PDO $pdo)
|
|||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
feed_id INTEGER NOT NULL,
|
feed_id INTEGER NOT NULL,
|
||||||
checksum TEXT NOT NULL,
|
checksum TEXT NOT NULL,
|
||||||
status TEXT,
|
status TEXT NOT NULL,
|
||||||
bookmark INTEGER DEFAULT 0,
|
bookmark INTEGER DEFAULT 0,
|
||||||
url TEXT,
|
url TEXT NOT NULL,
|
||||||
title TEXT COLLATE NOCASE,
|
title TEXT COLLATE NOCASE NOT NULL,
|
||||||
author TEXT,
|
author TEXT,
|
||||||
content TEXT,
|
content TEXT,
|
||||||
updated INTEGER,
|
updated INTEGER,
|
||||||
@ -73,7 +73,7 @@ function version_1(PDO $pdo)
|
|||||||
$pdo->exec('CREATE TABLE "groups" (
|
$pdo->exec('CREATE TABLE "groups" (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
title TEXT COLLATE NOCASE,
|
title TEXT COLLATE NOCASE NOT NULL,
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
UNIQUE(user_id, title)
|
UNIQUE(user_id, title)
|
||||||
)');
|
)');
|
||||||
|
@ -113,7 +113,7 @@ Miniflux.Feed = (function() {
|
|||||||
Miniflux.Item = (function() {
|
Miniflux.Item = (function() {
|
||||||
|
|
||||||
// timestamp of the latest item per feed ever seen
|
// timestamp of the latest item per feed ever seen
|
||||||
var latest_feeds_items = [];
|
var latest_feeds_items = {};
|
||||||
|
|
||||||
// indicator for new unread items
|
// indicator for new unread items
|
||||||
var unreadItems = false;
|
var unreadItems = false;
|
||||||
@ -474,27 +474,24 @@ Miniflux.Item = (function() {
|
|||||||
var first_run = (latest_feeds_items.length === 0);
|
var first_run = (latest_feeds_items.length === 0);
|
||||||
var current_unread = false;
|
var current_unread = false;
|
||||||
var response = JSON.parse(this.responseText);
|
var response = JSON.parse(this.responseText);
|
||||||
|
var last_items_timestamps = response['last_items_timestamps'];
|
||||||
|
|
||||||
for (var feed_id in response['feeds']) {
|
for (var i = 0; i < last_items_timestamps.length; i++) {
|
||||||
var current_feed = response['feeds'][feed_id];
|
var current_feed = last_items_timestamps[i];
|
||||||
|
var feed_id = current_feed.feed_id;
|
||||||
|
|
||||||
if (! latest_feeds_items.hasOwnProperty(feed_id) || current_feed.time > latest_feeds_items[feed_id]) {
|
if (! latest_feeds_items.hasOwnProperty(feed_id) || current_feed.updated > latest_feeds_items[feed_id]) {
|
||||||
Miniflux.App.Log('feed ' + feed_id + ': New item(s)');
|
latest_feeds_items[feed_id] = current_feed.updated;
|
||||||
latest_feeds_items[feed_id] = current_feed.time;
|
|
||||||
|
|
||||||
if (current_feed.status === 'unread') {
|
|
||||||
Miniflux.App.Log('feed ' + feed_id + ': New unread item(s)');
|
|
||||||
current_unread = true;
|
current_unread = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Miniflux.App.Log('first_run: ' + first_run + ', current_unread: ' + current_unread + ', response.nbUnread: ' + response['nbUnread'] + ', nbUnreadItems: ' + nbUnreadItems);
|
Miniflux.App.Log('first_run: ' + first_run + ', current_unread: ' + current_unread + ', response.nbUnread: ' + response['nbUnread'] + ', nbUnreadItems: ' + nbUnreadItems);
|
||||||
|
|
||||||
if (! document.hidden && (response['nbUnread'] !== nbUnreadItems || unreadItems)) {
|
if (! document.hidden && (response['nb_unread_items'] !== nbUnreadItems || unreadItems)) {
|
||||||
Miniflux.App.Log('Counter changed! Updating unread counter.');
|
Miniflux.App.Log('Counter changed! Updating unread counter.');
|
||||||
unreadItems = false;
|
unreadItems = false;
|
||||||
nbUnreadItems = response['nbUnread'];
|
nbUnreadItems = response['nb_unread_items'];
|
||||||
updateCounters();
|
updateCounters();
|
||||||
}
|
}
|
||||||
else if (document.hidden && ! first_run && current_unread) {
|
else if (document.hidden && ! first_run && current_unread) {
|
||||||
@ -509,7 +506,7 @@ Miniflux.Item = (function() {
|
|||||||
Miniflux.App.Log('unreadItems: ' + unreadItems);
|
Miniflux.App.Log('unreadItems: ' + unreadItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
request.open("POST", "?action=latest-feeds-items", true);
|
request.open("GET", "?action=latest-feeds-items", true);
|
||||||
request.send();
|
request.send();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
Miniflux.Item = (function() {
|
Miniflux.Item = (function() {
|
||||||
|
|
||||||
// timestamp of the latest item per feed ever seen
|
// timestamp of the latest item per feed ever seen
|
||||||
var latest_feeds_items = [];
|
var latest_feeds_items = {};
|
||||||
|
|
||||||
// indicator for new unread items
|
// indicator for new unread items
|
||||||
var unreadItems = false;
|
var unreadItems = false;
|
||||||
@ -362,27 +362,24 @@ Miniflux.Item = (function() {
|
|||||||
var first_run = (latest_feeds_items.length === 0);
|
var first_run = (latest_feeds_items.length === 0);
|
||||||
var current_unread = false;
|
var current_unread = false;
|
||||||
var response = JSON.parse(this.responseText);
|
var response = JSON.parse(this.responseText);
|
||||||
|
var last_items_timestamps = response['last_items_timestamps'];
|
||||||
|
|
||||||
for (var feed_id in response['feeds']) {
|
for (var i = 0; i < last_items_timestamps.length; i++) {
|
||||||
var current_feed = response['feeds'][feed_id];
|
var current_feed = last_items_timestamps[i];
|
||||||
|
var feed_id = current_feed.feed_id;
|
||||||
|
|
||||||
if (! latest_feeds_items.hasOwnProperty(feed_id) || current_feed.time > latest_feeds_items[feed_id]) {
|
if (! latest_feeds_items.hasOwnProperty(feed_id) || current_feed.updated > latest_feeds_items[feed_id]) {
|
||||||
Miniflux.App.Log('feed ' + feed_id + ': New item(s)');
|
latest_feeds_items[feed_id] = current_feed.updated;
|
||||||
latest_feeds_items[feed_id] = current_feed.time;
|
|
||||||
|
|
||||||
if (current_feed.status === 'unread') {
|
|
||||||
Miniflux.App.Log('feed ' + feed_id + ': New unread item(s)');
|
|
||||||
current_unread = true;
|
current_unread = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Miniflux.App.Log('first_run: ' + first_run + ', current_unread: ' + current_unread + ', response.nbUnread: ' + response['nbUnread'] + ', nbUnreadItems: ' + nbUnreadItems);
|
Miniflux.App.Log('first_run: ' + first_run + ', current_unread: ' + current_unread + ', response.nbUnread: ' + response['nbUnread'] + ', nbUnreadItems: ' + nbUnreadItems);
|
||||||
|
|
||||||
if (! document.hidden && (response['nbUnread'] !== nbUnreadItems || unreadItems)) {
|
if (! document.hidden && (response['nb_unread_items'] !== nbUnreadItems || unreadItems)) {
|
||||||
Miniflux.App.Log('Counter changed! Updating unread counter.');
|
Miniflux.App.Log('Counter changed! Updating unread counter.');
|
||||||
unreadItems = false;
|
unreadItems = false;
|
||||||
nbUnreadItems = response['nbUnread'];
|
nbUnreadItems = response['nb_unread_items'];
|
||||||
updateCounters();
|
updateCounters();
|
||||||
}
|
}
|
||||||
else if (document.hidden && ! first_run && current_unread) {
|
else if (document.hidden && ! first_run && current_unread) {
|
||||||
@ -397,7 +394,7 @@ Miniflux.Item = (function() {
|
|||||||
Miniflux.App.Log('unreadItems: ' + unreadItems);
|
Miniflux.App.Log('unreadItems: ' + unreadItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
request.open("POST", "?action=latest-feeds-items", true);
|
request.open("GET", "?action=latest-feeds-items", true);
|
||||||
request.send();
|
request.send();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -26,13 +26,13 @@
|
|||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"files": [
|
"files": [
|
||||||
"app/schemas/sqlite.php",
|
|
||||||
"app/helpers/app.php",
|
"app/helpers/app.php",
|
||||||
"app/helpers/config.php",
|
"app/helpers/config.php",
|
||||||
"app/helpers/csrf.php",
|
"app/helpers/csrf.php",
|
||||||
"app/helpers/favicon.php",
|
"app/helpers/favicon.php",
|
||||||
"app/helpers/form.php",
|
"app/helpers/form.php",
|
||||||
"app/helpers/template.php",
|
"app/helpers/template.php",
|
||||||
|
"app/core/database.php",
|
||||||
"app/core/translator.php",
|
"app/core/translator.php",
|
||||||
"app/core/request.php",
|
"app/core/request.php",
|
||||||
"app/core/response.php",
|
"app/core/response.php",
|
||||||
|
@ -7,7 +7,7 @@ define('HTTP_TIMEOUT', '20');
|
|||||||
define('HTTP_MAX_RESPONSE_SIZE', 2097152);
|
define('HTTP_MAX_RESPONSE_SIZE', 2097152);
|
||||||
|
|
||||||
// DATA_DIRECTORY => default is data (writable directory)
|
// DATA_DIRECTORY => default is data (writable directory)
|
||||||
define('DATA_DIRECTORY', __DIR__.'/data');
|
define('DATA_DIRECTORY', 'data');
|
||||||
|
|
||||||
// FAVICON_DIRECTORY => default is favicons (writable directory)
|
// FAVICON_DIRECTORY => default is favicons (writable directory)
|
||||||
define('FAVICON_DIRECTORY', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'favicons');
|
define('FAVICON_DIRECTORY', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'favicons');
|
||||||
@ -15,8 +15,17 @@ define('FAVICON_DIRECTORY', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'favicons');
|
|||||||
// FAVICON_URL_PATH => default is data/favicons/
|
// FAVICON_URL_PATH => default is data/favicons/
|
||||||
define('FAVICON_URL_PATH', 'data/favicons');
|
define('FAVICON_URL_PATH', 'data/favicons');
|
||||||
|
|
||||||
// DB_FILENAME => default value is db.sqlite (default database filename)
|
// Database driver: "sqlite" or "postgres", default is sqlite
|
||||||
define('DB_FILENAME', 'db.sqlite');
|
define('DB_DRIVER', 'sqlite');
|
||||||
|
|
||||||
|
// Database connection parameters when Postgres is used
|
||||||
|
define('DB_HOSTNAME', 'localhost');
|
||||||
|
define('DB_NAME', 'miniflux');
|
||||||
|
define('DB_USERNAME', 'postgres');
|
||||||
|
define('DB_PASSWORD', '');
|
||||||
|
|
||||||
|
// DB_FILENAME => database file when Sqlite is used
|
||||||
|
define('DB_FILENAME', DATA_DIRECTORY.'/db.sqlite');
|
||||||
|
|
||||||
// Enable/disable debug mode
|
// Enable/disable debug mode
|
||||||
define('DEBUG_MODE', false);
|
define('DEBUG_MODE', false);
|
||||||
|
13
tests/phpunit.unit.postgres.xml
Normal file
13
tests/phpunit.unit.postgres.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<phpunit stopOnError="true" stopOnFailure="true" colors="true">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Miniflux Postgres Unit Tests">
|
||||||
|
<directory>unit</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<php>
|
||||||
|
<const name="DB_DRIVER" value="postgres" />
|
||||||
|
<const name="DB_USERNAME" value="postgres" />
|
||||||
|
<const name="DB_PASSWORD" value="" />
|
||||||
|
<const name="DB_NAME" value="miniflux_unit_test" />
|
||||||
|
</php>
|
||||||
|
</phpunit>
|
@ -1,6 +1,6 @@
|
|||||||
<phpunit stopOnError="true" stopOnFailure="true" colors="true">
|
<phpunit stopOnError="true" stopOnFailure="true" colors="true">
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Miniflux Unit Tests">
|
<testsuite name="Miniflux Sqlite Unit Tests">
|
||||||
<directory>unit</directory>
|
<directory>unit</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
@ -12,20 +12,18 @@ abstract class BaseTest extends PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
public function setUp()
|
public function setUp()
|
||||||
{
|
{
|
||||||
SessionStorage::getInstance()->flush();
|
if (DB_DRIVER === 'postgres') {
|
||||||
|
$pdo = new PDO('pgsql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD);
|
||||||
|
$pdo->exec('DROP DATABASE '.DB_NAME);
|
||||||
|
$pdo->exec('CREATE DATABASE '.DB_NAME.' WITH OWNER '.DB_USERNAME);
|
||||||
|
$pdo = null;
|
||||||
|
}
|
||||||
|
|
||||||
PicoDb\Database::setInstance('db', function () {
|
PicoDb\Database::setInstance('db', function () {
|
||||||
$db = new PicoDb\Database(array(
|
return Miniflux\Database\get_connection();
|
||||||
'driver' => 'sqlite',
|
|
||||||
'filename' => DB_FILENAME,
|
|
||||||
));
|
|
||||||
|
|
||||||
$db->getStatementHandler()->withLogging();
|
|
||||||
if (! $db->schema('\Miniflux\Schema')->check(Miniflux\Schema\VERSION)) {
|
|
||||||
var_dump($db->getLogMessages());
|
|
||||||
}
|
|
||||||
return $db;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SessionStorage::getInstance()->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
public function tearDown()
|
||||||
|
@ -26,10 +26,10 @@ class FeedModelTest extends BaseTest
|
|||||||
$this->assertEquals('feed url', $subscription['feed_url']);
|
$this->assertEquals('feed url', $subscription['feed_url']);
|
||||||
$this->assertEquals('etag', $subscription['etag']);
|
$this->assertEquals('etag', $subscription['etag']);
|
||||||
$this->assertEquals('last modified', $subscription['last_modified']);
|
$this->assertEquals('last modified', $subscription['last_modified']);
|
||||||
$this->assertEquals('0', $subscription['download_content']);
|
$this->assertFalse((bool) $subscription['download_content']);
|
||||||
$this->assertEquals('0', $subscription['rtl']);
|
$this->assertFalse((bool) $subscription['rtl']);
|
||||||
$this->assertEquals('0', $subscription['cloak_referrer']);
|
$this->assertFalse((bool) $subscription['cloak_referrer']);
|
||||||
$this->assertEquals('1', $subscription['enabled']);
|
$this->assertTrue((bool) $subscription['enabled']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetAll()
|
public function testGetAll()
|
||||||
|
@ -27,7 +27,7 @@ class ItemModelTest extends BaseTest
|
|||||||
$this->assertEquals('', $item['enclosure_type']);
|
$this->assertEquals('', $item['enclosure_type']);
|
||||||
$this->assertEquals('', $item['language']);
|
$this->assertEquals('', $item['language']);
|
||||||
|
|
||||||
$item = Model\Item\get_item(2, 'ID 1');
|
$item = Model\Item\get_item(2, 1);
|
||||||
$this->assertNull($item);
|
$this->assertNull($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
vendor/composer/autoload_files.php
vendored
2
vendor/composer/autoload_files.php
vendored
@ -7,13 +7,13 @@ $baseDir = dirname($vendorDir);
|
|||||||
|
|
||||||
return array(
|
return array(
|
||||||
'e40631d46120a9c38ea139981f8dab26' => $vendorDir . '/ircmaxell/password-compat/lib/password.php',
|
'e40631d46120a9c38ea139981f8dab26' => $vendorDir . '/ircmaxell/password-compat/lib/password.php',
|
||||||
'1c6478a893aa3a9ae898668d7af7b441' => $baseDir . '/app/schemas/sqlite.php',
|
|
||||||
'f0aaf41fd213d5a97cf56c060c89445c' => $baseDir . '/app/helpers/app.php',
|
'f0aaf41fd213d5a97cf56c060c89445c' => $baseDir . '/app/helpers/app.php',
|
||||||
'640b6eca1c41bc50addc30eb8c8e3b5b' => $baseDir . '/app/helpers/config.php',
|
'640b6eca1c41bc50addc30eb8c8e3b5b' => $baseDir . '/app/helpers/config.php',
|
||||||
'5d8e3de7e01942090f1b7b4dbdfb5577' => $baseDir . '/app/helpers/csrf.php',
|
'5d8e3de7e01942090f1b7b4dbdfb5577' => $baseDir . '/app/helpers/csrf.php',
|
||||||
'3f7c6586b45b98746d18450089390313' => $baseDir . '/app/helpers/favicon.php',
|
'3f7c6586b45b98746d18450089390313' => $baseDir . '/app/helpers/favicon.php',
|
||||||
'e96e91c85d691b966391981f72a31834' => $baseDir . '/app/helpers/form.php',
|
'e96e91c85d691b966391981f72a31834' => $baseDir . '/app/helpers/form.php',
|
||||||
'b8d3001d29a919647064eeaec3f6551e' => $baseDir . '/app/helpers/template.php',
|
'b8d3001d29a919647064eeaec3f6551e' => $baseDir . '/app/helpers/template.php',
|
||||||
|
'a79069799c6e73d978adffa1caf9894d' => $baseDir . '/app/core/database.php',
|
||||||
'7793918f03299c5c8c900e412e166b4f' => $baseDir . '/app/core/translator.php',
|
'7793918f03299c5c8c900e412e166b4f' => $baseDir . '/app/core/translator.php',
|
||||||
'69d59b59e8b15a53cc782e283523018d' => $baseDir . '/app/core/request.php',
|
'69d59b59e8b15a53cc782e283523018d' => $baseDir . '/app/core/request.php',
|
||||||
'3fd802226a33a97ae29d1f8315ff66a9' => $baseDir . '/app/core/response.php',
|
'3fd802226a33a97ae29d1f8315ff66a9' => $baseDir . '/app/core/response.php',
|
||||||
|
2
vendor/composer/autoload_static.php
vendored
2
vendor/composer/autoload_static.php
vendored
@ -8,13 +8,13 @@ class ComposerStaticInitfd7e8d436e1dc450edc3153ac8bc31b4
|
|||||||
{
|
{
|
||||||
public static $files = array (
|
public static $files = array (
|
||||||
'e40631d46120a9c38ea139981f8dab26' => __DIR__ . '/..' . '/ircmaxell/password-compat/lib/password.php',
|
'e40631d46120a9c38ea139981f8dab26' => __DIR__ . '/..' . '/ircmaxell/password-compat/lib/password.php',
|
||||||
'1c6478a893aa3a9ae898668d7af7b441' => __DIR__ . '/../..' . '/app/schemas/sqlite.php',
|
|
||||||
'f0aaf41fd213d5a97cf56c060c89445c' => __DIR__ . '/../..' . '/app/helpers/app.php',
|
'f0aaf41fd213d5a97cf56c060c89445c' => __DIR__ . '/../..' . '/app/helpers/app.php',
|
||||||
'640b6eca1c41bc50addc30eb8c8e3b5b' => __DIR__ . '/../..' . '/app/helpers/config.php',
|
'640b6eca1c41bc50addc30eb8c8e3b5b' => __DIR__ . '/../..' . '/app/helpers/config.php',
|
||||||
'5d8e3de7e01942090f1b7b4dbdfb5577' => __DIR__ . '/../..' . '/app/helpers/csrf.php',
|
'5d8e3de7e01942090f1b7b4dbdfb5577' => __DIR__ . '/../..' . '/app/helpers/csrf.php',
|
||||||
'3f7c6586b45b98746d18450089390313' => __DIR__ . '/../..' . '/app/helpers/favicon.php',
|
'3f7c6586b45b98746d18450089390313' => __DIR__ . '/../..' . '/app/helpers/favicon.php',
|
||||||
'e96e91c85d691b966391981f72a31834' => __DIR__ . '/../..' . '/app/helpers/form.php',
|
'e96e91c85d691b966391981f72a31834' => __DIR__ . '/../..' . '/app/helpers/form.php',
|
||||||
'b8d3001d29a919647064eeaec3f6551e' => __DIR__ . '/../..' . '/app/helpers/template.php',
|
'b8d3001d29a919647064eeaec3f6551e' => __DIR__ . '/../..' . '/app/helpers/template.php',
|
||||||
|
'a79069799c6e73d978adffa1caf9894d' => __DIR__ . '/../..' . '/app/core/database.php',
|
||||||
'7793918f03299c5c8c900e412e166b4f' => __DIR__ . '/../..' . '/app/core/translator.php',
|
'7793918f03299c5c8c900e412e166b4f' => __DIR__ . '/../..' . '/app/core/translator.php',
|
||||||
'69d59b59e8b15a53cc782e283523018d' => __DIR__ . '/../..' . '/app/core/request.php',
|
'69d59b59e8b15a53cc782e283523018d' => __DIR__ . '/../..' . '/app/core/request.php',
|
||||||
'3fd802226a33a97ae29d1f8315ff66a9' => __DIR__ . '/../..' . '/app/core/response.php',
|
'3fd802226a33a97ae29d1f8315ff66a9' => __DIR__ . '/../..' . '/app/core/response.php',
|
||||||
|
Loading…
Reference in New Issue
Block a user