Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
Florine W. Dekker | d7c2658ba6 | |
Florine W. Dekker | 2daa74b319 | |
Florine W. Dekker | 6a1be5ac2f | |
Florine W. Dekker | 7b86673590 |
|
@ -1,7 +1,7 @@
|
|||
const path = require("path");
|
||||
|
||||
module.exports = grunt => {
|
||||
const testTarget = grunt.option("test-target") || ".";
|
||||
const testFilter = grunt.option("test-filter") || "'' .";
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON("package.json"),
|
||||
|
@ -71,7 +71,7 @@ module.exports = grunt => {
|
|||
exec: "composer.phar install --no-dev"
|
||||
},
|
||||
phpunit: {
|
||||
exec: `cd dist/ && chmod +x .vendor/bin/phpunit && .vendor/bin/phpunit --testdox ${testTarget}`
|
||||
exec: `cd dist/ && chmod +x .vendor/bin/phpunit && .vendor/bin/phpunit --filter ${testFilter}`
|
||||
},
|
||||
stan: {
|
||||
exec: "vendor/bin/phpstan analyse -l 8 src/main src/test"
|
||||
|
|
30
README.md
30
README.md
|
@ -9,9 +9,10 @@ This tool regularly checks if people are still alive according to Wikipedia, and
|
|||
|
||||
## Development
|
||||
### Requirements
|
||||
* PHP 8.1+ (i.e. `apt install php php-cgi`)
|
||||
* PHP 8.1+ (i.e. `apt install php php-cgi`) (compiled with Argon2 support)
|
||||
* [PHP cURL](https://www.php.net/manual/en/book.curl.php) (i.e. `apt install php-curl`)
|
||||
* [PHP DOM](https://www.php.net/manual/en/book.dom.php) (i.e. `apt install php-dom`)
|
||||
* [PHP mbstring](https://www.php.net/manual/en/book.mbstring.php) (i.e. `apt install php-mbstring`)
|
||||
* [PHP SQLite 3](https://www.php.net/manual/en/book.sqlite3.php) (i.e. `apt install php-sqlite3`)
|
||||
* [composer](https://getcomposer.org/) (make sure `composer.phar` is on your path)
|
||||
* [npm](https://www.npmjs.com/)
|
||||
|
@ -45,7 +46,10 @@ composer.phar update
|
|||
npm install
|
||||
```
|
||||
|
||||
### Static analysis
|
||||
### Static analysis and tests
|
||||
Note that PHPUnit suppresses output from `print`.
|
||||
Instead, you can use `fwrite(STDERR, print_r("my message", TRUE));`.
|
||||
|
||||
* Run static analysis
|
||||
```shell
|
||||
npm run analyze
|
||||
|
@ -54,10 +58,24 @@ npm install
|
|||
```shell
|
||||
npm run test
|
||||
```
|
||||
* Run all tests in package
|
||||
* Run only select tests
|
||||
```shell
|
||||
npm run test -- --test-target=com/fwdekker/deathnotifier/wikipedia/
|
||||
npm run test -- --test-filter="test-name file-name"
|
||||
```
|
||||
Note that a `test-name` of `''` and a `file-name` of `.` matches all tests.
|
||||
|
||||
* Run all tests in package
|
||||
```shell
|
||||
npm run test -- --test-filter="'' com/fwdekker/deathnotifier/wikipedia/"
|
||||
```
|
||||
* Run all tests in class
|
||||
```shell
|
||||
npm run test -- --test-filter="'' com/fwdekker/deathnotifier/wikipedia/WikipediaTest.php"
|
||||
```
|
||||
* Run only specific test
|
||||
```shell
|
||||
npm run test -- --test-filter="test_query_detects_dead_person ."
|
||||
```
|
||||
* Run static analysis and tests
|
||||
```shell
|
||||
npm run check
|
||||
|
@ -77,12 +95,12 @@ Inside the installation directory, create `config.ini.php` and use it to overrid
|
|||
Make sure only the user that runs PHP can read/write `config.ini.php`.
|
||||
|
||||
### Cron jobs
|
||||
You should run the `process-email-queue` and `update-all-trackings` actions regularly;
|
||||
You should run the `process-email-queue` and `update-trackings` actions regularly;
|
||||
recommended is every minute and every five minutes, respectively.
|
||||
For example, you can add the following lines to your crontab (e.g. using `sudo -u www crontab -e`):
|
||||
```
|
||||
* * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=process-email-queue password=secret_password
|
||||
*/5 * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=update-all-trackings password=secret_password
|
||||
*/5 * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=update-trackings password=secret_password
|
||||
```
|
||||
Replace `secret_password` with the password you configured in `config.ini.php`.
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "1.1.1", "_comment_version": "Also update version in `package.json`!",
|
||||
"version": "1.3.0", "_comment_version": "Also update version in `package.json`!",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||
|
@ -19,12 +19,12 @@
|
|||
"composer/semver": "^3.3",
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"monolog/monolog": "^3.3",
|
||||
"monolog/monolog": "^3.4",
|
||||
"phpmailer/phpmailer": "^6.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
"phpstan/phpstan": "^1.10.32",
|
||||
"phpunit/phpunit": "^10.3.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "1.1.1", "_comment_version": "Also update version in `composer.json`!",
|
||||
"version": "1.3.0", "_comment_version": "Also update version in `composer.json`!",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -1,39 +1,37 @@
|
|||
;<?php exit(); ?>
|
||||
|
||||
[admin]
|
||||
# bcrypt hash of password to use the CLI of `api.php`. If set to its default value, or if empty, the CLI is disabled.
|
||||
cli_password = REPLACE THIS WITH A SECRET VALUE
|
||||
# PHC-formatted hash of password for the CLI of `api.php`. You can create one using PHP's `password_hash`. Escaping
|
||||
# dollar symbols is optional. If set to its default value, or if empty, the CLI is disabled.
|
||||
cli_password = "REPLACE THIS WITH A SECRET VALUE"
|
||||
|
||||
[database]
|
||||
# File to store SQLite database in. The default value assumes that you have configured your server to hide files
|
||||
# starting with a `.`.
|
||||
filename = .death-notifier.db
|
||||
# File to store SQLite database in.
|
||||
filename = ".death-notifier.db"
|
||||
|
||||
[logger]
|
||||
# File to store general logs in. The default value assumes that you have configured your server to hide files starting
|
||||
# with a `.`.
|
||||
filename = .death-notifier.log
|
||||
# File to store general logs in.
|
||||
filename = ".death-notifier.log"
|
||||
# Log level for general log events. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
|
||||
level = 250
|
||||
|
||||
[logger_db]
|
||||
# File to store database logs in. The default value assumes that you have configured your server to hide files starting
|
||||
# with a `.`.
|
||||
filename = .death-notifier.db.log
|
||||
# File to store database logs in.
|
||||
filename = ".death-notifier.db.log"
|
||||
# Log level for database log events. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
|
||||
level = 250
|
||||
|
||||
[mail]
|
||||
# Host name of SMTP server to send mail through.
|
||||
host = TODO
|
||||
host = "TODO"
|
||||
# Port of SMTP server to send mail through.
|
||||
port = TODO
|
||||
# Username to authenticate with at SMTP server.
|
||||
username = TODO
|
||||
username = "TODO"
|
||||
# Password to authenticate with at SMTP server.
|
||||
password = TODO
|
||||
password = "TODO"
|
||||
# Name to show to recipient.
|
||||
from_name = TODO
|
||||
from_name = "TODO"
|
||||
|
||||
[security]
|
||||
# `true` if and only if insecure file permissions for config files should be tolerated.
|
||||
|
@ -41,8 +39,13 @@ allow_config_insecure_permissions = false
|
|||
|
||||
[server]
|
||||
# The path to the directory containing the site's main page.
|
||||
base_path = https://example.com/death-notifier/
|
||||
base_path = "https://example.com/death-notifier/"
|
||||
# The path at which users can report bugs, or an empty string if there is no such path.
|
||||
issue_path =
|
||||
issue_path = ""
|
||||
# The message to display at the top of all pages. A blank string hides the message.
|
||||
global_message =
|
||||
global_message = ""
|
||||
|
||||
[wikipedia]
|
||||
# Contact information to include in requests to Wikipedia's API. Typically a URL and an email address separated by a
|
||||
# semicolon. See also https://www.mediawiki.org/wiki/API:Etiquette.
|
||||
user_agent_contact = "https://example.com/death-notifier; name@example.com"
|
||||
|
|
|
@ -89,7 +89,7 @@ class ProcessEmailQueueAction extends Action
|
|||
$mailer->SMTPKeepAlive = true;
|
||||
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
$mailer->Host = $config["host"];
|
||||
$mailer->Port = $config["port"];
|
||||
$mailer->Port = intval($config["port"]);
|
||||
$mailer->Username = $config["username"];
|
||||
$mailer->Password = $config["password"];
|
||||
try {
|
||||
|
|
|
@ -104,7 +104,7 @@ class UserList
|
|||
VALUES (:email, :password)
|
||||
RETURNING email_verification_token;");
|
||||
$stmt->bindValue(":email", $email);
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_BCRYPT));
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_ARGON2ID));
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["email_verification_token"];
|
||||
}
|
||||
|
@ -265,11 +265,12 @@ class UserList
|
|||
public function set_password(string $uuid, string $password): void
|
||||
{
|
||||
$stmt = $this->database->conn->prepare("UPDATE users
|
||||
SET password=:password, password_last_change=unixepoch(),
|
||||
SET password=:password,
|
||||
password_last_change=unixepoch(),
|
||||
password_reset_token=null
|
||||
WHERE uuid=:uuid;");
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_BCRYPT));
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_ARGON2ID));
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier\wikipedia;
|
||||
|
||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||
use com\fwdekker\deathnotifier\Config;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use JsonException;
|
||||
|
||||
|
@ -19,9 +19,7 @@ class Wikipedia
|
|||
/**
|
||||
* The user agent used to represent the death notifier to Wikipedia.
|
||||
*/
|
||||
private const USER_AGENT =
|
||||
"death-notifier/%%VERSION_NUMBER%% " .
|
||||
"(https://git.fwdekker.com/tools/death-notifier; florine@fwdekker.com)";
|
||||
private const USER_AGENT_FORMAT = "death-notifier/%%VERSION_NUMBER%% (%1\$s) %2\$s";
|
||||
/**
|
||||
* Number of articles to query per query.
|
||||
*/
|
||||
|
@ -48,10 +46,13 @@ class Wikipedia
|
|||
*/
|
||||
private function api_fetch(array $params): mixed
|
||||
{
|
||||
$curl_version = curl_version()["version"] ?? "unknown";
|
||||
$user_agent = sprintf(self::USER_AGENT_FORMAT, Config::get("wikipedia")["user_agent_contact"], $curl_version);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_URL, self::API_URL . http_build_query($params));
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, self::USER_AGENT);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
|
||||
|
||||
$output = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
@ -262,7 +263,7 @@ class Wikipedia
|
|||
return null;
|
||||
|
||||
$category_titles = array_column($article["categories"], "title");
|
||||
$dead_regex = "/^Category:([0-9]{1,4}s? (BC |AD )?deaths|Year of death (missing|unknown))$/";
|
||||
$dead_regex = "/^Category:([0-9]{1,4}s? (BC |AD )?(deaths|suicides)|Year of death (missing|unknown))$/";
|
||||
|
||||
if (!empty(array_filter($category_titles, fn($it) => preg_match($dead_regex, $it))))
|
||||
return PersonStatus::Dead;
|
||||
|
|
|
@ -44,9 +44,9 @@ class EqualsCliPasswordRuleTest extends TestCase
|
|||
* Returns the test cases.
|
||||
*
|
||||
* @return array<string, array{string|null, string|null, class-string<Throwable>|null, string|null}> the test cases
|
||||
* @see RuleTest::test_check()
|
||||
* @see RuleTestTemplate::test_check()
|
||||
*/
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$hash = "\$2y\$04\$fwXTw7Rjzw0EpU094u4agOBaBNqtCHGc4TMoxfbPrxuqO5tpYyRka"; # Hash of "password"
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@ use InvalidArgumentException;
|
|||
/**
|
||||
* Unit tests for {@see EqualsRule}.
|
||||
*/
|
||||
class EqualsRuleTest extends RuleTest
|
||||
class EqualsRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
|
|
@ -8,9 +8,9 @@ use InvalidArgumentException;
|
|||
/**
|
||||
* Unit tests for {@see HasStringLengthRule}.
|
||||
*/
|
||||
class HasStringLengthRuleTest extends RuleTest
|
||||
class HasStringLengthRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsBooleanRule}.
|
||||
*/
|
||||
class IsBooleanRuleTest extends RuleTest
|
||||
class IsBooleanRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsEmailRule}.
|
||||
*/
|
||||
class IsEmailRuleTest extends RuleTest
|
||||
class IsEmailRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsNotBlankRule}.
|
||||
*/
|
||||
class IsNotBlankRuleTest extends RuleTest
|
||||
class IsNotBlankRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsStringRule}.
|
||||
*/
|
||||
class IsStringRuleTest extends RuleTest
|
||||
class IsStringRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsValidCsrfTokenRule}.
|
||||
*/
|
||||
class IsValidCsrfTokenRuleTest extends RuleTest
|
||||
class IsValidCsrfTokenRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use Throwable;
|
|||
/**
|
||||
* Unit tests for {@see Rule} implementations.
|
||||
*/
|
||||
abstract class RuleTest extends TestCase
|
||||
abstract class RuleTestTemplate extends TestCase
|
||||
{
|
||||
/**
|
||||
* Tests the output of {@see Rule::check()}.
|
||||
|
@ -40,7 +40,7 @@ abstract class RuleTest extends TestCase
|
|||
* Returns the test cases.
|
||||
*
|
||||
* @return array<string, array{Rule, mixed|null, class-string<Throwable>|null, string|null}> the test cases
|
||||
* @see RuleTest::test_check()
|
||||
* @see RuleTestTemplate::test_check()
|
||||
*/
|
||||
abstract public function check_provider(): array;
|
||||
abstract public static function check_provider(): array;
|
||||
}
|
|
@ -135,6 +135,13 @@ class WikipediaTest extends TestCase
|
|||
}
|
||||
|
||||
public function test_query_detects_dead_person(): void
|
||||
{
|
||||
$output = $this->wikipedia->query_people_info(["Sophie (musician)"], resolve_moves: false);
|
||||
|
||||
self::assertEquals(PersonStatus::Dead, $output->results["Sophie (musician)"]["status"]);
|
||||
}
|
||||
|
||||
public function test_query_detects_suicide_person(): void
|
||||
{
|
||||
$output = $this->wikipedia->query_people_info(["Adolf Hitler"], resolve_moves: false);
|
||||
|
||||
|
@ -150,8 +157,8 @@ class WikipediaTest extends TestCase
|
|||
|
||||
public function test_query_detects_possibly_living_person(): void
|
||||
{
|
||||
$output = $this->wikipedia->query_people_info(["Judge Edward Aaron"], resolve_moves: false);
|
||||
$output = $this->wikipedia->query_people_info(["Angela Anderes"], resolve_moves: false);
|
||||
|
||||
self::assertEquals(PersonStatus::PossiblyAlive, $output->results["Judge Edward Aaron"]["status"]);
|
||||
self::assertEquals(PersonStatus::PossiblyAlive, $output->results["Angela Anderes"]["status"]);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue