1
0
Fork 0
mirror of synced 2024-10-01 01:37:56 +13:00

Merge remote-tracking branch 'origin/1.3.x' into feat-relations-2

# Conflicts:
#	app/config/specs/open-api3-latest-client.json
#	app/config/specs/open-api3-latest-console.json
#	app/config/specs/open-api3-latest-server.json
#	app/config/specs/swagger2-latest-client.json
#	app/config/specs/swagger2-latest-console.json
#	app/config/specs/swagger2-latest-server.json
This commit is contained in:
Jake Barnby 2023-03-25 15:32:55 +13:00
commit 0797df8414
No known key found for this signature in database
GPG key ID: C437A8CC85B96E9C
13 changed files with 330 additions and 21 deletions

View file

@ -1875,6 +1875,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('prefs'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65535,
'signed' => true,
'required' => false,
'default' => new \stdClass(),
'array' => false,
'filters' => ['json'],
],
],
'indexes' => [
[

View file

@ -183,13 +183,16 @@ return [
],
],
'create' => [
'$description' => 'This event triggers when a bucket is created.'
'$description' => 'This event triggers when a team is created.'
],
'delete' => [
'$description' => 'This event triggers when a bucket is deleted.',
'$description' => 'This event triggers when a team is deleted.',
],
'update' => [
'$description' => 'This event triggers when a bucket is updated.',
'$description' => 'This event triggers when a team is updated.',
'prefs' => [
'$description' => 'This event triggers when a team\'s preferences are updated.',
],
]
],
'functions' => [

View file

@ -36,8 +36,9 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Locale\Locale;
use Utopia\Validator\Text;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Text;
App::post('/v1/teams')
->desc('Create Team')
@ -75,6 +76,7 @@ App::post('/v1/teams')
],
'name' => $name,
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
'prefs' => new \stdClass(),
'search' => implode(' ', [$teamId, $name]),
])));
@ -198,8 +200,36 @@ App::get('/v1/teams/:teamId')
$response->dynamic($team, Response::MODEL_TEAM);
});
App::get('/v1/teams/:teamId/prefs')
->desc('Get Team Preferences')
->groups(['api', 'teams'])
->label('scope', 'teams.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'getPrefs')
->label('sdk.description', '/docs/references/teams/get-team-prefs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PREFERENCES)
->label('sdk.offline.model', '/teams/{teamId}/prefs')
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('dbForProject')
->action(function (string $teamId, Response $response, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$prefs = $team->getAttribute('prefs', new \stdClass());
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
App::put('/v1/teams/:teamId')
->desc('Update Team')
->desc('Update Name')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].update')
->label('scope', 'teams.write')
@ -207,8 +237,8 @@ App::put('/v1/teams/:teamId')
->label('audits.resource', 'team/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'update')
->label('sdk.description', '/docs/references/teams/update-team.md')
->label('sdk.method', 'updateName')
->label('sdk.description', '/docs/references/teams/update-team-name.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM)
@ -241,6 +271,42 @@ App::put('/v1/teams/:teamId')
$response->dynamic($team, Response::MODEL_TEAM);
});
App::put('/v1/teams/:teamId/prefs')
->desc('Update Preferences')
->groups(['api', 'teams'])
->label('event', 'teams.[teamId].update.prefs')
->label('scope', 'teams.write')
->label('audits.event', 'team.update')
->label('audits.resource', 'team/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'updatePrefs')
->label('sdk.description', '/docs/references/teams/update-team-prefs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PREFERENCES)
->label('sdk.offline.model', '/teams/{teamId}/prefs')
->param('teamId', '', new UID(), 'Team ID.')
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('dbForProject')
->inject('events')
->action(function (string $teamId, array $prefs, Response $response, Database $dbForProject, Event $events) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$team = $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('prefs', $prefs));
$events->setParam('teamId', $team->getId());
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
});
App::delete('/v1/teams/:teamId')
->desc('Delete Team')
->groups(['api', 'teams'])

View file

@ -0,0 +1 @@
Get the team's shared preferences by its unique ID. If a preference doesn't need to be shared by all team members, prefer storing them in [user preferences](/docs/client/account#accountGetPrefs).

View file

@ -0,0 +1 @@
Update the team's name by its unique ID.

View file

@ -0,0 +1 @@
Update the team's preferences by its unique ID. The object you pass is stored as is and replaces any previous value. The maximum allowed prefs size is 64kB and throws an error if exceeded.

View file

@ -1 +0,0 @@
Update a team using its ID. Only members with the owner role can update the team.

View file

@ -40,6 +40,12 @@ class Team extends Model
'default' => 0,
'example' => 7,
])
->addRule('prefs', [
'type' => Response::MODEL_PREFERENCES,
'description' => 'Team preferences as a key-value object',
'default' => new \stdClass(),
'example' => ['theme' => 'pink', 'timezone' => 'UTC'],
])
;
}

View file

@ -110,9 +110,12 @@ trait Base
// Teams
public static string $GET_TEAM = 'get_team';
public static string $GET_TEAM_PREFERENCES = 'get_team_preferences';
public static string $GET_TEAMS = 'list_teams';
public static string $CREATE_TEAM = 'create_team';
public static string $UPDATE_TEAM = 'update_team';
public static string $UPDATE_TEAM_NAME = 'update_team_name';
public static string $UPDATE_TEAM_PREFERENCES = 'update_team_preferences';
public static string $DELETE_TEAM = 'delete_team';
public static string $GET_TEAM_MEMBERSHIP = 'get_team_membership';
public static string $GET_TEAM_MEMBERSHIPS = 'list_team_memberships';
@ -1212,6 +1215,12 @@ trait Base
total
}
}';
case self::$GET_TEAM_PREFERENCES:
return 'query getTeamPreferences($teamId: String!) {
teamsGetPrefs(teamId: $teamId) {
data
}
}';
case self::$GET_TEAMS:
return 'query listTeams {
teamsList {
@ -1230,14 +1239,20 @@ trait Base
total
}
}';
case self::$UPDATE_TEAM:
return 'mutation updateTeam($teamId: String!, $name: String!){
teamsUpdate(teamId: $teamId, name : $name) {
case self::$UPDATE_TEAM_NAME:
return 'mutation updateTeamName($teamId: String!, $name: String!){
teamsUpdateName(teamId: $teamId, name : $name) {
_id
name
total
}
}';
case self::$UPDATE_TEAM_PREFERENCES:
return 'mutation updateTeamPrefs($teamId: String!, $prefs: Assoc!){
teamsUpdatePrefs(teamId: $teamId, prefs: $prefs) {
data
}
}';
case self::$DELETE_TEAM:
return 'mutation deleteTeam($teamId: String!){
teamsDelete(teamId: $teamId) {

View file

@ -111,6 +111,62 @@ class TeamsServerTest extends Scope
$this->assertArrayNotHasKey('errors', $team['body']);
$team = $team['body']['data']['teamsGet'];
$this->assertIsArray($team);
return $team;
}
/**
* @depends testGetTeam
*/
public function testUpdateTeamPrefs($team)
{
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$UPDATE_TEAM_PREFERENCES);
$graphQLPayload = [
'query' => $query,
'variables' => [
'teamId' => $team['_id'],
'prefs' => [
'key' => 'value'
]
],
];
$prefs = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertIsArray($prefs['body']['data']);
$this->assertArrayNotHasKey('errors', $prefs['body']);
$this->assertIsArray($prefs['body']['data']['teamsUpdatePrefs']);
$this->assertEquals('{"key":"value"}', $prefs['body']['data']['teamsUpdatePrefs']['data']);
return $team;
}
/**
* @depends testUpdateTeamPrefs
*/
public function testGetTeamPreferences($team)
{
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$GET_TEAM_PREFERENCES);
$graphQLPayload = [
'query' => $query,
'variables' => [
'teamId' => $team['_id'],
]
];
$prefs = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertIsArray($prefs['body']['data']);
$this->assertArrayNotHasKey('errors', $prefs['body']);
$this->assertIsArray($prefs['body']['data']['teamsGetPrefs']);
}
/**
@ -168,7 +224,7 @@ class TeamsServerTest extends Scope
public function testUpdateTeam($team)
{
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$UPDATE_TEAM);
$query = $this->getQuery(self::$UPDATE_TEAM_NAME);
$graphQLPayload = [
'query' => $query,
'variables' => [
@ -184,7 +240,7 @@ class TeamsServerTest extends Scope
$this->assertIsArray($team['body']['data']);
$this->assertArrayNotHasKey('errors', $team['body']);
$team = $team['body']['data']['teamsUpdate'];
$team = $team['body']['data']['teamsUpdateName'];
$this->assertEquals('New Name', $team['name']);
}

View file

@ -1459,6 +1459,43 @@ class RealtimeCustomClientTest extends Scope
$this->assertContains("teams.*", $response['data']['events']);
$this->assertNotEmpty($response['data']['payload']);
/**
* Test Team Update Prefs
*/
$team = $this->client->call(Client::METHOD_PUT, '/teams/' . $teamId . '/prefs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'prefs' => [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
]
]);
$this->assertEquals($team['headers']['status-code'], 200);
$this->assertEquals($team['body']['funcKey1'], 'funcValue1');
$this->assertEquals($team['body']['funcKey2'], 'funcValue2');
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('teams', $response['data']['channels']);
$this->assertContains("teams.{$teamId}", $response['data']['channels']);
$this->assertContains("teams.{$teamId}.update", $response['data']['events']);
$this->assertContains("teams.{$teamId}.update.prefs", $response['data']['events']);
$this->assertContains("teams.{$teamId}", $response['data']['events']);
$this->assertContains("teams.*.update.prefs", $response['data']['events']);
$this->assertContains("teams.*.update", $response['data']['events']);
$this->assertContains("teams.*", $response['data']['events']);
$this->assertNotEmpty($response['data']['payload']);
$this->assertEquals($response['data']['payload']['funcKey1'], 'funcValue1');
$this->assertEquals($response['data']['payload']['funcKey2'], 'funcValue2');
$client->close();
return ['teamId' => $teamId];

View file

@ -27,6 +27,8 @@ trait TeamsBase
$this->assertEquals('Arsenal', $response1['body']['name']);
$this->assertGreaterThan(-1, $response1['body']['total']);
$this->assertIsInt($response1['body']['total']);
$this->assertArrayHasKey('prefs', $response1['body']);
$dateValidator = new DatetimeValidator();
$this->assertEquals(true, $dateValidator->isValid($response1['body']['$createdAt']));
@ -48,6 +50,7 @@ trait TeamsBase
$this->assertEquals('Manchester United', $response2['body']['name']);
$this->assertGreaterThan(-1, $response2['body']['total']);
$this->assertIsInt($response2['body']['total']);
$this->assertArrayHasKey('prefs', $response2['body']);
$this->assertEquals(true, $dateValidator->isValid($response2['body']['$createdAt']));
$response3 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
@ -64,6 +67,7 @@ trait TeamsBase
$this->assertGreaterThan(-1, $response3['body']['total']);
$this->assertIsInt($response3['body']['total']);
$this->assertEquals(true, $dateValidator->isValid($response3['body']['$createdAt']));
/**
* Test for FAILURE
*/
@ -98,6 +102,7 @@ trait TeamsBase
$this->assertEquals('Arsenal', $response['body']['name']);
$this->assertGreaterThan(-1, $response['body']['total']);
$this->assertIsInt($response['body']['total']);
$this->assertArrayHasKey('prefs', $response['body']);
$dateValidator = new DatetimeValidator();
$this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt']));
@ -292,6 +297,7 @@ trait TeamsBase
$this->assertEquals('Demo New', $response['body']['name']);
$this->assertGreaterThan(-1, $response['body']['total']);
$this->assertIsInt($response['body']['total']);
$this->assertArrayHasKey('prefs', $response['body']);
$this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt']));
/**
@ -328,6 +334,7 @@ trait TeamsBase
$this->assertEquals('Demo', $response['body']['name']);
$this->assertGreaterThan(-1, $response['body']['total']);
$this->assertIsInt($response['body']['total']);
$this->assertArrayHasKey('prefs', $response['body']);
$dateValidator = new DatetimeValidator();
$this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt']));
@ -351,4 +358,63 @@ trait TeamsBase
return [];
}
/**
* @depends testCreateTeam
*/
public function testUpdateAndGetUserPrefs(array $data): void
{
$id = $data['teamUid'] ?? '';
/**
* Test for SUCCESS
*/
$team = $this->client->call(Client::METHOD_PUT, '/teams/' . $id . '/prefs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'prefs' => [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
],
]);
$this->assertEquals($team['headers']['status-code'], 200);
$this->assertEquals($team['body']['funcKey1'], 'funcValue1');
$this->assertEquals($team['body']['funcKey2'], 'funcValue2');
$team = $this->client->call(Client::METHOD_GET, '/teams/' . $id . '/prefs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($team['headers']['status-code'], 200);
$this->assertEquals($team['body'], [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
]);
$team = $this->client->call(Client::METHOD_GET, '/teams/' . $id, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($team['headers']['status-code'], 200);
$this->assertEquals($team['body']['prefs'], [
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
]);
/**
* Test for FAILURE
*/
$user = $this->client->call(Client::METHOD_PUT, '/teams/' . $id . '/prefs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'prefs' => 'bad-string',
]);
$this->assertEquals($user['headers']['status-code'], 400);
}
}

View file

@ -777,6 +777,53 @@ trait WebhooksBase
return ['teamId' => $team['body']['$id']];
}
/**
* @depends testCreateTeam
*/
public function testUpdateTeamPrefs(array $data): array
{
$id = $data['teamId'] ?? '';
$team = $this->client->call(Client::METHOD_PUT, '/teams/' . $id . '/prefs', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'prefs' => [
'prefKey1' => 'prefValue1',
'prefKey2' => 'prefValue2',
]
]);
$this->assertEquals($team['headers']['status-code'], 200);
$this->assertIsArray($team['body']);
$webhook = $this->getLastRequest();
$signatureKey = $this->getProject()['signatureKey'];
$payload = json_encode($webhook['data']);
$url = $webhook['url'];
$signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true));
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('teams.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('teams.*.update.prefs', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("teams.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("teams.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("teams.{$id}.update.prefs", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide()));
$this->assertEquals($webhook['data'], [
'prefKey1' => 'prefValue1',
'prefKey2' => 'prefValue2',
]);
return $data;
}
public function testDeleteTeam(): array
{
/**