Skip to content

Commit

Permalink
New group sync endpoints with separate add/remove lists (#3538)
Browse files Browse the repository at this point in the history
* New group sync endpoints with separate add/remove lists

* Code review changes
  • Loading branch information
Derkades authored Oct 18, 2024
1 parent b193dd7 commit cddb325
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 1 deletion.
123 changes: 123 additions & 0 deletions core/classes/Group_Sync/GroupSyncManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ private function compileValidatorMessages(Language $language): array
* @param string $sending_injector_class Class name of injector broadcasting this change
* @param array $group_ids Array of Group IDs native to the sending injector which were added/removed to the user
*
* @deprecated broadcastGroupChange should be used instead. This function has issues, such as that on the first sync it will replace
* groups on one side with groups from the other side, depending on which side happens to sync first. On the first sync,
* the desired behaviour is for the new roles on both sides to become the union of roles on both sides.
*
* @return array Array of logs of changed groups
*/
public function broadcastChange(User $user, string $sending_injector_class, array $group_ids): array
Expand Down Expand Up @@ -283,6 +287,125 @@ public function broadcastChange(User $user, string $sending_injector_class, arra
return $logs;
}

/**
* Execute respective `addGroup()` or `removeGroup()` function on each of the injectors (e.g. Nameless itself, Minecraft, Discord)
* synced to the changed group.
*
* @param User $user NamelessMC user to apply changes to
* @param string $sending_injector_class Class name of injector broadcasting this change
* @param array $group_ids_add Array of injector-native Group IDs were added to the user
* @param array $group_ids_remove Array of injector-native Group IDs were removed from the user
*
* @return array Array of logs of changed groups
*/
public function broadcastGroupChange(User $user, string $sending_injector_class, array $group_ids_add, array $group_ids_remove): array
{
$sending_injector = $this->getInjectorByClass($sending_injector_class);

if ($sending_injector === null) {
throw new InvalidArgumentException("Can't find injector by class: " . $sending_injector_class);
}

$logs = [];

$modified = [];

$namelessmc_injector = $this->getInjectorByClass(NamelessMCGroupSyncInjector::class);
$namelessmc_column = $namelessmc_injector->getColumnName();

// Get all group sync rules where this injector is not null
$rules = DB::getInstance()->query("SELECT * FROM nl2_group_sync WHERE {$sending_injector->getColumnName()} IS NOT NULL")->results();

$batched_changes = [];
foreach ($rules as $rule) {
foreach ($this->getEnabledInjectors() as $injector) {
if ($injector == $sending_injector) {
continue;
}

$injector_class = get_class($injector);

$batchable = $injector instanceof BatchableGroupSyncInjector;
if ($batchable && !array_key_exists($injector_class, $batched_changes)) {
$batched_changes[$injector_class] = [
'add' => [],
'remove' => [],
];
}

$injector_column = $injector->getColumnName();
$injector_group_id = $rule->{$injector_column};
$sending_group_id = $rule->{$sending_injector->getColumnName()};

// Skip this injector if it doesn't have a group id setup for this rule
if ($injector_group_id === null) {
continue;
}

if (!isset($modified[$injector_column])) {
$modified[$injector_column] = [];
}

// Skip this specific injector for this rule if we have already modified the user
// with the same injector group id
if (in_array($injector_group_id, $modified[$injector_column])) {
continue;
}

if (in_array($sending_group_id, $group_ids_add)) {
// Add group to user
$modified[$injector_column][] = $injector_group_id;
if ($batchable) {
$batched_changes[$injector_class]['add'][] = $injector_group_id;
} elseif ($injector->addGroup($user, $injector_group_id)) {
$logs['added'][] = "{$injector_column} -> {$injector_group_id}";
}
} elseif (in_array($sending_group_id, $group_ids_remove)) {
// Remove group from user
$modified[$injector_column][] = $injector_group_id;
if ($batchable) {
$batched_changes[$injector_class]['remove'][] = $injector_group_id;
} elseif ($injector->removeGroup($user, $injector_group_id)) {
$logs['removed'][] = "{$injector_column} -> {$injector_group_id}";
}
}
}
}

foreach ($batched_changes as $injector_class => $data) {
/** @var GroupSyncInjector&BatchableGroupSyncInjector $injector */
$injector = $this->getInjectorByClass($injector_class);
$injector_column = $injector->getColumnName();

$add = $data['add'];
$remove = $data['remove'];

if (count($add)) {
$result = $injector->batchAddGroups($user, $add);
if (is_array($result)) {
foreach ($result as $res) {
if ($res['status'] === 'added') {
$logs['added'][] = "{$injector_column} -> {$res['group_id']}";
}
}
}
}

if (count($remove)) {
$result = $injector->batchRemoveGroups($user, $remove);
if (is_array($result)) {
foreach ($result as $res) {
if ($res['status'] === 'removed') {
$logs['removed'][] = "{$injector_column} -> {$res['group_id']}";
}
}
}
}
}

return $logs;
}

/**
* Get an enabled `GroupSyncInjector` from its class name, if it exists.
*
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
# Reinstall:
# docker compose exec php php -f dev/scripts/cli_install.php '--' '--iSwearIKnowWhatImDoing' '--reinstall'
#
# Run phpstan:
# docker compose exec php vendor/bin/phpstan --configuration=dev/phpstan.neon
#
# Uninstall
# docker compose down
# rm -rf core/config.php cache/*
Expand Down
36 changes: 36 additions & 0 deletions modules/Core/includes/endpoints/SyncMinecraftGroupsEndpoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

class SyncMinecraftGroupsEndpoint extends KeyAuthEndpoint {

public function __construct() {
$this->_route = 'minecraft/{user}/sync-groups';
$this->_module = 'Core';
$this->_description = 'Update a users groups based on added or removed groups from the Minecraft server';
$this->_method = 'POST';
}

public function execute(Nameless2API $api, User $user): void {
$api->validateParams($_POST, ['server_id']);

$server_id = $_POST['server_id'];
$integration = Integrations::getInstance()->getIntegration('Minecraft');

if (!$integration || $server_id != Settings::get('group_sync_mc_server')) {
$api->returnArray(['message' => $api->getLanguage()->get('api', 'groups_updates_ignored')]);
}

$log = GroupSyncManager::getInstance()->broadcastGroupChange(
$user,
MinecraftGroupSyncInjector::class,
$_POST['add'] ?? [],
$_POST['remove'] ?? [],
);

Log::getInstance()->log(Log::Action('mc_group_sync/role_set'), json_encode($log), $user->data()->id);

$api->returnArray([
'message' => $api->getLanguage()->get('api', 'groups_updates_successfully'),
'log' => $log,
]);
}
}
3 changes: 3 additions & 0 deletions modules/Core/includes/endpoints/UpdateGroupsEndpoint.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?php

/**
* @deprecated SyncMinecraftGroupsEndpoint should be used instead
*/
class UpdateGroupsEndpoint extends KeyAuthEndpoint {

public function __construct() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/**
* @param int $user The NamelessMC user ID to edit
* @param string $roles An array of Discord Role ID to give to the user
*
* @deprecated Use SyncDiscordRolesEndpoint instead
* @return string JSON Array
*/
class SetDiscordRolesEndpoint extends KeyAuthEndpoint {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

class SyncDiscordRolesEndpoint extends KeyAuthEndpoint {

public function __construct() {
$this->_route = 'discord/{user}/sync-roles';
$this->_module = 'Discord Integration';
$this->_description = 'Set a NamelessMC user\'s according to the supplied Discord Role ID list';
$this->_method = 'POST';
}

public function execute(Nameless2API $api, User $user): void {
$api->validateParams($_POST, []);

if (!Discord::isBotSetup()) {
$api->throwError(DiscordApiErrors::ERROR_DISCORD_INTEGRATION_DISABLED);
}

$log_array = GroupSyncManager::getInstance()->broadcastGroupChange(
$user,
DiscordGroupSyncInjector::class,
$_POST['add'] ?? [],
$_POST['remove'] ?? []
);

if (count($log_array)) {
Log::getInstance()->log(Log::Action('discord/role_set'), json_encode($log_array), $user->data()->id);
}

$api->returnArray(array_merge(['message' => Discord::getLanguageTerm('group_updated')], $log_array));
}
}

0 comments on commit cddb325

Please sign in to comment.