mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.9.x' into add-api-key-migration
This commit is contained in:
@@ -2755,4 +2755,146 @@ return [
|
||||
]
|
||||
]
|
||||
],
|
||||
|
||||
// Naming it presenceLogs as later it might be only be used as a presence events table only and not for the actual presence
|
||||
'presenceLogs' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('presenceLogs'),
|
||||
'name' => 'Presence Logs',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('userInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('expiresAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('source'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('hostname'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('metadata'),
|
||||
'type' => Database::VAR_TEXT,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => new \stdClass(),
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('permissionsHash'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 32,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$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,
|
||||
'attributes' => ['userInternalId'],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_expiresAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['expiresAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['status'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_source'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['source'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_source_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['source', 'status']
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_permissionsHash'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['permissionsHash']
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -2798,146 +2798,4 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// Naming it presenceLogs as later it might be only be used as a presence events table only and not for the actual presence
|
||||
'presenceLogs' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('presenceLogs'),
|
||||
'name' => 'Presence Logs',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('userInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('expiresAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('source'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('hostname'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('metadata'),
|
||||
'type' => Database::VAR_TEXT,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => new \stdClass(),
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('permissionsHash'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 32,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$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,
|
||||
'attributes' => ['userInternalId'],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_expiresAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['expiresAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['status'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_source'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['source'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_source_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['source', 'status']
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_permissionsHash'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['permissionsHash']
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
@@ -707,6 +707,7 @@ services:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_POOL_ADAPTER
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
||||
+400
-303
File diff suppressed because it is too large
Load Diff
@@ -127,7 +127,7 @@ trait PresenceBase
|
||||
|
||||
public function testUpsertAndGetPresence(): void
|
||||
{
|
||||
if ($this->getSide() === 'client') {
|
||||
if ($this->getSide() === 'client' || $this->getSide() === 'console') {
|
||||
$userId = $this->getUser()['$id'];
|
||||
|
||||
$upsert = $this->client->call(
|
||||
@@ -183,7 +183,7 @@ trait PresenceBase
|
||||
|
||||
public function testListPresences(): void
|
||||
{
|
||||
if ($this->getSide() === 'client') {
|
||||
if ($this->getSide() === 'client' || $this->getSide() === 'console') {
|
||||
$upsert = $this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
'/presences/' . ID::unique(),
|
||||
@@ -224,18 +224,36 @@ trait PresenceBase
|
||||
// Client sessions must not be able to list presences belonging to a different user.
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$originalUser = $this->getUser();
|
||||
$otherUserId = $this->getUser(true)['$id'];
|
||||
$otherUser = $this->getUser(true);
|
||||
$otherUserId = $otherUser['$id'];
|
||||
|
||||
// Important: don't let `getUser(true)` overwrite the cached user/session for the rest
|
||||
// of this test run. We only need the other user's ID.
|
||||
// of this test run.
|
||||
self::$user[$projectId] = $originalUser;
|
||||
|
||||
// Seed another presence for the other user (setup via API key, not the client session).
|
||||
$this->setupPresence([
|
||||
'userId' => $otherUserId,
|
||||
'status' => 'online',
|
||||
'metadata' => ['device' => 'other-user'],
|
||||
]);
|
||||
if ($projectId === 'console') {
|
||||
// The console project has no API keys; seed via the other user's own session.
|
||||
$this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
'/presences/' . ID::unique(),
|
||||
[
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $otherUser['session'],
|
||||
],
|
||||
[
|
||||
'status' => 'online',
|
||||
'metadata' => ['device' => 'other-user'],
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// Seed another presence for the other user (setup via API key, not the client session).
|
||||
$this->setupPresence([
|
||||
'userId' => $otherUserId,
|
||||
'status' => 'online',
|
||||
'metadata' => ['device' => 'other-user'],
|
||||
]);
|
||||
}
|
||||
|
||||
$otherList = $this->client->call(
|
||||
Client::METHOD_GET,
|
||||
@@ -284,6 +302,8 @@ trait PresenceBase
|
||||
|
||||
public function testClientPresenceCustomPermissionsForOtherUser(): void
|
||||
{
|
||||
// Requires API key to create two concurrent presences for the same user with
|
||||
// different ACLs. Server-only — also skipped on console (which has no API keys).
|
||||
if ($this->getSide() !== 'client') {
|
||||
$this->expectNotToPerformAssertions();
|
||||
return;
|
||||
@@ -431,7 +451,7 @@ trait PresenceBase
|
||||
|
||||
public function testUpdatePresenceSparseFields(): void
|
||||
{
|
||||
if ($this->getSide() === 'client') {
|
||||
if ($this->getSide() === 'client' || $this->getSide() === 'console') {
|
||||
$upsert = $this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
'/presences/' . ID::unique(),
|
||||
@@ -614,7 +634,7 @@ trait PresenceBase
|
||||
|
||||
public function testDeletePresence(): void
|
||||
{
|
||||
if ($this->getSide() === 'client') {
|
||||
if ($this->getSide() === 'client' || $this->getSide() === 'console') {
|
||||
$upsert = $this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
'/presences/' . ID::unique(),
|
||||
@@ -671,6 +691,14 @@ trait PresenceBase
|
||||
|
||||
public function testUpdatePresencePurgeListCache(): void
|
||||
{
|
||||
if ($this->getProject()['$id'] === 'console') {
|
||||
// The console project shares dbForPlatform's cache with every other request,
|
||||
// so parallel workers can wipe the list cache between calls and the hit/miss
|
||||
// assertions become flaky. Skip on console.
|
||||
$this->expectNotToPerformAssertions();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->getSide() === 'client') {
|
||||
$upsert = $this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
@@ -743,6 +771,11 @@ trait PresenceBase
|
||||
|
||||
public function testUpdatePresencePurgeOnlyListCache(): void
|
||||
{
|
||||
if ($this->getProject()['$id'] === 'console') {
|
||||
$this->expectNotToPerformAssertions();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->getSide() === 'client') {
|
||||
$upsert = $this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
@@ -814,6 +847,11 @@ trait PresenceBase
|
||||
|
||||
public function testDeletePresencePurgesListCache(): void
|
||||
{
|
||||
if ($this->getProject()['$id'] === 'console') {
|
||||
$this->expectNotToPerformAssertions();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->getSide() === 'client') {
|
||||
$upsert = $this->client->call(
|
||||
Client::METHOD_PUT,
|
||||
@@ -875,7 +913,7 @@ trait PresenceBase
|
||||
|
||||
public function testUpdateNotFound(): void
|
||||
{
|
||||
if ($this->getSide() === 'client') {
|
||||
if ($this->getSide() === 'client' || $this->getSide() === 'console') {
|
||||
$response = $this->client->call(
|
||||
Client::METHOD_PATCH,
|
||||
'/presences/' . ID::unique(),
|
||||
@@ -938,7 +976,8 @@ trait PresenceBase
|
||||
|
||||
public function testServerRequiresUserId(): void
|
||||
{
|
||||
if ($this->getSide() === 'client') {
|
||||
// Server-only behavior — also skipped on console (no API keys for the console project).
|
||||
if ($this->getSide() === 'client' || $this->getSide() === 'console') {
|
||||
$this->expectNotToPerformAssertions();
|
||||
return;
|
||||
}
|
||||
@@ -960,7 +999,8 @@ trait PresenceBase
|
||||
|
||||
public function testUpsertSameUserMaintainsSinglePresence(): void
|
||||
{
|
||||
if ($this->getSide() === 'client') {
|
||||
// Server-only behavior — also skipped on console (no API keys for the console project).
|
||||
if ($this->getSide() === 'client' || $this->getSide() === 'console') {
|
||||
$this->expectNotToPerformAssertions();
|
||||
return;
|
||||
}
|
||||
@@ -1032,7 +1072,7 @@ trait PresenceBase
|
||||
*/
|
||||
public function testCrossUserUpsertDoesNotOverwriteForeignPresence(): void
|
||||
{
|
||||
if ($this->getSide() !== 'client') {
|
||||
if ($this->getSide() !== 'client' && $this->getSide() !== 'console') {
|
||||
$this->expectNotToPerformAssertions();
|
||||
return;
|
||||
}
|
||||
@@ -1091,14 +1131,20 @@ trait PresenceBase
|
||||
|
||||
// Verify User1's row is intact. Read via a presence-scoped API key to bypass
|
||||
// any read-permission ambiguity and inspect the persisted state directly.
|
||||
$check = $this->client->call(
|
||||
Client::METHOD_GET,
|
||||
'/presences/' . $sharedPresenceId,
|
||||
[
|
||||
// The console project has no API keys, so fall back to user1's own session —
|
||||
// if the bug ever resurfaces and user2 overwrote the row, user1 would lose
|
||||
// read permission and this GET would return 404, still surfacing the failure.
|
||||
$checkHeaders = $projectId === 'console'
|
||||
? $headersUser1
|
||||
: [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getPresenceApiKey(),
|
||||
]
|
||||
];
|
||||
$check = $this->client->call(
|
||||
Client::METHOD_GET,
|
||||
'/presences/' . $sharedPresenceId,
|
||||
$checkHeaders
|
||||
);
|
||||
$this->assertEquals(200, $check['headers']['status-code']);
|
||||
$this->assertEquals($user1['$id'], $check['body']['userId']);
|
||||
|
||||
@@ -9,15 +9,38 @@ use Tests\E2E\Scopes\SideConsole;
|
||||
|
||||
class PresenceConsoleClientTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
use PresenceBase;
|
||||
use ProjectCustom {
|
||||
getProject as getCustomProject;
|
||||
}
|
||||
use SideConsole {
|
||||
getHeaders as getAdminHeaders;
|
||||
}
|
||||
|
||||
public function getProject(bool $fresh = false): array
|
||||
{
|
||||
return ['$id' => 'console'];
|
||||
}
|
||||
|
||||
// `x-appwrite-mode: admin` is forbidden for the console project, so authenticate
|
||||
// as a console session user instead — `getUser()` signs them up against project=console.
|
||||
public function getHeaders(bool $devKey = true): array
|
||||
{
|
||||
return [
|
||||
'origin' => 'http://localhost',
|
||||
'cookie' => 'a_session_console=' . $this->getUser()['session'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testGetPresenceUsage(): void
|
||||
{
|
||||
// Usage requires admin scope, which the console project rejects — run against a regular project.
|
||||
$projectId = $this->getCustomProject()['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/presences/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getAdminHeaders()), [
|
||||
'range' => '32h',
|
||||
]);
|
||||
|
||||
@@ -25,8 +48,8 @@ class PresenceConsoleClientTest extends Scope
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/presences/usage', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getAdminHeaders()), [
|
||||
'range' => '24h',
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user