From 968b1c0861276ea2621d69fe78cd4b19264dbb88 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Wed, 15 Apr 2026 16:11:05 +0530 Subject: [PATCH] updated configs --- app/config/collections/projects.php | 10 ++-- app/realtime.php | 20 ++----- .../Modules/Presences/HTTP/Upsert.php | 19 ++---- tests/e2e/Services/Presence/PresenceBase.php | 59 +++++++++++++++++++ 4 files changed, 74 insertions(+), 34 deletions(-) diff --git a/app/config/collections/projects.php b/app/config/collections/projects.php index 02b286767b..d5e8952896 100644 --- a/app/config/collections/projects.php +++ b/app/config/collections/projects.php @@ -2764,7 +2764,7 @@ return [ 'attributes' => [ [ '$id' => ID::custom('userInternalId'), - 'type' => Database::VAR_ID, + 'type' => Database::VAR_STRING, 'format' => '', 'size' => Database::LENGTH_KEY, 'signed' => true, @@ -2775,7 +2775,7 @@ return [ ], [ '$id' => ID::custom('userId'), - 'type' => Database::VAR_ID, + 'type' => Database::VAR_STRING, 'format' => '', 'size' => Database::LENGTH_KEY, 'signed' => true, @@ -2844,15 +2844,15 @@ return [ // permissions must be sorted before md5 conversion to have deterministic hashes 'indexes' => [ [ - '$id' => ID::custom('_key_userId'), - 'type' => Database::INDEX_KEY, + '$id' => ID::custom('_unique_userId'), + 'type' => Database::INDEX_UNIQUE, 'attributes' => ['userId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC] ], [ '$id' => ID::custom('_key_userInternal'), - 'type' => Database::INDEX_KEY, + 'type' => Database::INDEX_UNIQUE, 'attributes' => ['userInternalId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC] diff --git a/app/realtime.php b/app/realtime.php index 9b1de14a2f..b0e3cac66e 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -1146,23 +1146,15 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re if (array_key_exists('metadata', $message['data'])) { $presenceData['metadata'] = $message['data']['metadata']; } - // for an user if presence is present and expiry is null and service is realtime is the indicator that connection cleanup for the user failed - // in later case of multi-presence per user these must be cleaned up first - // currently in case of single presence per user we can directly upsert $presenceDocument = new Document($presenceData); setPermission($presenceDocument, $message['data']['permissions'] ?? null, $authorization); - $presence = $database->withTransaction(function () use ($database, $userId, $presenceDocument, $message) { - $existingPresence = $database->findOne('presenceLogs', [ - Query::equal('userId', [$userId]), - ]); - if ($existingPresence->isEmpty()) { - $presenceId = $message['data']['presenceId'] ?? 'unique()'; - $presenceDocument->setAttribute('$id', $presenceId === 'unique()' ? ID::unique() : $presenceId); - return $database->createDocument('presenceLogs', $presenceDocument); - } - return $database->updateDocument('presenceLogs', $existingPresence->getId(), $presenceDocument); - }); + $presenceId = $message['data']['presenceId'] ?? 'unique()'; + if ($presenceId !== 'unique()') { + $presenceDocument->setAttribute('$id', $presenceId); + } + + $presence = $database->upsertDocument('presenceLogs', $presenceDocument); $presence->removeAttribute('hostname'); diff --git a/src/Appwrite/Platform/Modules/Presences/HTTP/Upsert.php b/src/Appwrite/Platform/Modules/Presences/HTTP/Upsert.php index 5c8e639359..feb17b10f9 100644 --- a/src/Appwrite/Platform/Modules/Presences/HTTP/Upsert.php +++ b/src/Appwrite/Platform/Modules/Presences/HTTP/Upsert.php @@ -3,7 +3,6 @@ namespace Appwrite\Platform\Modules\Presences\HTTP; use Appwrite\Extend\Exception; -use Appwrite\ID; use Appwrite\Platform\Modules\Presences\HTTP\Action as PresenceAction; use Appwrite\SDK\AuthType; use Appwrite\SDK\Method; @@ -13,7 +12,6 @@ use Appwrite\Utopia\Response; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; -use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Datetime as DatetimeValidator; use Utopia\Database\Validator\Permissions; @@ -118,20 +116,11 @@ class Upsert extends PresenceAction $presenceDocument = new Document($presenceData); $this->setPermission($presenceDocument, $permissions, $user, $authorization); - // inside transaction as realtime also do get -> update/create - $presence = $dbForProject->withTransaction(function () use ($dbForProject, $resolvedUserId, $presenceId, $presenceDocument) { - $existingPresence = $dbForProject->findOne('presenceLogs', [ - Query::equal('userId', [$resolvedUserId]), - ]); + if ($presenceId !== 'unique()') { + $presenceDocument->setAttribute('$id', $presenceId); + } - if ($existingPresence->isEmpty()) { - $presenceId = $presenceId === 'unique()' ? ID::unique() : $presenceId; - $presenceDocument->setAttribute('$id', $presenceId); - return $dbForProject->createDocument('presenceLogs', $presenceDocument); - } - - return $dbForProject->updateDocument('presenceLogs', $existingPresence->getId(), $presenceDocument); - }); + $presence = $dbForProject->upsertDocument('presenceLogs', $presenceDocument); $response->dynamic($presence, Response::MODEL_PRESENCE); } diff --git a/tests/e2e/Services/Presence/PresenceBase.php b/tests/e2e/Services/Presence/PresenceBase.php index 88360e8acc..1a8958ddce 100644 --- a/tests/e2e/Services/Presence/PresenceBase.php +++ b/tests/e2e/Services/Presence/PresenceBase.php @@ -312,4 +312,63 @@ trait PresenceBase $this->assertEquals(400, $response['headers']['status-code']); } + + public function testUpsertSameUserMaintainsSinglePresence(): void + { + if ($this->getSide() === 'client') { + $this->expectNotToPerformAssertions(); + return; + } + + $projectId = $this->getProject()['$id']; + $userId = $this->getUser()['$id']; + $headers = \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders(false)); + + $firstUpsert = $this->client->call( + Client::METHOD_PUT, + '/presences/' . ID::unique(), + $headers, + [ + 'userId' => $userId, + 'status' => 'online', + 'metadata' => ['source' => 'first-upsert'], + ] + ); + $this->assertEquals(200, $firstUpsert['headers']['status-code']); + + $secondUpsert = $this->client->call( + Client::METHOD_PUT, + '/presences/' . ID::unique(), + $headers, + [ + 'userId' => $userId, + 'status' => 'away', + 'metadata' => ['source' => 'second-upsert'], + ] + ); + $this->assertEquals(200, $secondUpsert['headers']['status-code']); + + $this->assertEquals('away', $secondUpsert['body']['status']); + $this->assertEquals(['source' => 'second-upsert'], $secondUpsert['body']['metadata']); + + $list = $this->client->call( + Client::METHOD_GET, + '/presences', + $headers, + [ + 'queries' => [ + Query::equal('userId', [$userId])->toString(), + ], + ] + ); + + $this->assertEquals(200, $list['headers']['status-code']); + $this->assertEquals(1, $list['body']['total']); + $this->assertCount(1, $list['body']['presences']); + $this->assertEquals($userId, $list['body']['presences'][0]['userId']); + $this->assertEquals('away', $list['body']['presences'][0]['status']); + } }