Added optional string role IDs for the API.

This commit is contained in:
flash 2024-09-16 21:44:37 +00:00
parent f373690b12
commit aabffb7b30
6 changed files with 125 additions and 39 deletions
database
public-legacy/manage/users
src/Users
templates/manage/users

View file

@ -0,0 +1,13 @@
<?php
use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigration;
final class AddRoleIdString_20240916_205613 implements IDbMigration {
public function migrate(IDbConnection $conn): void {
$conn->execute(<<<SQL
ALTER TABLE msz_roles
ADD COLUMN role_string VARCHAR(20) NULL DEFAULT NULL COLLATE 'ascii_general_ci' AFTER role_id,
ADD UNIQUE INDEX roles_string_unique (role_string);
SQL);
}
}

View file

@ -42,6 +42,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
break; break;
} }
$roleString = (string)filter_input(INPUT_POST, 'ur_string');
$roleName = (string)filter_input(INPUT_POST, 'ur_name'); $roleName = (string)filter_input(INPUT_POST, 'ur_name');
$roleHide = !empty($_POST['ur_hidden']); $roleHide = !empty($_POST['ur_hidden']);
$roleLeavable = !empty($_POST['ur_leavable']); $roleLeavable = !empty($_POST['ur_leavable']);
@ -54,6 +55,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$colourBlue = (int)filter_input(INPUT_POST, 'ur_col_blue', FILTER_SANITIZE_NUMBER_INT); $colourBlue = (int)filter_input(INPUT_POST, 'ur_col_blue', FILTER_SANITIZE_NUMBER_INT);
Template::set([ Template::set([
'role_ur_string' => $roleString,
'role_ur_name' => $roleName, 'role_ur_name' => $roleName,
'role_ur_hidden' => $roleHide, 'role_ur_hidden' => $roleHide,
'role_ur_leavable' => $roleLeavable, 'role_ur_leavable' => $roleLeavable,
@ -96,11 +98,31 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
break; break;
} }
if(strlen($roleString) > 20) {
echo 'Role string may not be longer than 20 characters.';
break;
}
if(strlen($roleString) > 1 && !ctype_alpha($roleString[0])) {
echo 'Role string most start with an alphabetical character.';
break;
}
if($isNew) { if($isNew) {
$roleInfo = $roles->createRole($roleName, $roleRank, $roleColour, $roleTitle, $roleDesc, $roleHide, $roleLeavable); $roleInfo = $roles->createRole(
$roleName,
$roleRank,
$roleColour,
string: $roleString,
title: $roleTitle,
description: $roleDesc,
hidden: $roleHide,
leavable: $roleLeavable
);
} else { } else {
if($roleName === $roleInfo->getName()) if($roleName === $roleInfo->getName())
$roleName = null; $roleName = null;
if($roleString === $roleInfo->getString())
$roleString = null;
if($roleHide === $roleInfo->isHidden()) if($roleHide === $roleInfo->isHidden())
$roleHide = null; $roleHide = null;
if($roleLeavable === $roleInfo->isLeavable()) if($roleLeavable === $roleInfo->isLeavable())
@ -115,7 +137,17 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
if((string)$roleColour === (string)$roleInfo->getColour()) if((string)$roleColour === (string)$roleInfo->getColour())
$roleColour = null; $roleColour = null;
$roles->updateRole($roleInfo, $roleName, $roleRank, $roleColour, $roleTitle, $roleDesc, $roleHide, $roleLeavable); $roles->updateRole(
$roleInfo,
string: $roleString,
name: $roleName,
rank: $roleRank,
colour: $roleColour,
title: $roleTitle,
description: $roleDesc,
hidden: $roleHide,
leavable: $roleLeavable
);
} }
$msz->createAuditLog( $msz->createAuditLog(

View file

@ -9,6 +9,7 @@ use Index\Data\IDbResult;
class RoleInfo implements Stringable { class RoleInfo implements Stringable {
public function __construct( public function __construct(
private string $id, private string $id,
private ?string $string,
private int $rank, private int $rank,
private string $name, private string $name,
private ?string $title, private ?string $title,
@ -22,14 +23,15 @@ class RoleInfo implements Stringable {
public static function fromResult(IDbResult $result): RoleInfo { public static function fromResult(IDbResult $result): RoleInfo {
return new RoleInfo( return new RoleInfo(
id: $result->getString(0), id: $result->getString(0),
rank: $result->getInteger(1), string: $result->getStringOrNull(1),
name: $result->getString(2), rank: $result->getInteger(2),
title: $result->getStringOrNull(3), name: $result->getString(3),
description: $result->getStringOrNull(4), title: $result->getStringOrNull(4),
hidden: $result->getBoolean(5), description: $result->getStringOrNull(5),
leavable: $result->getBoolean(6), hidden: $result->getBoolean(6),
colour: $result->getIntegerOrNull(7), leavable: $result->getBoolean(7),
created: $result->getInteger(8), colour: $result->getIntegerOrNull(8),
created: $result->getInteger(9),
); );
} }
@ -41,6 +43,14 @@ class RoleInfo implements Stringable {
return $this->id === Roles::DEFAULT_ROLE; return $this->id === Roles::DEFAULT_ROLE;
} }
public function hasString(): bool {
return $this->string !== null && $this->string !== '';
}
public function getString(): ?string {
return $this->string;
}
public function getRank(): int { public function getRank(): int {
return $this->rank; return $this->rank;
} }

View file

@ -57,6 +57,8 @@ class Roles {
public function getRoles( public function getRoles(
UserInfo|string|null $userInfo = null, UserInfo|string|null $userInfo = null,
?bool $hidden = null, ?bool $hidden = null,
?bool $hasString = null,
bool $orderByRank = false,
?Pagination $pagination = null ?Pagination $pagination = null
): iterable { ): iterable {
if($userInfo instanceof UserInfo) if($userInfo instanceof UserInfo)
@ -67,23 +69,26 @@ class Roles {
$hasPagination = $pagination !== null; $hasPagination = $pagination !== null;
$args = 0; $args = 0;
$query = 'SELECT role_id, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour, UNIX_TIMESTAMP(role_created) FROM msz_roles'; $query = 'SELECT role_id, role_string, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour, UNIX_TIMESTAMP(role_created) FROM msz_roles';
if($hasUserInfo) { if($hasUserInfo) {
++$args; ++$args;
$query .= ' WHERE role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)'; $query .= ' WHERE role_id IN (SELECT role_id FROM msz_users_roles WHERE user_id = ?)';
} }
if($hasHidden) if($hasHidden)
$query .= sprintf(' %s role_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '<>' : '='); $query .= sprintf(' %s role_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '<>' : '=');
if($hasString)
$query .= sprintf(' %s role_string %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $hasString ? 'IS NOT' : 'IS');
if($orderByRank)
$query .= ' ORDER BY role_hierarchy DESC';
if($hasPagination) if($hasPagination)
$query .= ' LIMIT ? OFFSET ?'; $query .= ' LIMIT ? OFFSET ?';
$args = 0;
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
if($hasUserInfo) if($hasUserInfo)
$stmt->addParameter(++$args, $userInfo); $stmt->nextParameter($userInfo);
if($hasPagination) { if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange()); $stmt->nextParameter($pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset()); $stmt->nextParameter($pagination->getOffset());
} }
$stmt->execute(); $stmt->execute();
@ -91,7 +96,7 @@ class Roles {
} }
public function getRole(string $roleId): RoleInfo { public function getRole(string $roleId): RoleInfo {
$stmt = $this->cache->get('SELECT role_id, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour, UNIX_TIMESTAMP(role_created) FROM msz_roles WHERE role_id = ?'); $stmt = $this->cache->get('SELECT role_id, role_string, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour, UNIX_TIMESTAMP(role_created) FROM msz_roles WHERE role_id = ?');
$stmt->addParameter(1, $roleId); $stmt->addParameter(1, $roleId);
$stmt->execute(); $stmt->execute();
@ -106,6 +111,7 @@ class Roles {
string $name, string $name,
int $rank, int $rank,
Colour $colour, Colour $colour,
string $string = '',
string $title = '', string $title = '',
string $description = '', string $description = '',
bool $hidden = false, bool $hidden = false,
@ -113,18 +119,22 @@ class Roles {
): RoleInfo { ): RoleInfo {
$colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour); $colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour);
if($string === '')
$string = null;
// should these continue to accept NULL? // should these continue to accept NULL?
if($title === '') $title = null; if($title === '') $title = null;
if($description === '') $description = null; if($description === '') $description = null;
$stmt = $this->cache->get('INSERT INTO msz_roles (role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour) VALUES (?, ?, ?, ?, ?, ?, ?)'); $stmt = $this->cache->get('INSERT INTO msz_roles (role_string, role_hierarchy, role_name, role_title, role_description, role_hidden, role_can_leave, role_colour) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');
$stmt->addParameter(1, $rank); $stmt->nextParameter($string);
$stmt->addParameter(2, $name); $stmt->nextParameter($rank);
$stmt->addParameter(3, $title); $stmt->nextParameter($name);
$stmt->addParameter(4, $description); $stmt->nextParameter($title);
$stmt->addParameter(5, $hidden ? 1 : 0); $stmt->nextParameter($description);
$stmt->addParameter(6, $leavable ? 1 : 0); $stmt->nextParameter($hidden ? 1 : 0);
$stmt->addParameter(7, $colour); $stmt->nextParameter($leavable ? 1 : 0);
$stmt->nextParameter($colour);
$stmt->execute(); $stmt->execute();
return $this->getRole((string)$this->dbConn->getLastInsertId()); return $this->getRole((string)$this->dbConn->getLastInsertId());
@ -156,6 +166,7 @@ class Roles {
public function updateRole( public function updateRole(
RoleInfo|string $roleInfo, RoleInfo|string $roleInfo,
?string $string = null,
?string $name = null, ?string $name = null,
?int $rank = null, ?int $rank = null,
?Colour $colour = null, ?Colour $colour = null,
@ -167,6 +178,7 @@ class Roles {
if($roleInfo instanceof RoleInfo) if($roleInfo instanceof RoleInfo)
$roleInfo = $roleInfo->getId(); $roleInfo = $roleInfo->getId();
$applyString = $string !== null;
$applyTitle = $title !== null; $applyTitle = $title !== null;
$applyDescription = $description !== null; $applyDescription = $description !== null;
$applyColour = $colour !== null; $applyColour = $colour !== null;
@ -176,24 +188,29 @@ class Roles {
if($applyColour) if($applyColour)
$colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour); $colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour);
if($string === '')
$string = null;
// should these continue to accept NULL? // should these continue to accept NULL?
if($title === '') $title = null; if($title === '') $title = null;
if($description === '') $description = null; if($description === '') $description = null;
$stmt = $this->cache->get('UPDATE msz_roles SET role_hierarchy = COALESCE(?, role_hierarchy), role_name = COALESCE(?, role_name), role_title = IF(?, ?, role_title), role_description = IF(?, ?, role_description), role_hidden = IF(?, ?, role_hidden), role_can_leave = IF(?, ?, role_can_leave), role_colour = IF(?, ?, role_colour) WHERE role_id = ?'); $stmt = $this->cache->get('UPDATE msz_roles SET role_string = IF(?, ?, role_string), role_hierarchy = COALESCE(?, role_hierarchy), role_name = COALESCE(?, role_name), role_title = IF(?, ?, role_title), role_description = IF(?, ?, role_description), role_hidden = IF(?, ?, role_hidden), role_can_leave = IF(?, ?, role_can_leave), role_colour = IF(?, ?, role_colour) WHERE role_id = ?');
$stmt->addParameter(1, $rank); $stmt->nextParameter($applyString ? 1 : 0);
$stmt->addParameter(2, $name); $stmt->nextParameter($string);
$stmt->addParameter(3, $applyTitle ? 1 : 0); $stmt->nextParameter($rank);
$stmt->addParameter(4, $title); $stmt->nextParameter($name);
$stmt->addParameter(5, $applyDescription ? 1 : 0); $stmt->nextParameter($applyTitle ? 1 : 0);
$stmt->addParameter(6, $description); $stmt->nextParameter($title);
$stmt->addParameter(7, $applyHidden ? 1 : 0); $stmt->nextParameter($applyDescription ? 1 : 0);
$stmt->addParameter(8, $hidden ? 1 : 0); $stmt->nextParameter($description);
$stmt->addParameter(9, $applyLeavable ? 1 : 0); $stmt->nextParameter($applyHidden ? 1 : 0);
$stmt->addParameter(10, $leavable ? 1 : 0); $stmt->nextParameter($hidden ? 1 : 0);
$stmt->addParameter(11, $applyColour ? 1 : 0); $stmt->nextParameter($applyLeavable ? 1 : 0);
$stmt->addParameter(12, $colour); $stmt->nextParameter($leavable ? 1 : 0);
$stmt->addParameter(13, $roleInfo); $stmt->nextParameter($applyColour ? 1 : 0);
$stmt->nextParameter($colour);
$stmt->nextParameter($roleInfo);
$stmt->execute(); $stmt->execute();
} }

View file

@ -6,6 +6,7 @@ use Misuzu\SiteInfo;
use Misuzu\URLs\URLRegistry; use Misuzu\URLs\URLRegistry;
use Misuzu\Users\Assets\UserAvatarAsset; use Misuzu\Users\Assets\UserAvatarAsset;
use Aiwass\Server\{RpcActionHandler,RpcQuery}; use Aiwass\Server\{RpcActionHandler,RpcQuery};
use Index\XArray;
use Index\Colour\{Colour,ColourRGB}; use Index\Colour\{Colour,ColourRGB};
final class UsersRpcActions extends RpcActionHandler { final class UsersRpcActions extends RpcActionHandler {
@ -65,6 +66,13 @@ final class UsersRpcActions extends RpcActionHandler {
if($userInfo->hasLastActive()) if($userInfo->hasLastActive())
$output['last_active_at'] = $userInfo->getLastActiveAt()->toIso8601ZuluString(); $output['last_active_at'] = $userInfo->getLastActiveAt()->toIso8601ZuluString();
$roles = XArray::select(
$this->usersCtx->getRoles()->getRoles(userInfo: $userInfo, hasString: true, orderByRank: true),
fn($roleInfo) => $roleInfo->getString(),
);
if(!empty($roles))
$output['roles'] = $roles;
if($userInfo->hasTitle()) if($userInfo->hasTitle())
$output['title'] = $userInfo->getTitle(); $output['title'] = $userInfo->getTitle();

View file

@ -17,6 +17,13 @@
</div> </div>
</label> </label>
<label class="form__label">
<div class="form__label__text">Role String (used in API)</div>
<div class="form__label__input">
{{ input_text('ur_string', '', role_ur_string|default(role_info.string|default()), 'text', '', false, {'maxlength':20}) }}
</div>
</label>
<label class="form__label"> <label class="form__label">
<div class="form__label__text">Hide Role</div> <div class="form__label__text">Hide Role</div>
<div class="form__label__input"> <div class="form__label__input">
@ -44,7 +51,6 @@
{{ input_text('ur_title', '', role_ur_title|default(role_info.title|default()), 'text', '', false, {'maxlength':64}) }} {{ input_text('ur_title', '', role_ur_title|default(role_info.title|default()), 'text', '', false, {'maxlength':64}) }}
</div> </div>
</label> </label>
</div> </div>
<div class="container"> <div class="container">