Merge branch '1.6.x' into feat-renaming-attributes

# Conflicts:
#	app/config/specs/open-api3-latest-console.json
#	app/config/specs/open-api3-latest-server.json
#	app/config/specs/swagger2-1.6.X-client.json
#	app/config/specs/swagger2-1.6.X-console.json
#	app/config/specs/swagger2-latest-console.json
#	app/config/specs/swagger2-latest-server.json
#	app/controllers/shared/api.php
#	app/realtime.php
#	composer.lock
#	src/Appwrite/Platform/Workers/Deletes.php
This commit is contained in:
Bradley Schofield
2024-08-20 13:07:55 +09:00
28 changed files with 159 additions and 63 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.9.1 as final
FROM appwrite/base:0.9.2 as final
LABEL maintainer="team@appwrite.io"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -3
View File
@@ -1635,15 +1635,16 @@ App::post('/v1/functions/:functionId/executions')
->label('sdk.method', 'createExecution')
->label('sdk.description', '/docs/references/functions/create-execution.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.type', Response::CONTENT_TYPE_MULTIPART)
->label('sdk.response.model', Response::MODEL_EXECUTION)
->label('sdk.request.type', Response::CONTENT_TYPE_MULTIPART)
->param('functionId', '', new UID(), 'Function ID.')
->param('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true)
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
->param('headers', [], new AnyOf([new Text(65535), new Assoc()], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
->param('scheduledAt', null, new DatetimeValidator(true, DateTimeValidator::PRECISION_MINUTES, 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
->inject('response')
->inject('request')
->inject('project')
+10 -10
View File
@@ -373,15 +373,15 @@ App::init()
* Abuse Check
*/
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
$abuseAdapterArray = [];
$timeLimitArray = [];
$abuseKeyLabel = (!is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
foreach ($abuseKeyLabel as $abuseKey) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
$abuseAdapter = new AbuseDatabase($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$abuseAdapter
$timeLimit = new AbuseDatabase($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
$timeLimit
->setParam('{projectId}', $project->getId())
->setParam('{userId}', $user->getId())
->setParam('{userAgent}', $request->getUserAgent(''))
@@ -389,7 +389,7 @@ App::init()
->setParam('{url}', $request->getHostname() . $route->getPath())
->setParam('{method}', $request->getMethod())
->setParam('{chunkId}', (int) ($start / ($end + 1 - $start)));
$abuseAdapterArray[] = $abuseAdapter;
$timeLimitArray[] = $timeLimit;
}
$closestLimit = null;
@@ -398,17 +398,17 @@ App::init()
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
foreach ($abuseAdapterArray as $abuseAdapter) {
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
if (!empty($value)) {
$abuseAdapter->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
$timeLimit->setParam('{param-' . $key . '}', (\is_array($value)) ? \json_encode($value) : $value);
}
}
$abuse = new Abuse($abuseAdapter);
$remaining = $abuseAdapter->remaining();
$limit = $abuseAdapter->limit();
$time = (new \DateTime($abuseAdapter->time()))->getTimestamp() + $route->getLabel('abuse-time', 3600);
$abuse = new Abuse($timeLimit);
$remaining = $timeLimit->remaining();
$limit = $timeLimit->limit();
$time = (new \DateTime($timeLimit->time()))->getTimestamp() + $route->getLabel('abuse-time', 3600);
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
$closestLimit = $remaining;
+1 -1
View File
@@ -117,7 +117,7 @@ const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4314;
const APP_CACHE_BUSTER = 4315;
const APP_VERSION_STABLE = '1.6.0';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
+6 -6
View File
@@ -463,12 +463,12 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
*
* Abuse limits are connecting 128 times per minute and ip address.
*/
$abuseAdapter = new AbuseDatabase('url:{url},ip:{ip}', 128, 60, $dbForProject);
$abuseAdapter
$timeLimit = new AbuseDatabase('url:{url},ip:{ip}', 128, 60, $dbForProject);
$timeLimit
->setParam('{ip}', $request->getIP())
->setParam('{url}', $request->getURI());
$abuse = new Abuse($abuseAdapter);
$abuse = new Abuse($timeLimit);
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many requests');
@@ -563,13 +563,13 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
*
* Abuse limits are sending 32 times per minute and connection.
*/
$abuseDatabase = new AbuseDatabase('url:{url},connection:{connection}', 32, 60, $database);
$timeLimit = new AbuseDatabase('url:{url},connection:{connection}', 32, 60, $database);
$abuseDatabase
$timeLimit
->setParam('{connection}', $connection)
->setParam('{container}', $containerId);
$abuse = new Abuse($abuseDatabase);
$abuse = new Abuse($timeLimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many messages.');
+3 -1
View File
@@ -166,7 +166,7 @@ $image = $this->getParam('image', '');
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:appwrite/console:5.0.0-rc14
image: <?php echo $organization; ?>/console:appwrite/console:5.0.0-rc16
restart: unless-stopped
networks:
- appwrite
@@ -475,6 +475,8 @@ $image = $this->getParam('image', '');
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
Generated
+12 -12
View File
@@ -1723,16 +1723,16 @@
},
{
"name": "utopia-php/database",
"version": "0.51.0",
"version": "0.51.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "cf723c720e5aa938709e74f1ccfc3b46ecdc6de3"
"reference": "845783a54cced784e00db084a29486fdb96d3d58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/cf723c720e5aa938709e74f1ccfc3b46ecdc6de3",
"reference": "cf723c720e5aa938709e74f1ccfc3b46ecdc6de3",
"url": "https://api.github.com/repos/utopia-php/database/zipball/845783a54cced784e00db084a29486fdb96d3d58",
"reference": "845783a54cced784e00db084a29486fdb96d3d58",
"shasum": ""
},
"require": {
@@ -1773,9 +1773,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.51.0"
"source": "https://github.com/utopia-php/database/tree/0.51.1"
},
"time": "2024-08-16T03:28:04+00:00"
"time": "2024-08-16T10:54:25+00:00"
},
{
"name": "utopia-php/domains",
@@ -2993,16 +2993,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.39.6",
"version": "0.39.7",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "00e6f9e77ea380d8ab3138a36548b353077e4061"
"reference": "a3998d8971c43ff2247542c128f98a94fa4833e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/00e6f9e77ea380d8ab3138a36548b353077e4061",
"reference": "00e6f9e77ea380d8ab3138a36548b353077e4061",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/a3998d8971c43ff2247542c128f98a94fa4833e7",
"reference": "a3998d8971c43ff2247542c128f98a94fa4833e7",
"shasum": ""
},
"require": {
@@ -3038,9 +3038,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.39.6"
"source": "https://github.com/appwrite/sdk-generator/tree/0.39.7"
},
"time": "2024-08-08T12:44:28+00:00"
"time": "2024-08-19T09:33:17+00:00"
},
{
"name": "doctrine/deprecations",
+1 -1
View File
@@ -195,7 +195,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:5.0.0-rc15
image: appwrite/console:5.0.0-rc16
restart: unless-stopped
networks:
- appwrite
+2 -2
View File
@@ -686,8 +686,8 @@ class Deletes extends Action
{
$projectId = $project->getId();
$dbForProject = $getProjectDB($project);
$abuseAdapter = new AbuseDatabase("", 0, 1, $dbForProject);
$abuse = new Abuse($abuseAdapter);
$timeLimit = new AbuseDatabase("", 0, 1, $dbForProject);
$abuse = new Abuse($timeLimit);
try {
$abuse->cleanup($abuseRetention);
@@ -286,7 +286,13 @@ class Swagger2 extends Format
$validator = $validator->getValidator();
}
switch ((!empty($validator)) ? \get_class($validator) : '') {
$validatorClass = (!empty($validator)) ? \get_class($validator) : '';
if($validatorClass === 'Utopia\Validator\AnyOf') {
$validator = $param['validator']->getValidators()[0];
$validatorClass = \get_class($validator);
}
switch ($validatorClass) {
case 'Utopia\Validator\Text':
case 'Utopia\Database\Validator\UID':
$node['type'] = $validator->getType();
+1 -1
View File
@@ -14,7 +14,7 @@ class V18 extends Filter
unset($content['otp']);
break;
case 'functions.create':
$content['templateVersion'] = $content['templateBranch'];
$content['templateVersion'] = $content['templateBranch'] ?? "";
unset($content['templateBranch']);
break;
}
@@ -219,7 +219,8 @@ class FunctionsCustomClientTest extends Scope
// Schedule execution for the future
\date_default_timezone_set('UTC');
$futureTime = (new \DateTime())->add(new \DateInterval('PT10S'));
$futureTime = (new \DateTime())->add(new \DateInterval('PT2M'));
$futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
'content-type' => 'application/json',
@@ -236,7 +237,7 @@ class FunctionsCustomClientTest extends Scope
$executionId = $execution['body']['$id'];
sleep(10);
sleep(60 + 60 + 15); // up to 1 minute round up, 1 minute schedule postpone, 15s cold start safety
$start = \microtime(true);
while (true) {
@@ -251,7 +252,7 @@ class FunctionsCustomClientTest extends Scope
}
if (\microtime(true) - $start > 10) {
$this->fail('Execution did not complete within 10 seconds of schedule in status ' . $execution['body']['status'] . ': ' . \json_encode($execution));
$this->fail('Scheduled execution did not complete with status ' . $execution['body']['status'] . ': ' . \json_encode($execution));
}
usleep(500000); // 0.5 seconds
@@ -267,7 +268,6 @@ class FunctionsCustomClientTest extends Scope
/* Test for FAILURE */
// Schedule synchronous execution
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -278,6 +278,41 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals(400, $execution['headers']['status-code']);
// Execution with seconds precision
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'async' => true,
'scheduledAt' => (new \DateTime("2100-12-08 16:12:02"))->format(\DateTime::ATOM)
]);
$this->assertEquals(400, $execution['headers']['status-code']);
// Execution with milliseconds precision
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'async' => true,
'scheduledAt' => (new \DateTime("2100-12-08 16:12:02.255"))->format(\DateTime::ATOM)
]);
$this->assertEquals(400, $execution['headers']['status-code']);
// Execution too soon
$futureTime = (new \DateTime())->add(new \DateInterval('PT1M'));
$futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'async' => true,
'scheduledAt' => $futureTime->format(\DateTime::ATOM),
]);
$this->assertEquals(400, $execution['headers']['status-code']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [
'content-type' => 'application/json',
@@ -454,8 +454,8 @@ class FunctionsCustomServerTest extends Scope
$entrypoint = null;
$rootDirectory = null;
$commands = null;
foreach($template['body']['runtimes'] as $runtime) {
if($runtime["name"] !== $runtimeName) {
foreach ($template['body']['runtimes'] as $runtime) {
if ($runtime["name"] !== $runtimeName) {
continue;
}
@@ -1286,14 +1286,15 @@ class FunctionsCustomServerTest extends Scope
*/
public function testDeleteScheduledExecution($data): array
{
$futureTime = (new \DateTime())->add(new \DateInterval('PT10H'))->format('Y-m-d H:i:s');
$futureTime = (new \DateTime())->add(new \DateInterval('PT10H'));
$futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'async' => true,
'scheduledAt' => $futureTime,
'scheduledAt' => $futureTime->format('Y-m-d H:i:s'),
]);
$executionId = $execution['body']['$id'] ?? '';
@@ -2063,8 +2064,33 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertNotEmpty($execution['body']['responseBody']);
$this->assertGreaterThan(0, $execution['body']['duration']);
$this->assertNotEmpty($execution['body']['responseBody']);
$this->assertStringContainsString("total", $execution['body']['responseBody']);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'async' => true
]);
$this->assertEquals(202, $execution['headers']['status-code']);
$this->assertNotEmpty($execution['body']['$id']);
\sleep(10);
$execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $execution['body']['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
$this->assertEquals(200, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertGreaterThan(0, $execution['body']['duration']);
$this->assertNotEmpty($execution['body']['logs']);
$this->assertStringContainsString("total", $execution['body']['logs']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
@@ -2418,4 +2444,30 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(204, $response['headers']['status-code']);
}
public function testCreateFunctionWithResponseFormatHeader()
{
$response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-response-format' => '1.5.0', // add response format header
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test',
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'timeout' => 15,
]);
$this->assertEquals(201, $response['headers']['status-code']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $response['body']['$id'], [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
$this->assertEquals(204, $response['headers']['status-code']);
}
}
@@ -12,5 +12,7 @@ return function ($context) {
->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID'))
->setKey($context->req->headers['x-appwrite-key']);
$users = new Users($client);
return $context->res->json($users->list());
$response = $users->list();
$context->log($response);
return $context->res->json($response);
};