mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
Implement CLI for user metadata
This commit is contained in:
parent
b7c7915a65
commit
68422390da
5 changed files with 172 additions and 7 deletions
|
@ -5,6 +5,7 @@ New features:
|
||||||
- Support for the Miniflux protocol (see manual for details)
|
- Support for the Miniflux protocol (see manual for details)
|
||||||
- Support for API level 15 of Tiny Tiny RSS
|
- Support for API level 15 of Tiny Tiny RSS
|
||||||
- Support for feed icons in Fever
|
- Support for feed icons in Fever
|
||||||
|
- Command-line functionality for managing user metadata
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
- Use icons specified in Atom feeds when available
|
- Use icons specified in Atom feeds when available
|
||||||
|
|
112
lib/CLI.php
112
lib/CLI.php
|
@ -17,8 +17,11 @@ Usage:
|
||||||
arsse.php feed refresh <n>
|
arsse.php feed refresh <n>
|
||||||
arsse.php conf save-defaults [<file>]
|
arsse.php conf save-defaults [<file>]
|
||||||
arsse.php user [list]
|
arsse.php user [list]
|
||||||
arsse.php user add <username> [<password>]
|
arsse.php user add <username> [<password>] [--admin]
|
||||||
arsse.php user remove <username>
|
arsse.php user remove <username>
|
||||||
|
arsse.php user show <username>
|
||||||
|
arsse.php user set <username> <property> <value>
|
||||||
|
arsse.php user unset <username> <property>
|
||||||
arsse.php user set-pass <username> [<password>]
|
arsse.php user set-pass <username> [<password>]
|
||||||
[--oldpass=<pass>] [--fever]
|
[--oldpass=<pass>] [--fever]
|
||||||
arsse.php user unset-pass <username>
|
arsse.php user unset-pass <username>
|
||||||
|
@ -63,11 +66,13 @@ Commands:
|
||||||
|
|
||||||
Prints a list of all existing users, one per line.
|
Prints a list of all existing users, one per line.
|
||||||
|
|
||||||
user add <username> [<password>]
|
user add <username> [<password>] [--admin]
|
||||||
|
|
||||||
Adds the user specified by <username>, with the provided password
|
Adds the user specified by <username>, with the provided password
|
||||||
<password>. If no password is specified, a random password will be
|
<password>. If no password is specified, a random password will be
|
||||||
generated and printed to standard output.
|
generated and printed to standard output. The --admin option will make
|
||||||
|
the user an administrator, which allows them to manage users via the
|
||||||
|
Miniflux protocol, among other things.
|
||||||
|
|
||||||
user remove <username>
|
user remove <username>
|
||||||
|
|
||||||
|
@ -76,6 +81,22 @@ Commands:
|
||||||
which the user was subscribed will be retained and refreshed until the
|
which the user was subscribed will be retained and refreshed until the
|
||||||
configured retention time elapses.
|
configured retention time elapses.
|
||||||
|
|
||||||
|
user show <username>
|
||||||
|
|
||||||
|
Displays the metadata of a user in a basic tabular format. See below for
|
||||||
|
details on the various properties displayed.
|
||||||
|
|
||||||
|
user set <username> <property> <value>
|
||||||
|
|
||||||
|
Sets a user's metadata proprty to the supplied value. See below for
|
||||||
|
details on the various properties available.
|
||||||
|
|
||||||
|
user unset <username> <property>
|
||||||
|
|
||||||
|
Sets a user's metadata proprty to its default value. See below for
|
||||||
|
details on the various properties available. What the default value
|
||||||
|
for a property evaluates to depends on which protocol is used.
|
||||||
|
|
||||||
user set-pass <username> [<password>]
|
user set-pass <username> [<password>]
|
||||||
|
|
||||||
Changes <username>'s password to <password>. If no password is specified,
|
Changes <username>'s password to <password>. If no password is specified,
|
||||||
|
@ -128,6 +149,65 @@ Commands:
|
||||||
The --flat option can be used to omit folders from the export. Some OPML
|
The --flat option can be used to omit folders from the export. Some OPML
|
||||||
implementations may not support folders, or arbitrary nesting; this option
|
implementations may not support folders, or arbitrary nesting; this option
|
||||||
may be used when planning to import into such software.
|
may be used when planning to import into such software.
|
||||||
|
|
||||||
|
User metadata:
|
||||||
|
|
||||||
|
User metadata is primary used by the Miniflux protocol, and most
|
||||||
|
properties have identical or similar names to those used by Miniflux.
|
||||||
|
Properties may also affect other protocols, or conversely may have no
|
||||||
|
effect even when using the Miniflux protocol; this is noted below when
|
||||||
|
appropriate.
|
||||||
|
|
||||||
|
Booleans accept any of the values true/false, 1/0, yes/no, on/off.
|
||||||
|
|
||||||
|
The following metadata properties exist for each user:
|
||||||
|
|
||||||
|
num
|
||||||
|
Integer. The numeric identifier of the user. This is assigned at user
|
||||||
|
creation and is read-only.
|
||||||
|
admin
|
||||||
|
Boolean. Whether the user is an administrator. Administrators may
|
||||||
|
manage other users via the Miniflux protocol, and also may trigger
|
||||||
|
feed updates manually via the Nextcloud News protocol.
|
||||||
|
lang
|
||||||
|
String. The preferred language of the user, as a BCP 47 language tag
|
||||||
|
e.g. "en-ca". Note that since The Arsse currently only includes
|
||||||
|
English text it is not used by The Arsse itself, but clients may
|
||||||
|
use this metadata in protocols which expose it.
|
||||||
|
tz
|
||||||
|
String. The time zone of the user, as a tzdata identifier e.g.
|
||||||
|
"America/Los_Angeles".
|
||||||
|
root_folder_name
|
||||||
|
String. The name of the root folder, in protocols which allow it to
|
||||||
|
be renamed.
|
||||||
|
sort_asc
|
||||||
|
Boolean. Whether the user prefers ascending sort order for articles.
|
||||||
|
Descending order is usually the default, but explicitly setting this
|
||||||
|
property false will also make a preference for descending order
|
||||||
|
explicit.
|
||||||
|
theme
|
||||||
|
String. The user's preferred theme. This is not used by The Arsse
|
||||||
|
itself, but clients may use this metadata in protocols which expose
|
||||||
|
it.
|
||||||
|
page_size
|
||||||
|
Integer. The user's preferred pge size when listing articles. This is
|
||||||
|
not used by The Arsse itself, but clients may use this metadata in
|
||||||
|
protocols which expose it.
|
||||||
|
shortcuts
|
||||||
|
Boolean. Whether to enable keyboard shortcuts. This is not used by
|
||||||
|
The Arsse itself, but clients may use this metadata in protocols which
|
||||||
|
expose it.
|
||||||
|
gestures
|
||||||
|
Boolean. Whether to enable touch gestures. This is not used by
|
||||||
|
The Arsse itself, but clients may use this metadata in protocols which
|
||||||
|
expose it.
|
||||||
|
reading_time
|
||||||
|
Boolean. Whether to calculate and display the estimated reading time
|
||||||
|
for articles. Currently The Arsse does not calculate reading time, so
|
||||||
|
changing this will likely have no effect.
|
||||||
|
stylesheet
|
||||||
|
String. A user CSS stylesheet. This is not used by The Arsse itself,
|
||||||
|
but clients may use this metadata in protocols which expose it.
|
||||||
USAGE_TEXT;
|
USAGE_TEXT;
|
||||||
|
|
||||||
protected function usage($prog): string {
|
protected function usage($prog): string {
|
||||||
|
@ -215,10 +295,14 @@ USAGE_TEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function userManage($args): int {
|
protected function userManage($args): int {
|
||||||
$cmd = $this->command(["add", "remove", "set-pass", "unset-pass", "list", "auth"], $args);
|
$cmd = $this->command(["add", "remove", "show", "set", "unset", "set-pass", "unset-pass", "list", "auth"], $args);
|
||||||
switch ($cmd) {
|
switch ($cmd) {
|
||||||
case "add":
|
case "add":
|
||||||
return $this->userAddOrSetPassword("add", $args["<username>"], $args["<password>"]);
|
$out = $this->userAddOrSetPassword("add", $args["<username>"], $args["<password>"]);
|
||||||
|
if ($args['--admin']) {
|
||||||
|
Arsse::$user->propertiesSet($args["<username>"], ['admin' => true]);
|
||||||
|
}
|
||||||
|
return $out;
|
||||||
case "set-pass":
|
case "set-pass":
|
||||||
if ($args['--fever']) {
|
if ($args['--fever']) {
|
||||||
$passwd = Arsse::$obj->get(Fever::class)->register($args["<username>"], $args["<password>"]);
|
$passwd = Arsse::$obj->get(Fever::class)->register($args["<username>"], $args["<password>"]);
|
||||||
|
@ -239,6 +323,12 @@ USAGE_TEXT;
|
||||||
return 0;
|
return 0;
|
||||||
case "remove":
|
case "remove":
|
||||||
return (int) !Arsse::$user->remove($args["<username>"]);
|
return (int) !Arsse::$user->remove($args["<username>"]);
|
||||||
|
case "show":
|
||||||
|
return $this->userShowProperties($args["<username>"]);
|
||||||
|
case "set":
|
||||||
|
return (int) !Arsse::$user->propertiesSet($args["<username>"], [$args["<property>"] => $args["<value>"]]);
|
||||||
|
case "unset":
|
||||||
|
return (int) !Arsse::$user->propertiesSet($args["<username>"], [$args["<property>"] => null]);
|
||||||
case "auth":
|
case "auth":
|
||||||
return $this->userAuthenticate($args["<username>"], $args["<password>"], $args["--fever"]);
|
return $this->userAuthenticate($args["<username>"], $args["<password>"], $args["--fever"]);
|
||||||
case "list":
|
case "list":
|
||||||
|
@ -275,4 +365,16 @@ USAGE_TEXT;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function userShowProperties(string $user): int {
|
||||||
|
$data = Arsse::$user->propertiesGet($user);
|
||||||
|
$len = array_reduce(array_keys($data), function($carry, $item) {
|
||||||
|
return max($carry, strlen($item));
|
||||||
|
}, 0) + 2;
|
||||||
|
foreach ($data as $k => $v) {
|
||||||
|
echo str_pad($k, $len, " ");
|
||||||
|
echo var_export($v, true).\PHP_EOL;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,14 @@ class User {
|
||||||
'admin' => V::T_BOOL,
|
'admin' => V::T_BOOL,
|
||||||
'lang' => V::T_STRING,
|
'lang' => V::T_STRING,
|
||||||
'tz' => V::T_STRING,
|
'tz' => V::T_STRING,
|
||||||
|
'root_folder_name' => V::T_STRING,
|
||||||
'sort_asc' => V::T_BOOL,
|
'sort_asc' => V::T_BOOL,
|
||||||
'theme' => V::T_STRING,
|
'theme' => V::T_STRING,
|
||||||
'page_size' => V::T_INT, // greater than zero
|
'page_size' => V::T_INT, // greater than zero
|
||||||
'shortcuts' => V::T_BOOL,
|
'shortcuts' => V::T_BOOL,
|
||||||
'gestures' => V::T_BOOL,
|
'gestures' => V::T_BOOL,
|
||||||
'stylesheet' => V::T_STRING,
|
|
||||||
'reading_time' => V::T_BOOL,
|
'reading_time' => V::T_BOOL,
|
||||||
'root_folder_name' => V::T_STRING,
|
'stylesheet' => V::T_STRING,
|
||||||
];
|
];
|
||||||
public const PROPERTIES_LARGE = ["stylesheet"];
|
public const PROPERTIES_LARGE = ["stylesheet"];
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,15 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAddAUserAsAdministrator(): void {
|
||||||
|
Arsse::$user = $this->createMock(User::class);
|
||||||
|
Arsse::$user->method("add")->willReturn("random password");
|
||||||
|
Arsse::$user->method("propertiesSet")->willReturn([]);
|
||||||
|
Arsse::$user->expects($this->exactly(1))->method("add")->with("jane.doe@example.com", null);
|
||||||
|
Arsse::$user->expects($this->exactly(1))->method("propertiesSet")->with("jane.doe@example.com", ['admin' => true]);
|
||||||
|
$this->assertConsole($this->cli, "arsse.php user add jane.doe@example.com --admin", 0, "random password");
|
||||||
|
}
|
||||||
|
|
||||||
/** @dataProvider provideUserAuthentication */
|
/** @dataProvider provideUserAuthentication */
|
||||||
public function testAuthenticateAUser(string $cmd, int $exitStatus, string $output): void {
|
public function testAuthenticateAUser(string $cmd, int $exitStatus, string $output): void {
|
||||||
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
// FIXME: Phake is somehow unable to mock the User class correctly, so we use PHPUnit's mocks instead
|
||||||
|
@ -357,4 +366,56 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest {
|
||||||
["arsse.php import jane.doe@example.com bad.opml --replace --flat", 10603, "bad.opml", "jane.doe@example.com", true, true],
|
["arsse.php import jane.doe@example.com bad.opml --replace --flat", 10603, "bad.opml", "jane.doe@example.com", true, true],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testShowMetadataOfAUser(): void {
|
||||||
|
$data = [
|
||||||
|
'num' => 42,
|
||||||
|
'admin' => false,
|
||||||
|
'lang' => "en-ca",
|
||||||
|
'tz' => "America/Toronto",
|
||||||
|
'root_folder_name' => null,
|
||||||
|
'sort_asc' => true,
|
||||||
|
'theme' => null,
|
||||||
|
'page_size' => 50,
|
||||||
|
'shortcuts' => true,
|
||||||
|
'gestures' => null,
|
||||||
|
'reading_time' => false,
|
||||||
|
'stylesheet' => "body {color:gray}",
|
||||||
|
];
|
||||||
|
$exp = implode(\PHP_EOL, [
|
||||||
|
"num 42",
|
||||||
|
"admin false",
|
||||||
|
"lang 'en-ca'",
|
||||||
|
"tz 'America/Toronto'",
|
||||||
|
"root_folder_name NULL",
|
||||||
|
"sort_asc true",
|
||||||
|
"theme NULL",
|
||||||
|
"page_size 50",
|
||||||
|
"shortcuts true",
|
||||||
|
"gestures NULL",
|
||||||
|
"reading_time false",
|
||||||
|
"stylesheet 'body {color:gray}'",
|
||||||
|
]);
|
||||||
|
Arsse::$user = $this->createMock(User::class);
|
||||||
|
Arsse::$user->method("propertiesGet")->willReturn($data);
|
||||||
|
Arsse::$user->expects($this->once())->method("propertiesGet")->with("john.doe@example.com", true);
|
||||||
|
$this->assertConsole($this->cli, "arsse.php user show john.doe@example.com", 0, $exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider provideMetadataChanges */
|
||||||
|
public function testSetMetadataOfAUser(string $cmd, string $user, array $in, array $out, int $exp): void {
|
||||||
|
Arsse::$user = $this->createMock(User::class);
|
||||||
|
Arsse::$user->method("propertiesSet")->willReturn($out);
|
||||||
|
Arsse::$user->expects($this->once())->method("propertiesSet")->with($user, $in);
|
||||||
|
$this->assertConsole($this->cli, $cmd, $exp, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideMetadataChanges(): iterable {
|
||||||
|
return [
|
||||||
|
["arsse.php user set john admin true", "john", ['admin' => "true"], ['admin' => "true"], 0],
|
||||||
|
["arsse.php user set john bogus 1", "john", ['bogus' => "1"], [], 1],
|
||||||
|
["arsse.php user unset john admin", "john", ['admin' => null], ['admin' => null], 0],
|
||||||
|
["arsse.php user unset john bogus", "john", ['bogus' => null], [], 1],
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
|
||||||
}, $params, array_keys($params)));
|
}, $params, array_keys($params)));
|
||||||
}
|
}
|
||||||
$url = URL::queryAppend($url, (string) $params);
|
$url = URL::queryAppend($url, (string) $params);
|
||||||
|
$params = null;
|
||||||
}
|
}
|
||||||
$q = parse_url($url, \PHP_URL_QUERY);
|
$q = parse_url($url, \PHP_URL_QUERY);
|
||||||
if (strlen($q ?? "")) {
|
if (strlen($q ?? "")) {
|
||||||
|
|
Loading…
Reference in a new issue