mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch '1.9.x' into feat-public-platform-api
This commit is contained in:
@@ -98,6 +98,9 @@ Http::init()
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle user authentication and session validation.
|
||||
@@ -489,6 +492,10 @@ Http::init()
|
||||
$request->setUser($user);
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $route->getMatchedPath();
|
||||
$databaseType = match (true) {
|
||||
str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB,
|
||||
|
||||
+16
-29
@@ -1,20 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Projects\Http\Projects\Labels;
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Labels;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
@@ -27,39 +22,37 @@ class Update extends Action
|
||||
return 'updateProjectLabels';
|
||||
}
|
||||
|
||||
protected function getQueriesValidator(): Validator
|
||||
{
|
||||
return new Projects();
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/projects/:projectId/labels')
|
||||
->setHttpPath('/v1/project/labels')
|
||||
->httpAlias('/v1/projects/:projectId/labels')
|
||||
->desc('Update project labels')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'labels.*.update')
|
||||
->label('audits.event', 'project.labels.update')
|
||||
->label('audits.resource', 'project.labels/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'updateLabels',
|
||||
description: <<<EOT
|
||||
Update the project labels by its unique ID. Labels can be used to easily filter projects in an organization.
|
||||
Update the project labels. Labels can be used to easily filter projects in an organization.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('labels', [], new ArrayList(new Text(36, allowList: [...Text::NUMBERS, ...Text::ALPHABET_UPPER, ...Text::ALPHABET_LOWER]), APP_LIMIT_ARRAY_LABELS_SIZE), 'Array of project labels. Replaces the previous labels. Maximum of ' . APP_LIMIT_ARRAY_LABELS_SIZE . ' labels are allowed, each up to 36 alphanumeric characters long.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -67,17 +60,11 @@ class Update extends Action
|
||||
* @param array<string> $labels
|
||||
*/
|
||||
public function action(
|
||||
string $projectId,
|
||||
array $labels,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
Database $dbForPlatform,
|
||||
Document $project
|
||||
): void {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$labels = (array) \array_values(\array_unique($labels));
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels]));
|
||||
@@ -16,6 +16,7 @@ use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Web\Update as Updat
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows\Create as CreateWindowsPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows\Update as UpdateWindowsPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\XList as ListPlatforms;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Labels\Update as UpdateProjectLabels;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Create as CreateVariable;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Delete as DeleteVariable;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Get as GetVariable;
|
||||
@@ -31,6 +32,9 @@ class Http extends Service
|
||||
|
||||
// Hooks
|
||||
$this->addAction(Init::getName(), new Init());
|
||||
|
||||
// Project
|
||||
$this->addAction(UpdateProjectLabels::getName(), new UpdateProjectLabels());
|
||||
|
||||
// Variables
|
||||
$this->addAction(CreateVariable::getName(), new CreateVariable());
|
||||
|
||||
@@ -8,7 +8,6 @@ use Appwrite\Platform\Modules\Projects\Http\DevKeys\Get as GetDevKey;
|
||||
use Appwrite\Platform\Modules\Projects\Http\DevKeys\Update as UpdateDevKey;
|
||||
use Appwrite\Platform\Modules\Projects\Http\DevKeys\XList as ListDevKeys;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\Create as CreateProject;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\Labels\Update as UpdateProjectLabels;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\Team\Update as UpdateProjectTeam;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\Update as UpdateProject;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\XList as ListProjects;
|
||||
@@ -31,7 +30,6 @@ class Http extends Service
|
||||
$this->addAction(CreateProject::getName(), new CreateProject());
|
||||
$this->addAction(UpdateProject::getName(), new UpdateProject());
|
||||
$this->addAction(ListProjects::getName(), new ListProjects());
|
||||
$this->addAction(UpdateProjectLabels::getName(), new UpdateProjectLabels());
|
||||
$this->addAction(UpdateProjectTeam::getName(), new UpdateProjectTeam());
|
||||
|
||||
$this->addAction(CreateSchedule::getName(), new CreateSchedule());
|
||||
|
||||
@@ -1324,8 +1324,8 @@ class UsageTest extends Scope
|
||||
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
|
||||
$this->validateDates($response['body']['requests']);
|
||||
// vectordbTotal should reflect only VectorsDB instances, not relational databases.
|
||||
$this->assertEquals($vectordbTotal, $response['body']['vectordbDatabasesTotal']);
|
||||
$this->assertEquals($documentsTotal, $response['body']['vectordbDocumentsTotal']);
|
||||
$this->assertEquals($vectordbTotal, $response['body']['vectorsdbDatabasesTotal']);
|
||||
$this->assertEquals($documentsTotal, $response['body']['vectorsdbDocumentsTotal']);
|
||||
});
|
||||
|
||||
$response = $this->client->call(
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Project;
|
||||
|
||||
trait LabelsBase
|
||||
{
|
||||
// Update labels tests
|
||||
|
||||
public function testUpdateLabels(): void
|
||||
{
|
||||
$response = $this->updateLabels(['frontend', 'backend']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertIsArray($response['body']['labels']);
|
||||
$this->assertCount(2, $response['body']['labels']);
|
||||
$this->assertContains('frontend', $response['body']['labels']);
|
||||
$this->assertContains('backend', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsReplace(): void
|
||||
{
|
||||
$response = $this->updateLabels(['alpha', 'beta']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(2, $response['body']['labels']);
|
||||
$this->assertContains('alpha', $response['body']['labels']);
|
||||
$this->assertContains('beta', $response['body']['labels']);
|
||||
|
||||
// Replace with new labels
|
||||
$response = $this->updateLabels(['gamma', 'delta', 'epsilon']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(3, $response['body']['labels']);
|
||||
$this->assertContains('gamma', $response['body']['labels']);
|
||||
$this->assertContains('delta', $response['body']['labels']);
|
||||
$this->assertContains('epsilon', $response['body']['labels']);
|
||||
$this->assertNotContains('alpha', $response['body']['labels']);
|
||||
$this->assertNotContains('beta', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsEmpty(): void
|
||||
{
|
||||
// Set some labels first
|
||||
$response = $this->updateLabels(['toRemove']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['labels']);
|
||||
|
||||
// Clear all labels
|
||||
$response = $this->updateLabels([]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertIsArray($response['body']['labels']);
|
||||
$this->assertCount(0, $response['body']['labels']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsDeduplicated(): void
|
||||
{
|
||||
$response = $this->updateLabels(['duplicate', 'duplicate', 'unique']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(2, $response['body']['labels']);
|
||||
$this->assertContains('duplicate', $response['body']['labels']);
|
||||
$this->assertContains('unique', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsSingleLabel(): void
|
||||
{
|
||||
$response = $this->updateLabels(['solo']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['labels']);
|
||||
$this->assertContains('solo', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsWithoutAuthentication(): void
|
||||
{
|
||||
$response = $this->updateLabels(['unauthorized'], false);
|
||||
|
||||
$this->assertSame(401, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidLabelTooLong(): void
|
||||
{
|
||||
$response = $this->updateLabels([str_repeat('a', 37)]);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidLabelCharacters(): void
|
||||
{
|
||||
$response = $this->updateLabels(['invalid-label!']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsAlphanumericOnly(): void
|
||||
{
|
||||
$response = $this->updateLabels(['ABC123', 'lowercase', 'UPPERCASE', '0123456789']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(4, $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsMaxLength(): void
|
||||
{
|
||||
$label = str_repeat('a', 36);
|
||||
$response = $this->updateLabels([$label]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['labels']);
|
||||
$this->assertContains($label, $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsIdempotent(): void
|
||||
{
|
||||
$labels = ['stable', 'production'];
|
||||
|
||||
$first = $this->updateLabels($labels);
|
||||
$this->assertSame(200, $first['headers']['status-code']);
|
||||
|
||||
$second = $this->updateLabels($labels);
|
||||
$this->assertSame(200, $second['headers']['status-code']);
|
||||
|
||||
$this->assertSame($first['body']['labels'], $second['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsDeduplicatedOrder(): void
|
||||
{
|
||||
$response = $this->updateLabels(['b', 'a', 'b']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(2, $response['body']['labels']);
|
||||
$this->assertSame('b', $response['body']['labels'][0]);
|
||||
$this->assertSame('a', $response['body']['labels'][1]);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidHyphen(): void
|
||||
{
|
||||
$response = $this->updateLabels(['my-label']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidUnderscore(): void
|
||||
{
|
||||
$response = $this->updateLabels(['my_label']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidSpace(): void
|
||||
{
|
||||
$response = $this->updateLabels(['my label']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidEmptyString(): void
|
||||
{
|
||||
$response = $this->updateLabels(['']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsResponseModel(): void
|
||||
{
|
||||
$response = $this->updateLabels(['test']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertArrayHasKey('$id', $response['body']);
|
||||
$this->assertArrayHasKey('name', $response['body']);
|
||||
$this->assertArrayHasKey('labels', $response['body']);
|
||||
$this->assertIsArray($response['body']['labels']);
|
||||
$this->assertContains('test', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* @param array<string> $labels
|
||||
*/
|
||||
protected function updateLabels(array $labels, bool $authenticated = true): mixed
|
||||
{
|
||||
$headers = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
];
|
||||
|
||||
if ($authenticated) {
|
||||
$headers = array_merge($headers, $this->getHeaders());
|
||||
}
|
||||
|
||||
return $this->client->call(\Tests\E2E\Client::METHOD_PUT, '/project/labels', $headers, [
|
||||
'labels' => $labels,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Project;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
|
||||
class LabelsConsoleClientTest extends Scope
|
||||
{
|
||||
use LabelsBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Project;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class LabelsCustomServerTest extends Scope
|
||||
{
|
||||
use LabelsBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
}
|
||||
Reference in New Issue
Block a user