Skip to content

Commit

Permalink
New group sync endpoints with separate add/remove lists
Browse files Browse the repository at this point in the history
Use new function
  • Loading branch information
Derkades committed Sep 12, 2024
1 parent 9ad3dcf commit 0dc8622
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 1 deletion.
137 changes: 137 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,139 @@ 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);

Check failure on line 306 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.1)

Binary operation "+" between 'Can\'t find injector…' and string results in an error.

Check failure on line 306 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2)

Binary operation "+" between 'Can\'t find injector…' and string results in an error.

Check failure on line 306 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 7.4)

Binary operation "+" between 'Can\'t find injector…' and string results in an error.

Check failure on line 306 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.0)

Binary operation "+" between 'Can\'t find injector…' and string results in an error.

Check failure on line 306 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2)

Binary operation "+" between 'Can\'t find injector…' and string results in an error.
}

$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();
foreach ($rules as $rule) {
if ($rule->website_group_id == PRE_VALIDATED_DEFAULT) {
// Require atleast 1 group if default group is synced
if (count($group_ids) === 0) {

Check failure on line 321 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.1)

Undefined variable: $group_ids

Check failure on line 321 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2)

Undefined variable: $group_ids

Check failure on line 321 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 7.4)

Undefined variable: $group_ids

Check failure on line 321 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.0)

Undefined variable: $group_ids

Check failure on line 321 in core/classes/Group_Sync/GroupSyncManager.php

View workflow job for this annotation

GitHub Actions / phpstan (ubuntu-latest, 8.2)

Undefined variable: $group_ids
return [];
}

break;
}
}

$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}";
}
} else if (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) {
$add = $data['add'];
$remove = $data['remove'];

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

if ($injector instanceof BatchableGroupSyncInjector) {
/** @var GroupSyncInjector&BatchableGroupSyncInjector $injector */
$batchable_injector = $injector;
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
1 change: 1 addition & 0 deletions modules/Core/classes/Misc/CoreApiErrors.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class CoreApiErrors {
public const ERROR_INTEGRATION_ALREADY_VERIFIED = 'core:integration_already_verified';
public const ERROR_INTEGRATION_ALREADY_LINKED = 'core:integration_already_linked';
public const ERROR_USER_ALREADY_ACTIVE = 'core:user_already_active';
public const ERROR_USER_NOT_VALIDATED = 'core:user_not_validated';

public const ERROR_UNABLE_TO_UPDATE_USERNAME = 'core:unable_to_update_username';

Expand Down
50 changes: 50 additions & 0 deletions modules/Core/includes/endpoints/SyncMinecraftGroupsEndpoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

class SyncMinecraftGroupsEndpoint extends KeyAuthEndpoint {

public function __construct() {
$this->_route = 'minecraft/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): void {
$api->validateParams($_POST, ['server_id', 'uuid']);

$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')]);
}

$uuid = $_POST['uuid'];

$integrationUser = new IntegrationUser($integration, $uuid, 'identifier');

if (!$integrationUser->exists()) {
$api->throwError(CoreApiErrors::ERROR_INTEGRATION_IDENTIFIER_ERRORS, "Cannot find user with uuid $uuid");
}

$user = $integrationUser->getUser();

if (!$user->isValidated()) {
$api->throwError(CoreApiErrors::ERROR_USER_NOT_VALIDATED, "User is not validated");
}

$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,40 @@
<?php

/**
* @param int $user The NamelessMC user ID to edit
* @param string $roles An array of Discord Role ID to give to the user
*
* @return string JSON Array
*/
class SyncDiscordRolesEndpoint extends KeyAuthEndpoint {

public function __construct() {
$this->_route = 'discord/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): void {
$api->validateParams($_POST, ['user']);

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

$user = $api->getUser('id', $_POST['user']);

$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 0dc8622

Please sign in to comment.