mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Implement presence expiry management and enhance user ID handling in presence actions. Add automated tests for expired presence deletion during maintenance.
This commit is contained in:
@@ -80,10 +80,6 @@ class Update extends PresenceAction
|
||||
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, 'userId is not allowed for non-API key and non-privileged users');
|
||||
}
|
||||
|
||||
if (($isAPIKey || $isPrivilegedUser) && !$userId) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'userId is required for API key and privileged users');
|
||||
}
|
||||
|
||||
$presence = $dbForProject->getDocument('presenceLogs', $presenceId);
|
||||
|
||||
if ($presence->isEmpty()) {
|
||||
@@ -94,6 +90,8 @@ class Update extends PresenceAction
|
||||
|
||||
if ($userId !== null) {
|
||||
$updateData['userId'] = $userId;
|
||||
$userDoc = $dbForProject->getDocument('users', $userId);
|
||||
$updateData['userInternalId'] = $userDoc->getSequence();
|
||||
}
|
||||
|
||||
if ($status !== null) {
|
||||
|
||||
@@ -55,7 +55,7 @@ class Upsert extends PresenceAction
|
||||
],
|
||||
))
|
||||
->param('presenceId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Presence unique ID.', false, ['dbForProject'])
|
||||
->param('userId', '', new UID(), 'User ID.', false)
|
||||
->param('userId', null, new Nullable(new UID()), 'User ID.', true)
|
||||
->param('status', '', new Text(Database::LENGTH_KEY), 'Presence status.', false)
|
||||
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE])), 'An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
// TODO: what shall be the min and max date here
|
||||
@@ -92,16 +92,16 @@ class Upsert extends PresenceAction
|
||||
$userInternalId = null;
|
||||
$resolvedUserId = $userId;
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
$userInternalId = $user->getId();
|
||||
$userInternalId = $user->getSequence();
|
||||
$resolvedUserId = $user->getId();
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
if ($user->isEmpty()) {
|
||||
$fetchedUser = $dbForProject->getDocument('users', $userId);
|
||||
if ($fetchedUser->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND, params: [$userId]);
|
||||
}
|
||||
|
||||
$userInternalId = $user->getId();
|
||||
$resolvedUserId = $user->getId();
|
||||
$userInternalId = $fetchedUser->getSequence();
|
||||
$resolvedUserId = $fetchedUser->getId();
|
||||
}
|
||||
|
||||
$presenceData = [
|
||||
|
||||
@@ -214,6 +214,7 @@ class Deletes extends Action
|
||||
$this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime);
|
||||
$this->deleteExpiredSessions($project, $getProjectDB);
|
||||
$this->deleteExpiredTransactions($project, $getProjectDB);
|
||||
$this->deleteExpiredPresences($project, $getProjectDB);
|
||||
$this->deleteOldDeployments($queueForDeletes, $project, $getProjectDB);
|
||||
break;
|
||||
default:
|
||||
@@ -1644,4 +1645,16 @@ class Deletes extends Action
|
||||
// Swallow errors to avoid breaking the cleanup process
|
||||
});
|
||||
}
|
||||
|
||||
private function deleteExpiredPresences(Document $project, callable $getProjectDB): void
|
||||
{
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$dbForProject->deleteDocuments('presenceLogs', [
|
||||
Query::isNotNull('expiry'),
|
||||
Query::lessThan('expiry', DateTime::format(new \DateTime())),
|
||||
], onError: function (Throwable $th) {
|
||||
// Swallow errors to avoid breaking the cleanup process
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +294,5 @@ trait PresenceBase
|
||||
);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('general_argument_invalid', $response['body']['type']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Presence;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
class PresenceExpiryTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
public function testExpiredPresenceDeletedByMaintenance(): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$userId = $this->getUser()['$id'];
|
||||
$expiredAt = \gmdate('Y-m-d\TH:i:s.v\Z', \time() - 120);
|
||||
|
||||
$createServer = $this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
'/presences/' . ID::unique(),
|
||||
[
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
],
|
||||
[
|
||||
'userId' => $userId,
|
||||
'status' => 'online',
|
||||
'metadata' => ['test' => 'presence-expiry'],
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $createServer['headers']['status-code']);
|
||||
$presenceIdServer = $createServer['body']['$id'];
|
||||
|
||||
$expireServer = $this->client->call(
|
||||
Client::METHOD_PATCH,
|
||||
'/presences/' . $presenceIdServer,
|
||||
[
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
],
|
||||
[
|
||||
'userId' => $userId,
|
||||
'expiry' => $expiredAt,
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $expireServer['headers']['status-code']);
|
||||
$this->assertEquals($expiredAt, $expireServer['body']['expiry']);
|
||||
|
||||
$createClient = $this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
'/presences/' . ID::unique(),
|
||||
[
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $this->getUser()['session'],
|
||||
],
|
||||
[
|
||||
'status' => 'online',
|
||||
'metadata' => ['test' => 'presence-expiry-client'],
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $createClient['headers']['status-code']);
|
||||
$presenceIdClient = $createClient['body']['$id'];
|
||||
|
||||
$expireClient = $this->client->call(
|
||||
Client::METHOD_PATCH,
|
||||
'/presences/' . $presenceIdClient,
|
||||
[
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $this->getUser()['session'],
|
||||
],
|
||||
[
|
||||
'expiry' => $expiredAt,
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEquals(200, $expireClient['headers']['status-code']);
|
||||
$this->assertEquals($expiredAt, $expireClient['body']['expiry']);
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute('docker exec appwrite maintenance --type=trigger', '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Maintenance command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$this->assertEventually(function () use ($presenceIdServer, $presenceIdClient, $projectId) {
|
||||
$getServer = $this->client->call(
|
||||
Client::METHOD_GET,
|
||||
'/presences/' . $presenceIdServer,
|
||||
[
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]
|
||||
);
|
||||
|
||||
$getClient = $this->client->call(
|
||||
Client::METHOD_GET,
|
||||
'/presences/' . $presenceIdClient,
|
||||
[
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEquals(404, $getServer['headers']['status-code']);
|
||||
$this->assertEquals(404, $getClient['headers']['status-code']);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user