Add Postgres database support
This commit is contained in:
parent
c8234cc3c3
commit
fbe69b54dc
@ -16,4 +16,5 @@ before_script:
|
||||
- composer install
|
||||
|
||||
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: js
|
||||
.PHONY: unit-test-sqlite
|
||||
.PHONY: unit-test-postgres
|
||||
|
||||
JS_FILE = assets/js/all.js
|
||||
CONTAINER = miniflux
|
||||
@ -39,4 +40,8 @@ archive:
|
||||
@ git archive --format=zip --prefix=miniflux/ v${version} -o ${dst}/miniflux-${version}.zip
|
||||
|
||||
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
|
||||
|
||||
|
@ -10,30 +10,10 @@ require_once __DIR__.'/constants.php';
|
||||
require_once __DIR__.'/check_setup.php';
|
||||
require_once __DIR__.'/functions.php';
|
||||
|
||||
PicoDb\Database::setInstance('db', function() {
|
||||
$db = new PicoDb\Database(array(
|
||||
'driver' => 'sqlite',
|
||||
'filename' => DB_FILENAME,
|
||||
));
|
||||
|
||||
$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);
|
||||
PicoDb\Database::setInstance('db', function () {
|
||||
try {
|
||||
return Miniflux\Database\get_connection();
|
||||
} catch (Exception $e) {
|
||||
die($e->getMessage());
|
||||
}
|
||||
});
|
||||
|
@ -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_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_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_FILENAME') or define('DEBUG_FILENAME', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'debug.log');
|
||||
|
@ -197,7 +197,7 @@ Router\action('subscribe', function () {
|
||||
$values['rtl'],
|
||||
$values['cloak_referrer'],
|
||||
$values['feed_group_ids'],
|
||||
$values['groups']
|
||||
$values['group_name']
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
Router\post_action('latest-feeds-items', function () {
|
||||
Router\get_action('latest-feeds-items', function () {
|
||||
$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');
|
||||
|
||||
$feeds = array_reduce($items, function ($result, $item) {
|
||||
$result[$item['id']] = array(
|
||||
'time' => $item['updated'] ?: 0,
|
||||
'status' => $item['status']
|
||||
);
|
||||
return $result;
|
||||
}, array());
|
||||
|
||||
Response\json(array(
|
||||
'feeds' => $feeds,
|
||||
'nbUnread' => $nb_unread_items
|
||||
'last_items_timestamps' => $items_timestamps,
|
||||
'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,17 +7,20 @@ use Miniflux\Session\SessionStorage;
|
||||
|
||||
function config($parameter, $default = null)
|
||||
{
|
||||
$session = SessionStorage::getInstance();
|
||||
$cache = $session->getConfig();
|
||||
$value = null;
|
||||
$session = SessionStorage::getInstance();
|
||||
|
||||
if (empty($cache)) {
|
||||
$cache = Model\Config\get_all($session->getUserId());
|
||||
$session->setConfig($cache);
|
||||
}
|
||||
if ($session->isLogged()) {
|
||||
$cache = $session->getConfig();
|
||||
|
||||
if (array_key_exists($parameter, $cache)) {
|
||||
$value = $cache[$parameter];
|
||||
if (empty($cache)) {
|
||||
$cache = Model\Config\get_all($session->getUserId());
|
||||
$session->setConfig($cache);
|
||||
}
|
||||
|
||||
if (array_key_exists($parameter, $cache)) {
|
||||
$value = $cache[$parameter];
|
||||
}
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
|
@ -46,26 +46,42 @@ function get_feeds($user_id)
|
||||
return Database::getInstance('db')
|
||||
->table(TABLE)
|
||||
->eq('user_id', $user_id)
|
||||
->desc('parsing_error')
|
||||
->desc('enabled')
|
||||
->asc('title')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
function get_feeds_with_items_count($user_id)
|
||||
{
|
||||
return Database::getInstance('db')
|
||||
->table(TABLE)
|
||||
->columns(
|
||||
'feeds.*',
|
||||
'SUM(CASE WHEN items.status IN ("unread") THEN 1 ELSE 0 END) as "items_unread"',
|
||||
'SUM(CASE WHEN items.status IN ("read", "unread") THEN 1 ELSE 0 END) as "items_total"'
|
||||
)
|
||||
->join('items', 'feed_id', 'id')
|
||||
->eq('feeds.user_id', $user_id)
|
||||
->groupBy('feeds.id')
|
||||
->desc('feeds.parsing_error')
|
||||
->desc('feeds.enabled')
|
||||
->asc('feeds.title')
|
||||
$feeds_count = array();
|
||||
$feeds = get_feeds($user_id);
|
||||
$feed_items = Database::getInstance('db')
|
||||
->table(Item\TABLE)
|
||||
->columns('feed_id', 'status', 'count(*) as nb')
|
||||
->eq('user_id', $user_id)
|
||||
->neq('status', Item\STATUS_REMOVED)
|
||||
->groupBy('feed_id', 'status')
|
||||
->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)
|
||||
|
@ -297,19 +297,18 @@ function get_item_ids_by_status($user_id, $status)
|
||||
->findAllByColumn('id');
|
||||
}
|
||||
|
||||
function get_latest_feeds_items($user_id)
|
||||
function get_latest_unread_items_timestamps($user_id)
|
||||
{
|
||||
return Database::getInstance('db')
|
||||
->table(Feed\TABLE)
|
||||
->table(TABLE)
|
||||
->columns(
|
||||
'feeds.id',
|
||||
'MAX(items.updated) as updated',
|
||||
'items.status'
|
||||
'feed_id',
|
||||
'MAX(updated) as updated'
|
||||
)
|
||||
->join(TABLE, 'feed_id', 'id')
|
||||
->eq('feeds.user_id', $user_id)
|
||||
->groupBy('feeds.id')
|
||||
->orderBy('feeds.id')
|
||||
->eq('user_id', $user_id)
|
||||
->eq('status', STATUS_UNREAD)
|
||||
->groupBy('feed_id')
|
||||
->desc('updated')
|
||||
->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 (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT UNIQUE,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
is_admin INTEGER DEFAULT 0,
|
||||
last_login INTEGER,
|
||||
@ -36,7 +36,7 @@ function version_1(PDO $pdo)
|
||||
user_id INTEGER NOT NULL,
|
||||
feed_url TEXT NOT NULL,
|
||||
site_url TEXT,
|
||||
title TEXT COLLATE NOCASE,
|
||||
title TEXT COLLATE NOCASE NOT NULL,
|
||||
last_checked INTEGER DEFAULT 0,
|
||||
last_modified TEXT,
|
||||
etag TEXT,
|
||||
@ -54,10 +54,10 @@ function version_1(PDO $pdo)
|
||||
user_id INTEGER NOT NULL,
|
||||
feed_id INTEGER NOT NULL,
|
||||
checksum TEXT NOT NULL,
|
||||
status TEXT,
|
||||
status TEXT NOT NULL,
|
||||
bookmark INTEGER DEFAULT 0,
|
||||
url TEXT,
|
||||
title TEXT COLLATE NOCASE,
|
||||
url TEXT NOT NULL,
|
||||
title TEXT COLLATE NOCASE NOT NULL,
|
||||
author TEXT,
|
||||
content TEXT,
|
||||
updated INTEGER,
|
||||
@ -73,13 +73,13 @@ function version_1(PDO $pdo)
|
||||
$pdo->exec('CREATE TABLE "groups" (
|
||||
id INTEGER PRIMARY KEY,
|
||||
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,
|
||||
UNIQUE(user_id, title)
|
||||
)');
|
||||
|
||||
$pdo->exec('CREATE TABLE "feeds_groups" (
|
||||
feed_id INTEGER NOT NULL,
|
||||
feed_id INTEGER NOT NULL,
|
||||
group_id INTEGER NOT NULL,
|
||||
PRIMARY KEY(feed_id, group_id),
|
||||
FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE,
|
||||
|
@ -113,7 +113,7 @@ Miniflux.Feed = (function() {
|
||||
Miniflux.Item = (function() {
|
||||
|
||||
// timestamp of the latest item per feed ever seen
|
||||
var latest_feeds_items = [];
|
||||
var latest_feeds_items = {};
|
||||
|
||||
// indicator for new unread items
|
||||
var unreadItems = false;
|
||||
@ -474,27 +474,24 @@ Miniflux.Item = (function() {
|
||||
var first_run = (latest_feeds_items.length === 0);
|
||||
var current_unread = false;
|
||||
var response = JSON.parse(this.responseText);
|
||||
var last_items_timestamps = response['last_items_timestamps'];
|
||||
|
||||
for (var feed_id in response['feeds']) {
|
||||
var current_feed = response['feeds'][feed_id];
|
||||
for (var i = 0; i < last_items_timestamps.length; i++) {
|
||||
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]) {
|
||||
Miniflux.App.Log('feed ' + feed_id + ': New item(s)');
|
||||
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;
|
||||
}
|
||||
if (! latest_feeds_items.hasOwnProperty(feed_id) || current_feed.updated > latest_feeds_items[feed_id]) {
|
||||
latest_feeds_items[feed_id] = current_feed.updated;
|
||||
current_unread = true;
|
||||
}
|
||||
}
|
||||
|
||||
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.');
|
||||
unreadItems = false;
|
||||
nbUnreadItems = response['nbUnread'];
|
||||
nbUnreadItems = response['nb_unread_items'];
|
||||
updateCounters();
|
||||
}
|
||||
else if (document.hidden && ! first_run && current_unread) {
|
||||
@ -509,7 +506,7 @@ Miniflux.Item = (function() {
|
||||
Miniflux.App.Log('unreadItems: ' + unreadItems);
|
||||
};
|
||||
|
||||
request.open("POST", "?action=latest-feeds-items", true);
|
||||
request.open("GET", "?action=latest-feeds-items", true);
|
||||
request.send();
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
Miniflux.Item = (function() {
|
||||
|
||||
// timestamp of the latest item per feed ever seen
|
||||
var latest_feeds_items = [];
|
||||
var latest_feeds_items = {};
|
||||
|
||||
// indicator for new unread items
|
||||
var unreadItems = false;
|
||||
@ -362,27 +362,24 @@ Miniflux.Item = (function() {
|
||||
var first_run = (latest_feeds_items.length === 0);
|
||||
var current_unread = false;
|
||||
var response = JSON.parse(this.responseText);
|
||||
var last_items_timestamps = response['last_items_timestamps'];
|
||||
|
||||
for (var feed_id in response['feeds']) {
|
||||
var current_feed = response['feeds'][feed_id];
|
||||
for (var i = 0; i < last_items_timestamps.length; i++) {
|
||||
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]) {
|
||||
Miniflux.App.Log('feed ' + feed_id + ': New item(s)');
|
||||
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;
|
||||
}
|
||||
if (! latest_feeds_items.hasOwnProperty(feed_id) || current_feed.updated > latest_feeds_items[feed_id]) {
|
||||
latest_feeds_items[feed_id] = current_feed.updated;
|
||||
current_unread = true;
|
||||
}
|
||||
}
|
||||
|
||||
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.');
|
||||
unreadItems = false;
|
||||
nbUnreadItems = response['nbUnread'];
|
||||
nbUnreadItems = response['nb_unread_items'];
|
||||
updateCounters();
|
||||
}
|
||||
else if (document.hidden && ! first_run && current_unread) {
|
||||
@ -397,7 +394,7 @@ Miniflux.Item = (function() {
|
||||
Miniflux.App.Log('unreadItems: ' + unreadItems);
|
||||
};
|
||||
|
||||
request.open("POST", "?action=latest-feeds-items", true);
|
||||
request.open("GET", "?action=latest-feeds-items", true);
|
||||
request.send();
|
||||
}
|
||||
};
|
||||
|
@ -26,13 +26,13 @@
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"app/schemas/sqlite.php",
|
||||
"app/helpers/app.php",
|
||||
"app/helpers/config.php",
|
||||
"app/helpers/csrf.php",
|
||||
"app/helpers/favicon.php",
|
||||
"app/helpers/form.php",
|
||||
"app/helpers/template.php",
|
||||
"app/core/database.php",
|
||||
"app/core/translator.php",
|
||||
"app/core/request.php",
|
||||
"app/core/response.php",
|
||||
|
@ -7,7 +7,7 @@ define('HTTP_TIMEOUT', '20');
|
||||
define('HTTP_MAX_RESPONSE_SIZE', 2097152);
|
||||
|
||||
// DATA_DIRECTORY => default is data (writable directory)
|
||||
define('DATA_DIRECTORY', __DIR__.'/data');
|
||||
define('DATA_DIRECTORY', 'data');
|
||||
|
||||
// FAVICON_DIRECTORY => default is favicons (writable directory)
|
||||
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/
|
||||
define('FAVICON_URL_PATH', 'data/favicons');
|
||||
|
||||
// DB_FILENAME => default value is db.sqlite (default database filename)
|
||||
define('DB_FILENAME', 'db.sqlite');
|
||||
// Database driver: "sqlite" or "postgres", default is 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
|
||||
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">
|
||||
<testsuites>
|
||||
<testsuite name="Miniflux Unit Tests">
|
||||
<testsuite name="Miniflux Sqlite Unit Tests">
|
||||
<directory>unit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
@ -12,20 +12,18 @@ abstract class BaseTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
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 () {
|
||||
$db = new PicoDb\Database(array(
|
||||
'driver' => 'sqlite',
|
||||
'filename' => DB_FILENAME,
|
||||
));
|
||||
|
||||
$db->getStatementHandler()->withLogging();
|
||||
if (! $db->schema('\Miniflux\Schema')->check(Miniflux\Schema\VERSION)) {
|
||||
var_dump($db->getLogMessages());
|
||||
}
|
||||
return $db;
|
||||
return Miniflux\Database\get_connection();
|
||||
});
|
||||
|
||||
SessionStorage::getInstance()->flush();
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
|
@ -26,10 +26,10 @@ class FeedModelTest extends BaseTest
|
||||
$this->assertEquals('feed url', $subscription['feed_url']);
|
||||
$this->assertEquals('etag', $subscription['etag']);
|
||||
$this->assertEquals('last modified', $subscription['last_modified']);
|
||||
$this->assertEquals('0', $subscription['download_content']);
|
||||
$this->assertEquals('0', $subscription['rtl']);
|
||||
$this->assertEquals('0', $subscription['cloak_referrer']);
|
||||
$this->assertEquals('1', $subscription['enabled']);
|
||||
$this->assertFalse((bool) $subscription['download_content']);
|
||||
$this->assertFalse((bool) $subscription['rtl']);
|
||||
$this->assertFalse((bool) $subscription['cloak_referrer']);
|
||||
$this->assertTrue((bool) $subscription['enabled']);
|
||||
}
|
||||
|
||||
public function testGetAll()
|
||||
|
@ -27,7 +27,7 @@ class ItemModelTest extends BaseTest
|
||||
$this->assertEquals('', $item['enclosure_type']);
|
||||
$this->assertEquals('', $item['language']);
|
||||
|
||||
$item = Model\Item\get_item(2, 'ID 1');
|
||||
$item = Model\Item\get_item(2, 1);
|
||||
$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(
|
||||
'e40631d46120a9c38ea139981f8dab26' => $vendorDir . '/ircmaxell/password-compat/lib/password.php',
|
||||
'1c6478a893aa3a9ae898668d7af7b441' => $baseDir . '/app/schemas/sqlite.php',
|
||||
'f0aaf41fd213d5a97cf56c060c89445c' => $baseDir . '/app/helpers/app.php',
|
||||
'640b6eca1c41bc50addc30eb8c8e3b5b' => $baseDir . '/app/helpers/config.php',
|
||||
'5d8e3de7e01942090f1b7b4dbdfb5577' => $baseDir . '/app/helpers/csrf.php',
|
||||
'3f7c6586b45b98746d18450089390313' => $baseDir . '/app/helpers/favicon.php',
|
||||
'e96e91c85d691b966391981f72a31834' => $baseDir . '/app/helpers/form.php',
|
||||
'b8d3001d29a919647064eeaec3f6551e' => $baseDir . '/app/helpers/template.php',
|
||||
'a79069799c6e73d978adffa1caf9894d' => $baseDir . '/app/core/database.php',
|
||||
'7793918f03299c5c8c900e412e166b4f' => $baseDir . '/app/core/translator.php',
|
||||
'69d59b59e8b15a53cc782e283523018d' => $baseDir . '/app/core/request.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 (
|
||||
'e40631d46120a9c38ea139981f8dab26' => __DIR__ . '/..' . '/ircmaxell/password-compat/lib/password.php',
|
||||
'1c6478a893aa3a9ae898668d7af7b441' => __DIR__ . '/../..' . '/app/schemas/sqlite.php',
|
||||
'f0aaf41fd213d5a97cf56c060c89445c' => __DIR__ . '/../..' . '/app/helpers/app.php',
|
||||
'640b6eca1c41bc50addc30eb8c8e3b5b' => __DIR__ . '/../..' . '/app/helpers/config.php',
|
||||
'5d8e3de7e01942090f1b7b4dbdfb5577' => __DIR__ . '/../..' . '/app/helpers/csrf.php',
|
||||
'3f7c6586b45b98746d18450089390313' => __DIR__ . '/../..' . '/app/helpers/favicon.php',
|
||||
'e96e91c85d691b966391981f72a31834' => __DIR__ . '/../..' . '/app/helpers/form.php',
|
||||
'b8d3001d29a919647064eeaec3f6551e' => __DIR__ . '/../..' . '/app/helpers/template.php',
|
||||
'a79069799c6e73d978adffa1caf9894d' => __DIR__ . '/../..' . '/app/core/database.php',
|
||||
'7793918f03299c5c8c900e412e166b4f' => __DIR__ . '/../..' . '/app/core/translator.php',
|
||||
'69d59b59e8b15a53cc782e283523018d' => __DIR__ . '/../..' . '/app/core/request.php',
|
||||
'3fd802226a33a97ae29d1f8315ff66a9' => __DIR__ . '/../..' . '/app/core/response.php',
|
||||
|
Loading…
Reference in New Issue
Block a user