mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'origin/1.8.x' into feat-txn
# Conflicts: # composer.json # composer.lock # tests/e2e/Services/Databases/Legacy/DatabasesBase.php
This commit is contained in:
@@ -383,6 +383,8 @@ class Event
|
||||
{
|
||||
$this->params = [];
|
||||
$this->sensitive = [];
|
||||
$this->event = '';
|
||||
$this->payload = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ abstract class Migration
|
||||
'1.7.2' => 'V22',
|
||||
'1.7.3' => 'V22',
|
||||
'1.7.4' => 'V22',
|
||||
'1.8.0' => 'V23'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Migration\Migration;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class V23 extends Migration
|
||||
{
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
/**
|
||||
* Disable SubQueries for Performance.
|
||||
*/
|
||||
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryDevKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
fn () => []
|
||||
);
|
||||
}
|
||||
|
||||
Console::info('Migrating databases');
|
||||
$this->migrateDatabases();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Databases.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|Throwable
|
||||
*/
|
||||
private function migrateDatabases(): void
|
||||
{
|
||||
if ($this->project->getId() === 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
// since required + default can't be used together
|
||||
// so first creating the attribute then bulk updating the attribute
|
||||
$this->createAttributeFromCollection($this->dbForProject, 'databases', 'type');
|
||||
$this->dbForProject->updateDocuments('databases', new Document(['type' => 'legacy']));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,24 +2,38 @@
|
||||
|
||||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Utopia\DNS\Client;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
|
||||
class DNS extends Validator
|
||||
{
|
||||
public const RECORD_A = 'a';
|
||||
public const RECORD_AAAA = 'aaaa';
|
||||
public const RECORD_CNAME = 'cname';
|
||||
public const RECORD_A = 'A';
|
||||
public const RECORD_AAAA = 'AAAA';
|
||||
public const RECORD_CNAME = 'CNAME';
|
||||
public const RECORD_CAA = 'CAA'; // You can provide domain only (as $target) for CAA validation
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected mixed $logs;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $dnsServer;
|
||||
|
||||
/**
|
||||
* @param string $target
|
||||
*/
|
||||
public function __construct(protected string $target, protected string $type = self::RECORD_CNAME)
|
||||
public function __construct(protected string $target, protected string $type = self::RECORD_CNAME, string $dnsServer = '')
|
||||
{
|
||||
if (empty($dnsServer)) {
|
||||
$dnsServer = System::getEnv('_APP_DNS', '8.8.8.8');
|
||||
}
|
||||
|
||||
$this->dnsServer = $dnsServer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,42 +56,65 @@ class DNS extends Validator
|
||||
* Check if DNS record value matches specific value
|
||||
*
|
||||
* @param mixed $domain
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
$typeNative = match ($this->type) {
|
||||
self::RECORD_A => DNS_A,
|
||||
self::RECORD_AAAA => DNS_AAAA,
|
||||
self::RECORD_CNAME => DNS_CNAME,
|
||||
default => throw new \Exception('Record type not supported.')
|
||||
};
|
||||
|
||||
$dnsKey = match ($this->type) {
|
||||
self::RECORD_A => 'ip',
|
||||
self::RECORD_AAAA => 'ipv6',
|
||||
self::RECORD_CNAME => 'target',
|
||||
default => throw new \Exception('Record type not supported.')
|
||||
};
|
||||
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dns = new Client($this->dnsServer);
|
||||
|
||||
try {
|
||||
$records = \dns_get_record($value, $typeNative);
|
||||
$this->logs = $records;
|
||||
} catch (\Throwable $th) {
|
||||
$rawQuery = $dns->query($value, $this->type);
|
||||
|
||||
// Some DNS servers return all records, not only type that's asked for
|
||||
// Likely occurs when no records of specific type are found
|
||||
$query = array_filter($rawQuery, function ($record) {
|
||||
return $record->getTypeName() === $this->type;
|
||||
});
|
||||
|
||||
$this->logs = $query;
|
||||
} catch (\Exception $e) {
|
||||
$this->logs = ['error' => $e->getMessage()];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$records) {
|
||||
if (empty($query)) {
|
||||
// CAA records inherit from parent (custom CAA behaviour)
|
||||
if ($this->type === self::RECORD_CAA) {
|
||||
$domain = new Domain($value);
|
||||
if ($domain->get() === $domain->getApex()) {
|
||||
return true; // No CAA on apex domain means anyone can issue certificate
|
||||
}
|
||||
|
||||
// Recursive validation by parent domain
|
||||
$parts = \explode('.', $value);
|
||||
\array_shift($parts);
|
||||
$parentDomain = \implode('.', $parts);
|
||||
$validator = new DNS($this->target, DNS::RECORD_CAA, $this->dnsServer);
|
||||
return $validator->isValid($parentDomain);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
if (isset($record[$dnsKey]) && $record[$dnsKey] === $this->target) {
|
||||
foreach ($query as $record) {
|
||||
// CAA validation only needs to ensure domain
|
||||
if ($this->type === self::RECORD_CAA) {
|
||||
// Extract domain; comments showcase extraction steps in most complex scenario
|
||||
$rdata = $record->getRdata(); // 255 issuewild "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
|
||||
$rdata = \explode(' ', $rdata, 3)[2] ?? ''; // "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
|
||||
$rdata = \trim($rdata, '"'); // certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600
|
||||
$rdata = \explode(';', $rdata, 2)[0] ?? ''; // certainly.com
|
||||
|
||||
if ($rdata === $this->target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($record->getRdata() === $this->target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+56
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
@@ -279,4 +280,59 @@ abstract class Action extends UtopiaAction
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For triggering different queues for each document for a bulk documents
|
||||
* @param string $event
|
||||
* @param Document $database
|
||||
* @param Document $collection
|
||||
* @param Document[] $documents
|
||||
* @param Event $queueForEvents
|
||||
* @param Event $queueForRealtime
|
||||
* @param Event $queueForFunctions
|
||||
* @param Event $queueForWebhooks
|
||||
* @return void
|
||||
*/
|
||||
protected function triggerBulk(
|
||||
string $event,
|
||||
Document $database,
|
||||
Document $collection,
|
||||
array $documents,
|
||||
Event $queueForEvents,
|
||||
Event $queueForRealtime,
|
||||
Event $queueForFunctions,
|
||||
Event $queueForWebhooks
|
||||
): void {
|
||||
$queueForEvents
|
||||
->setEvent($event)
|
||||
->setParam('databaseId', $database->getId())
|
||||
->setContext('database', $database)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
foreach ($documents as $document) {
|
||||
$queueForEvents
|
||||
->setParam('documentId', $document->getId())
|
||||
->setParam('rowId', $document->getId())
|
||||
->setPayload($document->getArrayCopy());
|
||||
|
||||
$queueForRealtime
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForEvents->reset();
|
||||
$queueForRealtime->reset();
|
||||
$queueForFunctions->reset();
|
||||
$queueForWebhooks->reset();
|
||||
}
|
||||
}
|
||||
|
||||
+17
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Bulk;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
|
||||
@@ -72,11 +73,15 @@ class Delete extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan): void
|
||||
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
|
||||
{
|
||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||
if ($database->isEmpty()) {
|
||||
@@ -134,5 +139,16 @@ class Delete extends Action
|
||||
'total' => $modified,
|
||||
$this->getSdkGroup() => $documents,
|
||||
]), $this->getResponseModel());
|
||||
|
||||
$this->triggerBulk(
|
||||
'databases.[databaseId].collections.[collectionId].documents.[documentId].delete',
|
||||
$database,
|
||||
$collection,
|
||||
$documents,
|
||||
$queueForEvents,
|
||||
$queueForRealtime,
|
||||
$queueForFunctions,
|
||||
$queueForWebhooks
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+29
-11
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Bulk;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
|
||||
@@ -76,11 +77,15 @@ class Update extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string|array $data, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan): void
|
||||
public function action(string $databaseId, string $collectionId, string|array $data, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
|
||||
{
|
||||
$data = \is_string($data)
|
||||
? \json_decode($data, true)
|
||||
@@ -125,16 +130,18 @@ class Update extends Action
|
||||
$documents = [];
|
||||
|
||||
try {
|
||||
$modified = $dbForProject->updateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
new Document($data),
|
||||
$queries,
|
||||
onNext: function (Document $document) use ($plan, &$documents) {
|
||||
if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$documents[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
$modified = $dbForProject->withPreserveDates(function () use ($plan, &$documents, $dbForProject, $database, $collection, $data, $queries) {
|
||||
return $dbForProject->updateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
new Document($data),
|
||||
$queries,
|
||||
onNext: function (Document $document) use ($plan, &$documents) {
|
||||
if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$documents[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (RelationshipException $e) {
|
||||
@@ -156,5 +163,16 @@ class Update extends Action
|
||||
'total' => $modified,
|
||||
$this->getSdkGroup() => $documents
|
||||
]), $this->getResponseModel());
|
||||
|
||||
$this->triggerBulk(
|
||||
'databases.[databaseId].collections.[collectionId].documents.[documentId].update',
|
||||
$database,
|
||||
$collection,
|
||||
$documents,
|
||||
$queueForEvents,
|
||||
$queueForRealtime,
|
||||
$queueForFunctions,
|
||||
$queueForWebhooks
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-9
@@ -106,15 +106,17 @@ class Upsert extends Action
|
||||
$upserted = [];
|
||||
|
||||
try {
|
||||
$modified = $dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents,
|
||||
onNext: function (Document $document) use ($plan, &$upserted) {
|
||||
if (\count($upserted) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$upserted[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
$modified = $dbForProject->withPreserveDates(function () use ($dbForProject, $database, $collection, $documents, $plan, &$upserted) {
|
||||
return $dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents,
|
||||
onNext: function (Document $document) use ($plan, &$upserted) {
|
||||
if (\count($upserted) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
|
||||
$upserted[] = $document;
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (DuplicateException) {
|
||||
|
||||
+32
-6
@@ -122,10 +122,12 @@ class Create extends Action
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
|
||||
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void
|
||||
{
|
||||
$data = \is_string($data)
|
||||
? \json_decode($data, true)
|
||||
@@ -332,7 +334,7 @@ class Create extends Action
|
||||
}
|
||||
};
|
||||
|
||||
$documents = \array_map(function ($document) use ($collection, $permissions, $checkPermissions, $isBulk, $documentId, $setPermissions) {
|
||||
$documents = \array_map(function ($document) use ($collection, $permissions, $checkPermissions, $isBulk, $documentId, $setPermissions, $isAPIKey, $isPrivilegedUser) {
|
||||
$document['$collection'] = $collection->getId();
|
||||
|
||||
// Determine the source ID depending on whether it's a bulk operation.
|
||||
@@ -350,6 +352,18 @@ class Create extends Action
|
||||
|
||||
// Assign a unique ID if needed, otherwise use the provided ID.
|
||||
$document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId;
|
||||
|
||||
// Allowing to add createdAt and updatedAt timestamps if server side(api key
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
if (isset($document['$createdAt'])) {
|
||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
||||
}
|
||||
|
||||
if (isset($document['$updatedAt'])) {
|
||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
||||
}
|
||||
}
|
||||
|
||||
$document = new Document($document);
|
||||
$setPermissions($document, $permissions);
|
||||
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
|
||||
@@ -358,9 +372,11 @@ class Create extends Action
|
||||
}, $documents);
|
||||
|
||||
try {
|
||||
$dbForProject->createDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents
|
||||
$dbForProject->withPreserveDates(
|
||||
fn () => $dbForProject->createDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$documents,
|
||||
)
|
||||
);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception($this->getDuplicateException());
|
||||
@@ -403,6 +419,16 @@ class Create extends Action
|
||||
$this->getSdkGroup() => $documents
|
||||
]), $this->getBulkResponseModel());
|
||||
|
||||
$this->triggerBulk(
|
||||
'databases.[databaseId].collections.[collectionId].documents.[documentId].create',
|
||||
$database,
|
||||
$collection,
|
||||
$documents,
|
||||
$queueForEvents,
|
||||
$queueForRealtime,
|
||||
$queueForFunctions,
|
||||
$queueForWebhooks
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -106,8 +106,13 @@ class XList extends Action
|
||||
]);
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
// getContext() => `document` or `row`.
|
||||
$resource = 'database/' . $databaseId . '/collection/' . $collectionId . '/' .$this->getContext(). '/' . $document->getId();
|
||||
$type = $this->getCollectionsEventsContext();
|
||||
$context = $this->getContext();
|
||||
$resource = match ($context) {
|
||||
ROWS => "database/$databaseId/grid/$type/$collectionId/$context/{$document->getId()}",
|
||||
default => "database/$databaseId/$type/$collectionId/$context/{$document->getId()}",
|
||||
};
|
||||
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
|
||||
$output = [];
|
||||
|
||||
+12
-2
@@ -109,6 +109,16 @@ class Update extends Action
|
||||
throw new Exception($this->getParentNotFoundException());
|
||||
}
|
||||
|
||||
// Allowing to add createdAt and updatedAt timestamps if server side(api key)
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
if (isset($data['$createdAt'])) {
|
||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
||||
}
|
||||
|
||||
if (isset($data['$updatedAt'])) {
|
||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
||||
}
|
||||
}
|
||||
// Read permission should not be required for update
|
||||
/** @var Document $document */
|
||||
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId));
|
||||
@@ -233,11 +243,11 @@ class Update extends Action
|
||||
try {
|
||||
$document = $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
fn () => $dbForProject->updateDocument(
|
||||
fn () => $dbForProject->withPreserveDates(fn () => $dbForProject->updateDocument(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
$document->getId(),
|
||||
$newDocument
|
||||
)
|
||||
))
|
||||
);
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
|
||||
+19
-7
@@ -153,6 +153,16 @@ class Upsert extends Action
|
||||
}
|
||||
}
|
||||
}
|
||||
// Allowing to add createdAt and updatedAt timestamps if server side(api key)
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
if (isset($data['$createdAt'])) {
|
||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
||||
}
|
||||
|
||||
if (isset($data['$updatedAt'])) {
|
||||
throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.');
|
||||
}
|
||||
}
|
||||
|
||||
$data['$id'] = $documentId;
|
||||
$data['$permissions'] = $permissions ?? [];
|
||||
@@ -236,13 +246,15 @@ class Upsert extends Action
|
||||
|
||||
$upserted = [];
|
||||
try {
|
||||
$dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
[$newDocument],
|
||||
onNext: function (Document $document) use (&$upserted) {
|
||||
$upserted[] = $document;
|
||||
},
|
||||
);
|
||||
$dbForProject->withPreserveDates(function () use (&$upserted, $dbForProject, $database, $collection, $newDocument) {
|
||||
return $dbForProject->createOrUpdateDocuments(
|
||||
'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
[$newDocument],
|
||||
onNext: function (Document $document) use (&$upserted) {
|
||||
$upserted[] = $document;
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (ConflictException) {
|
||||
throw new Exception($this->getConflictException());
|
||||
} catch (DuplicateException) {
|
||||
|
||||
@@ -103,7 +103,12 @@ class XList extends Action
|
||||
]);
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'database/' . $databaseId . '/' . $this->getContext() . '/' . $collectionId;
|
||||
$context = $this->getContext();
|
||||
$resource = match ($context) {
|
||||
TABLES => "database/$databaseId/grid/$context/$collectionId",
|
||||
default => "database/$databaseId/$context/$collectionId",
|
||||
};
|
||||
|
||||
$logs = $audit->getLogsByResource($resource, $queries);
|
||||
|
||||
$output = [];
|
||||
|
||||
@@ -23,6 +23,7 @@ use Utopia\Platform\Action;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
@@ -80,13 +81,14 @@ class Create extends Action
|
||||
->param('databaseId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Database name. Max length: 128 chars.')
|
||||
->param('enabled', true, new Boolean(), 'Is the database enabled? When set to \'disabled\', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled.', true)
|
||||
->param('type', 'grids', new WhiteList(['grids','legacy']), 'Database type.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $name, bool $enabled, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
public function action(string $databaseId, string $name, bool $enabled, string $type, UtopiaResponse $response, Database $dbForProject, Event $queueForEvents): void
|
||||
{
|
||||
$databaseId = $databaseId == 'unique()' ? ID::unique() : $databaseId;
|
||||
|
||||
@@ -96,6 +98,7 @@ class Create extends Action
|
||||
'name' => $name,
|
||||
'enabled' => $enabled,
|
||||
'search' => implode(' ', [$databaseId, $name]),
|
||||
'type' => $type
|
||||
]));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DATABASE_ALREADY_EXISTS);
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class Create extends BooleanCreate
|
||||
->label('scope', 'tables.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ class Update extends BooleanUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ class Create extends DatetimeCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ class Update extends DatetimeUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -36,7 +36,7 @@ class Delete extends AttributesDelete
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -36,7 +36,7 @@ class Create extends EmailCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -38,7 +38,7 @@ class Update extends EmailUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -38,7 +38,7 @@ class Create extends EnumCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -40,7 +40,7 @@ class Update extends EnumUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -36,7 +36,7 @@ class Create extends FloatCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -38,7 +38,7 @@ class Update extends FloatUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -36,7 +36,7 @@ class Create extends IPCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -38,7 +38,7 @@ class Update extends IPUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ class Create extends IntegerCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ class Update extends IntegerUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ class Create extends RelationshipCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ class Update extends RelationshipUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -38,7 +38,7 @@ class Create extends StringCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -40,7 +40,7 @@ class Update extends StringUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -36,7 +36,7 @@ class Create extends URLCreate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].create')
|
||||
->label('audits.event', 'column.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -38,7 +38,7 @@ class Update extends URLUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].columns.[columnId].update')
|
||||
->label('audits.event', 'column.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -38,7 +38,7 @@ class Create extends CollectionCreate
|
||||
->label('scope', 'tables.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'table.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{response.$id}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: null,
|
||||
|
||||
@@ -34,7 +34,7 @@ class Delete extends CollectionDelete
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].delete')
|
||||
->label('audits.event', 'table.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: null,
|
||||
|
||||
@@ -40,7 +40,7 @@ class Create extends IndexCreate
|
||||
->label('scope', 'tables.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'index.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -39,7 +39,7 @@ class Delete extends IndexDelete
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].indexes.[indexId].update')
|
||||
->label('audits.event', 'index.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: $this->getSdkGroup(),
|
||||
|
||||
@@ -35,7 +35,7 @@ class Delete extends DocumentsDelete
|
||||
->label('scope', 'rows.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'rows.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
@@ -59,6 +59,10 @@ class Delete extends DocumentsDelete
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class Update extends DocumentsUpdate
|
||||
->label('scope', 'rows.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'rows.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
@@ -61,6 +61,10 @@ class Update extends DocumentsUpdate
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class Upsert extends DocumentsUpsert
|
||||
->label('scope', 'rows.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'row.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
|
||||
@@ -36,7 +36,7 @@ class Decrement extends DecrementDocumentAttribute
|
||||
->label('scope', 'rows.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'rows.decrement')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
|
||||
@@ -36,7 +36,7 @@ class Increment extends IncrementDocumentAttribute
|
||||
->label('scope', 'rows.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'rows.increment')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
|
||||
@@ -44,7 +44,7 @@ class Create extends DocumentCreate
|
||||
->label('scope', 'rows.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'row.create')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
@@ -101,6 +101,9 @@ class Create extends DocumentCreate
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class Delete extends DocumentDelete
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].rows.[rowId].delete')
|
||||
->label('audits.event', 'row.delete')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}/row/{request.rowId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}/row/{request.rowId}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
|
||||
@@ -37,7 +37,7 @@ class Update extends DocumentUpdate
|
||||
->label('scope', 'rows.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'row.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}/row/{response.$id}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}/row/{response.$id}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
|
||||
@@ -37,7 +37,7 @@ class Upsert extends DocumentUpsert
|
||||
->label('scope', 'rows.write')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('audits.event', 'row.upsert')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}/row/{response.$id}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}/row/{response.$id}')
|
||||
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
|
||||
@@ -37,7 +37,7 @@ class Update extends CollectionUpdate
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('event', 'databases.[databaseId].tables.[tableId].update')
|
||||
->label('audits.event', 'table.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/table/{request.tableId}')
|
||||
->label('audits.resource', 'database/{request.databaseId}/grid/table/{request.tableId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: $this->getSdkNamespace(),
|
||||
group: null,
|
||||
|
||||
@@ -97,8 +97,8 @@ class Create extends Action
|
||||
$deviceForFunctions->transfer($path, $destination, $deviceForFunctions);
|
||||
|
||||
$deployment->removeAttribute('$sequence');
|
||||
|
||||
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
|
||||
'$sequence' => '',
|
||||
'$id' => $deploymentId,
|
||||
'sourcePath' => $destination,
|
||||
'totalSize' => $deployment->getAttribute('sourceSize', 0),
|
||||
|
||||
@@ -111,8 +111,8 @@ class Create extends Action
|
||||
}
|
||||
|
||||
$deployment->removeAttribute('$sequence');
|
||||
|
||||
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
|
||||
'$sequence' => '',
|
||||
'$id' => $deploymentId,
|
||||
'sourcePath' => $destination,
|
||||
'totalSize' => $deployment->getAttribute('sourceSize', 0),
|
||||
|
||||
@@ -122,6 +122,19 @@ class Maintenance extends Action
|
||||
Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
|
||||
|
||||
foreach ($certificates as $certificate) {
|
||||
$domain = $certificate->getAttribute('domain');
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = $dbForPlatform->getDocument('rules', md5($domain));
|
||||
} else {
|
||||
$rule = $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('region') !== System::getEnv('_APP_REGION', 'default')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$queueForCertificate
|
||||
->setDomain(new Document([
|
||||
'domain' => $certificate->getAttribute('domain')
|
||||
|
||||
@@ -119,7 +119,7 @@ class SDKs extends Action
|
||||
$gettingStarted = ($gettingStarted) ? \file_get_contents($gettingStarted) : '';
|
||||
$examples = \realpath(__DIR__ . '/../../../../docs/sdks/' . $language['key'] . '/EXAMPLES.md');
|
||||
$examples = ($examples) ? \file_get_contents($examples) : '';
|
||||
$changelog = \realpath(__DIR__ . '/../../../../docs/sdks/' . $language['key'] . '/CHANGELOG.md');
|
||||
$changelog = $language['changelog'] ?? '';
|
||||
$changelog = ($changelog) ? \file_get_contents($changelog) : '# Change Log';
|
||||
$warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check [previous releases](' . $language['url'] . '/releases).**';
|
||||
$license = 'BSD-3-Clause';
|
||||
|
||||
@@ -337,6 +337,19 @@ class Certificates extends Action
|
||||
|
||||
throw new Exception('Failed to verify domain DNS records.');
|
||||
}
|
||||
|
||||
// Ensure CAA won't block certificate issuance
|
||||
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
|
||||
$validationStart = \microtime(true);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
$error = $validator->getDescription();
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
throw new Exception('Failed to verify domain DNS records. CAA records do not allow Appwrite\'s certificate issuer.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Main domain validation
|
||||
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
|
||||
|
||||
@@ -632,6 +632,7 @@ class OpenAPI3 extends Format
|
||||
|
||||
$required = $model->getRequired();
|
||||
$rules = $model->getRules();
|
||||
$examples = [];
|
||||
|
||||
$output['components']['schemas'][$model->getType()] = [
|
||||
'description' => $model->getName(),
|
||||
@@ -655,6 +656,8 @@ class OpenAPI3 extends Format
|
||||
$format = null;
|
||||
$items = null;
|
||||
|
||||
$examples[$name] = $rule['example'] ?? null;
|
||||
|
||||
switch ($rule['type']) {
|
||||
case 'string':
|
||||
case 'datetime':
|
||||
@@ -712,6 +715,7 @@ class OpenAPI3 extends Format
|
||||
break;
|
||||
}
|
||||
|
||||
$readOnly = $rule['readOnly'] ?? false;
|
||||
if ($rule['array']) {
|
||||
$output['components']['schemas'][$model->getType()]['properties'][$name] = [
|
||||
'type' => 'array',
|
||||
@@ -725,6 +729,9 @@ class OpenAPI3 extends Format
|
||||
if ($format) {
|
||||
$output['components']['schemas'][$model->getType()]['properties'][$name]['items']['format'] = $format;
|
||||
}
|
||||
if ($readOnly) {
|
||||
$output['components']['schemas'][$model->getType()]['properties'][$name]['readOnly'] = true;
|
||||
}
|
||||
} else {
|
||||
$output['components']['schemas'][$model->getType()]['properties'][$name] = [
|
||||
'type' => $type,
|
||||
@@ -735,6 +742,9 @@ class OpenAPI3 extends Format
|
||||
if ($format) {
|
||||
$output['components']['schemas'][$model->getType()]['properties'][$name]['format'] = $format;
|
||||
}
|
||||
if ($readOnly) {
|
||||
$output['components']['schemas'][$model->getType()]['properties'][$name]['readOnly'] = true;
|
||||
}
|
||||
}
|
||||
if ($items) {
|
||||
$output['components']['schemas'][$model->getType()]['properties'][$name]['items'] = $items;
|
||||
@@ -743,6 +753,12 @@ class OpenAPI3 extends Format
|
||||
$output['components']['schemas'][$model->getType()]['properties'][$name]['nullable'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($model->isAny() && !empty($model->getSampleData())) {
|
||||
$examples = array_merge($examples, $model->getSampleData());
|
||||
}
|
||||
|
||||
$output['components']['schemas'][$model->getType()]['example'] = $examples;
|
||||
}
|
||||
|
||||
\ksort($output['paths']);
|
||||
|
||||
@@ -642,6 +642,7 @@ class Swagger2 extends Format
|
||||
|
||||
$required = $model->getRequired();
|
||||
$rules = $model->getRules();
|
||||
$examples = [];
|
||||
|
||||
$output['definitions'][$model->getType()] = [
|
||||
'description' => $model->getName(),
|
||||
@@ -665,6 +666,8 @@ class Swagger2 extends Format
|
||||
$format = null;
|
||||
$items = null;
|
||||
|
||||
$examples[$name] = $rule['example'] ?? null;
|
||||
|
||||
switch ($rule['type']) {
|
||||
case 'string':
|
||||
case 'datetime':
|
||||
@@ -721,6 +724,7 @@ class Swagger2 extends Format
|
||||
break;
|
||||
}
|
||||
|
||||
$readOnly = $rule['readOnly'] ?? false;
|
||||
if ($rule['type'] == 'json') {
|
||||
$output['definitions'][$model->getType()]['properties'][$name] = [
|
||||
'type' => $type,
|
||||
@@ -728,6 +732,10 @@ class Swagger2 extends Format
|
||||
'description' => $rule['description'] ?? '',
|
||||
'x-example' => $rule['example'] ?? null,
|
||||
];
|
||||
|
||||
if ($readOnly) {
|
||||
$output['definitions'][$model->getType()]['properties'][$name]['readOnly'] = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -744,6 +752,9 @@ class Swagger2 extends Format
|
||||
if ($format) {
|
||||
$output['definitions'][$model->getType()]['properties'][$name]['items']['format'] = $format;
|
||||
}
|
||||
if ($readOnly) {
|
||||
$output['definitions'][$model->getType()]['properties'][$name]['readOnly'] = true;
|
||||
}
|
||||
} else {
|
||||
$output['definitions'][$model->getType()]['properties'][$name] = [
|
||||
'type' => $type,
|
||||
@@ -754,6 +765,9 @@ class Swagger2 extends Format
|
||||
if ($format) {
|
||||
$output['definitions'][$model->getType()]['properties'][$name]['format'] = $format;
|
||||
}
|
||||
if ($readOnly) {
|
||||
$output['definitions'][$model->getType()]['properties'][$name]['readOnly'] = true;
|
||||
}
|
||||
}
|
||||
if ($items) {
|
||||
$output['definitions'][$model->getType()]['properties'][$name]['items'] = $items;
|
||||
@@ -762,6 +776,12 @@ class Swagger2 extends Format
|
||||
$output['definitions'][$model->getType()]['properties'][$name]['x-nullable'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($model->isAny() && !empty($model->getSampleData())) {
|
||||
$examples = array_merge($examples, $model->getSampleData());
|
||||
}
|
||||
|
||||
$output['definitions'][$model->getType()]['example'] = $examples;
|
||||
}
|
||||
|
||||
\ksort($output['paths']);
|
||||
|
||||
@@ -91,7 +91,8 @@ abstract class Model
|
||||
'array' => false,
|
||||
'description' => '',
|
||||
'example' => '',
|
||||
'sensitive' => false
|
||||
'sensitive' => false,
|
||||
'readOnly' => false
|
||||
], $options);
|
||||
|
||||
return $this;
|
||||
@@ -129,6 +130,27 @@ abstract class Model
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Readonly Fields
|
||||
*
|
||||
* Returns list of field names that are marked as readOnly
|
||||
* and should not be allowed in create/update payloads
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getReadonlyFields(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->rules as $key => $rule) {
|
||||
if ($rule['readOnly'] ?? false) {
|
||||
$list[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is None
|
||||
*
|
||||
|
||||
@@ -31,4 +31,14 @@ class Any extends Model
|
||||
{
|
||||
return Response::MODEL_ANY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sample data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSampleData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,24 +10,30 @@ class ConsoleVariables extends Model
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('_APP_DOMAIN_TARGET_CNAME', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'CNAME target for your Appwrite custom domains.',
|
||||
'default' => '',
|
||||
'example' => 'appwrite.io',
|
||||
])
|
||||
->addRule('_APP_DOMAIN_TARGET_A', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'A target for your Appwrite custom domains.',
|
||||
'default' => '',
|
||||
'example' => '127.0.0.1',
|
||||
])
|
||||
->addRule('_APP_DOMAIN_TARGET_AAAA', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'AAAA target for your Appwrite custom domains.',
|
||||
'default' => '',
|
||||
'example' => '::1',
|
||||
])
|
||||
->addRule('_APP_DOMAIN_TARGET_CNAME', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'CNAME target for your Appwrite custom domains.',
|
||||
'default' => '',
|
||||
'example' => 'appwrite.io',
|
||||
])
|
||||
->addRule('_APP_DOMAIN_TARGET_A', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'A target for your Appwrite custom domains.',
|
||||
'default' => '',
|
||||
'example' => '127.0.0.1',
|
||||
])
|
||||
->addRule('_APP_DOMAIN_TARGET_AAAA', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'AAAA target for your Appwrite custom domains.',
|
||||
'default' => '',
|
||||
'example' => '::1',
|
||||
])
|
||||
->addRule('_APP_DOMAIN_TARGET_CAA', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'CAA target for your Appwrite custom domains.',
|
||||
'default' => '',
|
||||
'example' => 'digicert.com',
|
||||
])
|
||||
->addRule('_APP_STORAGE_LIMIT', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Maximum file size allowed for file upload in bytes.',
|
||||
|
||||
@@ -40,6 +40,12 @@ class Database extends Model
|
||||
'default' => true,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Database type.',
|
||||
'default' => 'grids',
|
||||
'example' => 'grids',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,18 +41,21 @@ class Document extends Any
|
||||
'description' => 'Document automatically incrementing ID.',
|
||||
'default' => 0,
|
||||
'example' => 1,
|
||||
'readOnly' => true,
|
||||
])
|
||||
->addRule('$collectionId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Collection ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c15117e',
|
||||
'readOnly' => true,
|
||||
])
|
||||
->addRule('$databaseId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Database ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c15117e',
|
||||
'readOnly' => true,
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
@@ -79,6 +82,7 @@ class Document extends Any
|
||||
{
|
||||
$document->removeAttribute('$collection');
|
||||
$document->removeAttribute('$tenant');
|
||||
$document->setAttribute('$sequence', (int)$document->getAttribute('$sequence', 0));
|
||||
|
||||
foreach ($document->getAttributes() as $attribute) {
|
||||
if (\is_array($attribute)) {
|
||||
@@ -94,4 +98,15 @@ class Document extends Any
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
public function getSampleData(): array
|
||||
{
|
||||
return [
|
||||
'username' => 'john.doe',
|
||||
'email' => 'john.doe@example.com',
|
||||
'fullName' => 'John Doe',
|
||||
'age' => 30,
|
||||
'isAdmin' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,13 @@ class Preferences extends Any
|
||||
{
|
||||
return Response::MODEL_PREFERENCES;
|
||||
}
|
||||
|
||||
public function getSampleData(): array
|
||||
{
|
||||
return [
|
||||
'language' => 'en',
|
||||
'timezone' => 'UTC',
|
||||
'darkTheme' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user