Merge branch '1.5.x' into fix-templates-copy-hidden-files

This commit is contained in:
Luke B. Silver 2024-02-22 13:41:16 +00:00 committed by GitHub
commit 0f09fd5ade
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
295 changed files with 1869 additions and 1160 deletions

5
.env
View file

@ -79,7 +79,7 @@ _APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
_APP_MAINTENANCE_RETENTION_ABUSE=86400
_APP_MAINTENANCE_RETENTION_AUDIT=1209600
_APP_USAGE_AGGREGATION_INTERVAL=60000
_APP_USAGE_AGGREGATION_INTERVAL=30
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_USAGE_STATS=enabled
@ -102,4 +102,5 @@ _APP_ASSISTANT_OPENAI_API_KEY=
_APP_MESSAGE_SMS_TEST_DSN=
_APP_MESSAGE_EMAIL_TEST_DSN=
_APP_MESSAGE_PUSH_TEST_DSN=
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
_APP_PROJECT_REGIONS=default

View file

@ -105,7 +105,8 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-migrations && \
chmod +x /usr/local/bin/worker-webhooks && \
chmod +x /usr/local/bin/worker-hamster && \
chmod +x /usr/local/bin/worker-usage
chmod +x /usr/local/bin/worker-usage && \
chmod +x /usr/local/bin/worker-usage-dump
# Cloud Executabless

View file

@ -34,6 +34,28 @@ $commonCollections = [
'array' => false,
'filters' => [],
],
[
'$id' => 'resourceType',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('mimeType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 255, // https://tools.ietf.org/html/rfc4288#section-4.2
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'accessedAt',
'type' => Database::VAR_DATETIME,
@ -2113,6 +2135,13 @@ $commonCollections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_unique_target_topic'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['targetInternalId', 'topicInternalId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_fulltext_search'),
'type' => Database::INDEX_FULLTEXT,
@ -4362,17 +4391,6 @@ $consoleCollections = array_merge([
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceCollection'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceInternalId'),
'type' => Database::VAR_STRING,

View file

@ -272,6 +272,11 @@ return [
'description' => 'User phone is already verified',
'code' => 409
],
Exception::USER_DELETION_PROHIBITED => [
'name' => Exception::USER_DELETION_PROHIBITED,
'description' => 'User deletion is not allowed for users with active memberships. Please delete all confirmed memberships before deleting the account.',
'code' => 400
],
Exception::USER_TARGET_NOT_FOUND => [
'name' => Exception::USER_TARGET_NOT_FOUND,
'description' => 'The target could not be found.',
@ -420,6 +425,11 @@ return [
'description' => 'The value for x-appwrite-id header is invalid. Please check the value of the x-appwrite-id header is a valid id and not unique().',
'code' => 400,
],
Exception::STORAGE_FILE_NOT_PUBLIC => [
'name' => Exception::STORAGE_FILE_NOT_PUBLIC,
'description' => 'The requested file is not publicly readable.',
'code' => 403,
],
/** VCS */
Exception::INSTALLATION_NOT_FOUND => [
@ -718,6 +728,11 @@ return [
'description' => 'You can\'t delete default template. If you are trying to reset your template changes, you can ignore this error as it\'s already been reset.',
'code' => 401,
],
Exception::PROJECT_REGION_UNSUPPORTED => [
'name' => Exception::PROJECT_REGION_UNSUPPORTED,
'description' => 'The requested region is either inactive or unsupported. Please check the value of the _APP_REGIONS environment variable.',
'code' => 400,
],
Exception::WEBHOOK_NOT_FOUND => [
'name' => Exception::WEBHOOK_NOT_FOUND,
'description' => 'Webhook with the requested ID could not be found.',

View file

@ -303,7 +303,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '10.1.0',
'version' => '10.1.1',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,

View file

@ -2,8 +2,73 @@
return [
'default' => [
'name' => 'Default',
'default' => true,
'$id' => 'default',
'name' => 'Frankfurt',
'disabled' => false,
]
'flag' => 'de',
'default' => true,
],
'fra' => [
'$id' => 'fra',
'name' => 'Frankfurt',
'disabled' => false,
'flag' => 'de',
'default' => true,
],
'nyc' => [
'$id' => 'nyc',
'name' => 'New York',
'disabled' => true,
'flag' => 'us',
'default' => true,
],
'sfo' => [
'$id' => 'sfo',
'name' => 'San Francisco',
'disabled' => true,
'flag' => 'us',
'default' => true,
],
'blr' => [
'$id' => 'blr',
'name' => 'Banglore',
'disabled' => true,
'flag' => 'in',
'default' => true,
],
'lon' => [
'$id' => 'lon',
'name' => 'London',
'disabled' => true,
'flag' => 'gb',
'default' => true,
],
'ams' => [
'$id' => 'ams',
'name' => 'Amsterdam',
'disabled' => true,
'flag' => 'nl',
'default' => true,
],
'sgp' => [
'$id' => 'sgp',
'name' => 'Singapore',
'disabled' => true,
'flag' => 'sg',
'default' => true,
],
'tor' => [
'$id' => 'tor',
'name' => 'Toronto',
'disabled' => true,
'flag' => 'ca',
'default' => true,
],
'syd' => [
'$id' => 'syd',
'name' => 'Sydney',
'disabled' => true,
'flag' => 'au',
'default' => true,
],
];

View file

@ -1,16 +1,9 @@
<?php
/**
* List of Appwrite Cloud Functions supported runtimes
* List of Appwrite Functions supported runtimes
*/
use Utopia\App;
use Appwrite\Runtimes\Runtimes;
$runtimes = new Runtimes('v3');
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
$runtimes = $runtimes->getAll(true, $allowList);
return $runtimes;
return (new Runtimes('v3'))->getAll();

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

View file

@ -266,7 +266,7 @@ App::post('/v1/account/sessions/email')
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'factors' => ['email'],
'factors' => ['password'],
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
'expire' => DateTime::addSeconds(new \DateTime(), $duration)
],
@ -1773,10 +1773,10 @@ App::post('/v1/account/tokens/phone')
]);
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_INTERNAL)
->setMessage($messageDoc)
->setRecipients([$phone])
->setProviderType(MESSAGE_TYPE_SMS)
->trigger();
->setProviderType(MESSAGE_TYPE_SMS);
$queueForEvents->setPayload(
$response->output(
@ -2124,7 +2124,7 @@ App::get('/v1/account/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByUser($user->getId()),
'total' => $audit->countLogsByUser($user->getInternalId()),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@ -3314,10 +3314,10 @@ App::post('/v1/account/verification/phone')
]);
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_INTERNAL)
->setMessage($messageDoc)
->setRecipients([$user->getAttribute('phone')])
->setProviderType(MESSAGE_TYPE_SMS)
->trigger();
->setProviderType(MESSAGE_TYPE_SMS);
$queueForEvents
->setParam('userId', $user->getId())
@ -3677,14 +3677,14 @@ App::post('/v1/account/mfa/challenge')
}
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_INTERNAL)
->setMessage(new Document([
'$id' => $challenge->getId(),
'data' => [
'content' => $code,
],
]))
->setRecipients([$user->getAttribute('phone')])
->trigger();
->setRecipients([$user->getAttribute('phone')]);
break;
case 'email':
if (empty(App::getEnv('_APP_SMTP_HOST'))) {
@ -3791,15 +3791,27 @@ App::delete('/v1/account')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->inject('user')
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
->inject('queueForDeletes')
->action(function (Document $user, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
if ($project->getId() === 'console') {
// get all memberships
$memberships = $user->getAttribute('memberships', []);
foreach ($memberships as $membership) {
// prevent deletion if at least one active membership
if ($membership->getAttribute('confirm', false)) {
throw new Exception(Exception::USER_DELETION_PROHIBITED);
}
}
}
$dbForProject->deleteDocument('users', $user->getId());
$queueForDeletes

View file

@ -76,7 +76,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
}
if (empty($gitHubSession)) {
throw new Exception(Exception::GENERAL_UNKNOWN, 'GitHub session not found.');
throw new Exception(Exception::USER_SESSION_NOT_FOUND, 'GitHub session not found.');
}
$provider = $gitHubSession->getAttribute('provider', '');

View file

@ -121,9 +121,7 @@ $redeployVcs = function (Request $request, Document $function, Document $project
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template)
->setProject($project)
->trigger();
->setTemplate($template);
};
App::post('/v1/functions')
@ -172,6 +170,12 @@ App::post('/v1/functions')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$allowList = \array_filter(\explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
if (!empty($allowList) && !\in_array($runtime, $allowList)) {
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $runtime . '" is not supported');
}
// build from template
$template = new Document([]);
if (
@ -229,7 +233,6 @@ App::post('/v1/functions')
fn () => $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
'resourceType' => 'function',
'resourceCollection' => 'functions',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
@ -841,8 +844,8 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->inject('response')
->inject('request')
->inject('dbForProject')
->inject('deviceFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceFunctions) {
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForFunctions) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@ -859,7 +862,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
}
$path = $deployment->getAttribute('path', '');
if (!$deviceFunctions->exists($path)) {
if (!$deviceForFunctions->exists($path)) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
@ -869,7 +872,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
$size = $deviceFunctions->getFileSize($path);
$size = $deviceForFunctions->getFileSize($path);
$rangeHeader = $request->getHeader('range');
if (!empty($rangeHeader)) {
@ -891,13 +894,13 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->addHeader('Content-Length', $end - $start + 1)
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
$response->send($deviceFunctions->read($path, $start, ($end - $start + 1)));
$response->send($deviceForFunctions->read($path, $start, ($end - $start + 1)));
}
if ($size > APP_STORAGE_READ_BUFFER) {
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFunctions->read(
$deviceForFunctions->read(
$path,
($i * MAX_OUTPUT_CHUNK_SIZE),
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
@ -906,7 +909,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
);
}
} else {
$response->send($deviceFunctions->read($path));
$response->send($deviceForFunctions->read($path));
}
});
@ -1045,10 +1048,10 @@ App::post('/v1/functions/:functionId/deployments')
->inject('dbForProject')
->inject('queueForEvents')
->inject('project')
->inject('deviceFunctions')
->inject('deviceLocal')
->inject('deviceForFunctions')
->inject('deviceForLocal')
->inject('queueForBuilds')
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, bool $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceFunctions, Device $deviceLocal, Build $queueForBuilds) {
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, bool $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds) {
$activate = filter_var($activate, FILTER_VALIDATE_BOOLEAN);
@ -1129,11 +1132,11 @@ App::post('/v1/functions/:functionId/deployments')
}
// Save to storage
$fileSize ??= $deviceLocal->getFileSize($fileTmpName);
$path = $deviceFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$fileSize ??= $deviceForLocal->getFileSize($fileTmpName);
$path = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)];
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
if (!$deployment->isEmpty()) {
$chunks = $deployment->getAttribute('chunksTotal', 1);
$metadata = $deployment->getAttribute('metadata', []);
@ -1142,7 +1145,7 @@ App::post('/v1/functions/:functionId/deployments')
}
}
$chunksUploaded = $deviceFunctions->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
$chunksUploaded = $deviceForFunctions->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
if (empty($chunksUploaded)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
@ -1165,7 +1168,7 @@ App::post('/v1/functions/:functionId/deployments')
}
}
$fileSize = $deviceFunctions->getFileSize($path);
$fileSize = $deviceForFunctions->getFileSize($path);
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
@ -1196,9 +1199,7 @@ App::post('/v1/functions/:functionId/deployments')
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->trigger();
->setDeployment($deployment);
} else {
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
@ -1376,8 +1377,8 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
->inject('dbForProject')
->inject('queueForDeletes')
->inject('queueForEvents')
->inject('deviceFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Device $deviceFunctions) {
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Device $deviceForFunctions) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
@ -1398,7 +1399,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
}
if (!empty($deployment->getAttribute('path', ''))) {
if (!($deviceFunctions->delete($deployment->getAttribute('path', '')))) {
if (!($deviceForFunctions->delete($deployment->getAttribute('path', '')))) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
}
}
@ -1478,9 +1479,7 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->trigger();
->setDeployment($deployment);
$queueForEvents
->setParam('functionId', $function->getId())

View file

@ -792,8 +792,8 @@ App::get('/v1/health/stats') // Currently only used internally
->label('docs', false)
->inject('response')
->inject('register')
->inject('deviceFiles')
->action(function (Response $response, Registry $register, Device $deviceFiles) {
->inject('deviceForFiles')
->action(function (Response $response, Registry $register, Device $deviceForFiles) {
$cache = $register->get('cache');
@ -802,9 +802,9 @@ App::get('/v1/health/stats') // Currently only used internally
$response
->json([
'storage' => [
'used' => Storage::human($deviceFiles->getDirectorySize($deviceFiles->getRoot() . '/')),
'partitionTotal' => Storage::human($deviceFiles->getPartitionTotalSpace()),
'partitionFree' => Storage::human($deviceFiles->getPartitionFreeSpace()),
'used' => Storage::human($deviceForFiles->getDirectorySize($deviceForFiles->getRoot() . '/')),
'partitionTotal' => Storage::human($deviceForFiles->getPartitionTotalSpace()),
'partitionFree' => Storage::human($deviceForFiles->getPartitionFreeSpace()),
],
'cache' => [
'uptime' => $cacheStats['uptime_in_seconds'] ?? 0,

View file

@ -10,6 +10,7 @@ use Appwrite\Messaging\Status as MessageStatus;
use Appwrite\Network\Validator\Email;
use Appwrite\Permission;
use Appwrite\Role;
use Appwrite\Utopia\Database\Validator\CompoundUID;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Messages;
use Appwrite\Utopia\Database\Validator\Queries\Providers;
@ -27,11 +28,13 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Locale\Locale;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
@ -216,14 +219,14 @@ App::post('/v1/messaging/providers/smtp')
->label('scope', 'providers.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'createSMTPProvider')
->label('sdk.method', 'createSmtpProvider')
->label('sdk.description', '/docs/references/messaging/create-smtp-provider.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROVIDER)
->param('providerId', '', new CustomId(), 'Provider 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), 'Provider name.')
->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host by using this format: [hostname:port] (e.g. "smtp1.example.com:25;smtp2.example.com"). You can also specify encryption type, for example: (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). Hosts will be tried in order.')
->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host such as `smtp1.example.com:25;smtp2.example.com`. You can also specify encryption type, for example: `tls://smtp1.example.com:587;ssl://smtp2.example.com:465"`. Hosts will be tried in order.')
->param('port', 587, new Range(1, 65535), 'The default SMTP server port.', true)
->param('username', '', new Text(0), 'Authentication username.', true)
->param('password', '', new Text(0), 'Authentication password.', true)
@ -689,7 +692,7 @@ App::post('/v1/messaging/providers/fcm')
->label('scope', 'providers.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'createFCMProvider')
->label('sdk.method', 'createFcmProvider')
->label('sdk.description', '/docs/references/messaging/create-fcm-provider.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
@ -752,7 +755,7 @@ App::post('/v1/messaging/providers/apns')
->label('scope', 'providers.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'createAPNSProvider')
->label('sdk.method', 'createApnsProvider')
->label('sdk.description', '/docs/references/messaging/create-apns-provider.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
@ -881,7 +884,7 @@ App::get('/v1/messaging/providers/:providerId/logs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'listProviderLogs')
->label('sdk.description', '/docs/references/messaging/providers/get-logs.md')
->label('sdk.description', '/docs/references/messaging/list-provider-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
@ -1192,14 +1195,14 @@ App::patch('/v1/messaging/providers/smtp/:providerId')
->label('scope', 'providers.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'updateSMTPProvider')
->label('sdk.method', 'updateSmtpProvider')
->label('sdk.description', '/docs/references/messaging/update-smtp-provider.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROVIDER)
->param('providerId', '', new UID(), 'Provider ID.')
->param('name', '', new Text(128), 'Provider name.', true)
->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host by using this format: [hostname:port] (e.g. "smtp1.example.com:25;smtp2.example.com"). You can also specify encryption type, for example: (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). Hosts will be tried in order.', true)
->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host such as `smtp1.example.com:25;smtp2.example.com`. You can also specify encryption type, for example: `tls://smtp1.example.com:587;ssl://smtp2.example.com:465"`. Hosts will be tried in order.', true)
->param('port', null, new Range(1, 65535), 'SMTP port.', true)
->param('username', '', new Text(0), 'Authentication username.', true)
->param('password', '', new Text(0), 'Authentication password.', true)
@ -1724,7 +1727,7 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
->label('scope', 'providers.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'updateFCMProvider')
->label('sdk.method', 'updateFcmProvider')
->label('sdk.description', '/docs/references/messaging/update-fcm-provider.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
@ -1793,7 +1796,7 @@ App::patch('/v1/messaging/providers/apns/:providerId')
->label('scope', 'providers.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'updateAPNSProvider')
->label('sdk.method', 'updateApnsProvider')
->label('sdk.description', '/docs/references/messaging/update-apns-provider.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
@ -1906,7 +1909,7 @@ App::delete('/v1/messaging/providers/:providerId')
});
App::post('/v1/messaging/topics')
->desc('Create a topic.')
->desc('Create a topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.create')
->label('audits.resource', 'topic/{response.$id}')
@ -1949,7 +1952,7 @@ App::post('/v1/messaging/topics')
});
App::get('/v1/messaging/topics')
->desc('List topics.')
->desc('List topics')
->groups(['api', 'messaging'])
->label('scope', 'topics.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
@ -2006,7 +2009,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'listTopicLogs')
->label('sdk.description', '/docs/references/messaging/topics/get-logs.md')
->label('sdk.description', '/docs/references/messaging/list-topic-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
@ -2089,7 +2092,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
});
App::get('/v1/messaging/topics/:topicId')
->desc('Get a topic.')
->desc('Get a topic')
->groups(['api', 'messaging'])
->label('scope', 'topics.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
@ -2116,7 +2119,7 @@ App::get('/v1/messaging/topics/:topicId')
});
App::patch('/v1/messaging/topics/:topicId')
->desc('Update a topic.')
->desc('Update a topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.update')
->label('audits.resource', 'topic/{response.$id}')
@ -2155,7 +2158,7 @@ App::patch('/v1/messaging/topics/:topicId')
});
App::delete('/v1/messaging/topics/:topicId')
->desc('Delete a topic.')
->desc('Delete a topic')
->groups(['api', 'messaging'])
->label('audits.event', 'topic.delete')
->label('audits.resource', 'topic/{request.$topicId}')
@ -2195,7 +2198,7 @@ App::delete('/v1/messaging/topics/:topicId')
});
App::post('/v1/messaging/topics/:topicId/subscribers')
->desc('Create a subscriber.')
->desc('Create a subscriber')
->groups(['api', 'messaging'])
->label('audits.event', 'subscriber.create')
->label('audits.resource', 'subscriber/{response.$id}')
@ -2291,7 +2294,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
});
App::get('/v1/messaging/topics/:topicId/subscribers')
->desc('List subscribers.')
->desc('List subscribers')
->groups(['api', 'messaging'])
->label('scope', 'subscribers.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
@ -2371,7 +2374,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'listSubscriberLogs')
->label('sdk.description', '/docs/references/messaging/subscribers/get-logs.md')
->label('sdk.description', '/docs/references/messaging/list-subscriber-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
@ -2454,7 +2457,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
});
App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->desc('Get a subscriber.')
->desc('Get a subscriber')
->groups(['api', 'messaging'])
->label('scope', 'subscribers.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
@ -2493,7 +2496,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
});
App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->desc('Delete a subscriber.')
->desc('Delete a subscriber')
->groups(['api', 'messaging'])
->label('audits.event', 'subscriber.delete')
->label('audits.resource', 'subscriber/{request.$subscriberId}')
@ -2552,7 +2555,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
});
App::post('/v1/messaging/messages/email')
->desc('Create an email.')
->desc('Create an email')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
->label('audits.resource', 'message/{response.$id}')
@ -2573,6 +2576,7 @@ App::post('/v1/messaging/messages/email')
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('cc', [], new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true)
->param('bcc', [], new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true)
->param('attachments', [], new ArrayList(new CompoundUID()), 'Array of compound bucket IDs to file IDs to be attached to the email.', true)
->param('status', MessageStatus::DRAFT, new WhiteList([MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]), 'Message Status. Value must be one of: ' . implode(', ', [MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]) . '.', true)
->param('html', false, new Boolean(), 'Is content of type HTML', true)
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
@ -2582,7 +2586,7 @@ App::post('/v1/messaging/messages/email')
->inject('project')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, array $attachments, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
@ -2615,6 +2619,29 @@ App::post('/v1/messaging/messages/email')
}
}
if (!empty($attachments)) {
foreach ($attachments as &$attachment) {
[$bucketId, $fileId] = CompoundUID::parse($attachment);
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
$attachment = [
'bucketId' => $bucketId,
'fileId' => $fileId,
];
}
}
$message = $dbForProject->createDocument('messages', new Document([
'$id' => $messageId,
'providerType' => MESSAGE_TYPE_EMAIL,
@ -2628,6 +2655,7 @@ App::post('/v1/messaging/messages/email')
'html' => $html,
'cc' => $cc,
'bcc' => $bcc,
'attachments' => $attachments,
],
'status' => $status,
]));
@ -2635,14 +2663,13 @@ App::post('/v1/messaging/messages/email')
switch ($status) {
case MessageStatus::PROCESSING:
$queueForMessaging
->setMessageId($message->getId())
->trigger();
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceCollection' => 'messages',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
@ -2672,7 +2699,7 @@ App::post('/v1/messaging/messages/email')
});
App::post('/v1/messaging/messages/sms')
->desc('Create an SMS.')
->desc('Create an SMS')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
->label('audits.resource', 'message/{response.$id}')
@ -2680,7 +2707,7 @@ App::post('/v1/messaging/messages/sms')
->label('scope', 'messages.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'createSMS')
->label('sdk.method', 'createSms')
->label('sdk.description', '/docs/references/messaging/create-sms.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
@ -2744,14 +2771,13 @@ App::post('/v1/messaging/messages/sms')
switch ($status) {
case MessageStatus::PROCESSING:
$queueForMessaging
->setMessageId($message->getId())
->trigger();
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceCollection' => 'messages',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
@ -2781,7 +2807,7 @@ App::post('/v1/messaging/messages/sms')
});
App::post('/v1/messaging/messages/push')
->desc('Create a push notification.')
->desc('Create a push notification')
->groups(['api', 'messaging'])
->label('audits.event', 'message.create')
->label('audits.resource', 'message/{response.$id}')
@ -2790,7 +2816,7 @@ App::post('/v1/messaging/messages/push')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'createPush')
->label('sdk.description', '/docs/references/messaging/create-push-notification.md')
->label('sdk.description', '/docs/references/messaging/create-push.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MESSAGE)
@ -2802,6 +2828,7 @@ App::post('/v1/messaging/messages/push')
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
->param('data', null, new JSON(), 'Additional Data for push notification.', true)
->param('action', '', new Text(256), 'Action for push notification.', true)
->param('image', '', new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage.', true)
->param('icon', '', new Text(256), 'Icon for push notification. Available only for Android and Web Platform.', true)
->param('sound', '', new Text(256), 'Sound for push notification. Available only for Android and IOS Platform.', true)
->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true)
@ -2815,7 +2842,7 @@ App::post('/v1/messaging/messages/push')
->inject('project')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, string $badge, string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$messageId = $messageId == 'unique()'
? ID::unique()
: $messageId;
@ -2846,9 +2873,41 @@ App::post('/v1/messaging/messages/push')
}
}
if (!empty($image)) {
[$bucketId, $fileId] = CompoundUID::parse($image);
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
if (!\in_array(Permission::read(Role::any()), \array_merge($file->getRead(), $bucket->getRead()))) {
throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC);
}
if (!\in_array($file->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) {
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
}
$host = App::getEnv('_APP_DOMAIN', 'localhost');
$domain = new Domain(\parse_url($host, PHP_URL_HOST));
$protocol = App::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
if (!$domain->isKnown()) {
throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC);
}
$image = "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/view?project={$project->getId()}";
}
$pushData = [];
$keys = ['title', 'body', 'data', 'action', 'icon', 'sound', 'color', 'tag', 'badge'];
$keys = ['title', 'body', 'data', 'action', 'image', 'icon', 'sound', 'color', 'tag', 'badge'];
foreach ($keys as $key) {
if (!empty($$key)) {
@ -2870,14 +2929,13 @@ App::post('/v1/messaging/messages/push')
switch ($status) {
case MessageStatus::PROCESSING:
$queueForMessaging
->setMessageId($message->getId())
->trigger();
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
break;
case MessageStatus::SCHEDULED:
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceCollection' => 'messages',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
@ -2964,7 +3022,7 @@ App::get('/v1/messaging/messages/:messageId/logs')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'listMessageLogs')
->label('sdk.description', '/docs/references/messaging/messages/get-logs.md')
->label('sdk.description', '/docs/references/messaging/list-message-logs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOG_LIST)
@ -3136,7 +3194,7 @@ App::get('/v1/messaging/messages/:messageId')
});
App::patch('/v1/messaging/messages/email/:messageId')
->desc('Update an email.')
->desc('Update an email')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
->label('audits.resource', 'message/{response.$id}')
@ -3231,7 +3289,6 @@ App::patch('/v1/messaging/messages/email/:messageId')
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceCollection' => 'messages',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
@ -3263,8 +3320,8 @@ App::patch('/v1/messaging/messages/email/:messageId')
if ($status === MessageStatus::PROCESSING) {
$queueForMessaging
->setMessageId($message->getId())
->trigger();
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
}
$queueForEvents
@ -3275,7 +3332,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
});
App::patch('/v1/messaging/messages/sms/:messageId')
->desc('Update an SMS.')
->desc('Update an SMS')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
->label('audits.resource', 'message/{response.$id}')
@ -3283,7 +3340,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
->label('scope', 'messages.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'updateSMS')
->label('sdk.method', 'updateSms')
->label('sdk.description', '/docs/references/messaging/update-email.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
@ -3350,7 +3407,6 @@ App::patch('/v1/messaging/messages/sms/:messageId')
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceCollection' => 'messages',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
@ -3382,8 +3438,8 @@ App::patch('/v1/messaging/messages/sms/:messageId')
if ($status === MessageStatus::PROCESSING) {
$queueForMessaging
->setMessageId($message->getId())
->trigger();
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
}
$queueForEvents
@ -3394,7 +3450,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
});
App::patch('/v1/messaging/messages/push/:messageId')
->desc('Update a push notification.')
->desc('Update a push notification')
->groups(['api', 'messaging'])
->label('audits.event', 'message.update')
->label('audits.resource', 'message/{response.$id}')
@ -3403,7 +3459,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN, APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'messaging')
->label('sdk.method', 'updatePush')
->label('sdk.description', '/docs/references/messaging/update-push-notification.md')
->label('sdk.description', '/docs/references/messaging/update-push.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MESSAGE)
@ -3415,6 +3471,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
->param('body', null, new Text(64230), 'Body for push notification.', true)
->param('data', null, new JSON(), 'Additional Data for push notification.', true)
->param('action', null, new Text(256), 'Action for push notification.', true)
->param('image', null, new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage.', true)
->param('icon', null, new Text(256), 'Icon for push notification. Available only for Android and Web platforms.', true)
->param('sound', null, new Text(256), 'Sound for push notification. Available only for Android and iOS platforms.', true)
->param('color', null, new Text(256), 'Color for push notification. Available only for Android platforms.', true)
@ -3428,7 +3485,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
->inject('project')
->inject('queueForMessaging')
->inject('response')
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?string $status, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@ -3498,6 +3555,38 @@ App::patch('/v1/messaging/messages/push/:messageId')
$pushData['badge'] = $badge;
}
if (!\is_null($image)) {
[$bucketId, $fileId] = CompoundUID::parse($image);
$bucket = $dbForProject->getDocument('buckets', $bucketId);
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
if (!\in_array(Permission::read(Role::any()), \array_merge($file->getRead(), $bucket->getRead()))) {
throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC);
}
if (!\in_array($file->getAttribute('mimeType'), ['image/png', 'image/jpeg'])) {
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
}
$host = App::getEnv('_APP_DOMAIN', 'localhost');
$domain = new Domain(\parse_url($host, PHP_URL_HOST));
$protocol = App::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
if (!$domain->isKnown()) {
throw new Exception(Exception::STORAGE_FILE_NOT_PUBLIC);
}
$pushData['image'] = "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/view?project={$project->getId()}";
}
$message->setAttribute('data', $pushData);
if (!\is_null($status)) {
@ -3509,7 +3598,6 @@ App::patch('/v1/messaging/messages/push/:messageId')
$schedule = $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'),
'resourceType' => 'message',
'resourceCollection' => 'messages',
'resourceId' => $message->getId(),
'resourceInternalId' => $message->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
@ -3541,8 +3629,8 @@ App::patch('/v1/messaging/messages/push/:messageId')
if ($status === MessageStatus::PROCESSING) {
$queueForMessaging
->setMessageId($message->getId())
->trigger();
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
->setMessageId($message->getId());
}
$queueForEvents

View file

@ -144,9 +144,30 @@ App::get('/v1/project/usage')
];
}, $dbForProject->find('buckets'));
// merge network inbound + outbound
$projectBandwidth = [];
foreach ($usage[METRIC_NETWORK_INBOUND] as $item) {
$projectBandwidth[$item['date']] ??= 0;
$projectBandwidth[$item['date']] += $item['value'];
}
foreach ($usage[METRIC_NETWORK_OUTBOUND] as $item) {
$projectBandwidth[$item['date']] ??= 0;
$projectBandwidth[$item['date']] += $item['value'];
}
$network = [];
foreach ($projectBandwidth as $date => $value) {
$network[] = [
'date' => $date,
'value' => $value
];
}
$response->dynamic(new Document([
'requests' => ($usage[METRIC_NETWORK_REQUESTS]),
'network' => ($usage[METRIC_NETWORK_INBOUND] + $usage[METRIC_NETWORK_OUTBOUND]),
'network' => $network,
'users' => ($usage[METRIC_USERS]),
'executions' => ($usage[METRIC_EXECUTIONS]),
'executionsTotal' => $total[METRIC_EXECUTIONS],

View file

@ -78,13 +78,18 @@ App::post('/v1/projects')
->inject('pools')
->action(function (string $projectId, string $name, string $teamId, string $region, string $description, string $logo, string $url, string $legalName, string $legalCountry, string $legalState, string $legalCity, string $legalAddress, string $legalTaxId, Response $response, Database $dbForConsole, Cache $cache, Group $pools) {
$team = $dbForConsole->getDocument('teams', $teamId);
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$allowList = \array_filter(\explode(',', App::getEnv('_APP_PROJECT_REGIONS', '')));
if (!empty($allowList) && !\in_array($region, $allowList)) {
throw new Exception(Exception::PROJECT_REGION_UNSUPPORTED, 'Region "' . $region . '" is not supported');
}
$auth = Config::getParam('auth', []);
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false];
foreach ($auth as $index => $method) {

View file

@ -360,9 +360,9 @@ App::post('/v1/storage/buckets/:bucketId/files')
->inject('user')
->inject('queueForEvents')
->inject('mode')
->inject('deviceFiles')
->inject('deviceLocal')
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceFiles, Device $deviceLocal) {
->inject('deviceForFiles')
->inject('deviceForLocal')
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -493,13 +493,13 @@ App::post('/v1/storage/buckets/:bucketId/files')
}
// Save to storage
$fileSize ??= $deviceLocal->getFileSize($fileTmpName);
$path = $deviceFiles->getPath($fileId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$path = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
$fileSize ??= $deviceForLocal->getFileSize($fileTmpName);
$path = $deviceForFiles->getPath($fileId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
$metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)];
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
if (!$file->isEmpty()) {
$chunks = $file->getAttribute('chunksTotal', 1);
$uploaded = $file->getAttribute('chunksUploaded', 0);
@ -514,32 +514,32 @@ App::post('/v1/storage/buckets/:bucketId/files')
}
}
$chunksUploaded = $deviceFiles->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
$chunksUploaded = $deviceForFiles->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
if (empty($chunksUploaded)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed uploading file');
}
if ($chunksUploaded === $chunks) {
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antivirus', true) && $fileSize <= APP_LIMIT_ANTIVIRUS && $deviceFiles->getType() === Storage::DEVICE_LOCAL) {
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antivirus', true) && $fileSize <= APP_LIMIT_ANTIVIRUS && $deviceForFiles->getType() === Storage::DEVICE_LOCAL) {
$antivirus = new Network(
App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310)
);
if (!$antivirus->fileScan($path)) {
$deviceFiles->delete($path);
$deviceForFiles->delete($path);
throw new Exception(Exception::STORAGE_INVALID_FILE);
}
}
$mimeType = $deviceFiles->getFileMimeType($path); // Get mime-type before compression and encryption
$fileHash = $deviceFiles->getFileHash($path); // Get file hash before compression and encryption
$mimeType = $deviceForFiles->getFileMimeType($path); // Get mime-type before compression and encryption
$fileHash = $deviceForFiles->getFileHash($path); // Get file hash before compression and encryption
$data = '';
// Compression
$algorithm = $bucket->getAttribute('compression', Compression::NONE);
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != Compression::NONE) {
$data = $deviceFiles->read($path);
$data = $deviceForFiles->read($path);
switch ($algorithm) {
case Compression::ZSTD:
$compressor = new Zstd();
@ -559,7 +559,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) {
if (empty($data)) {
$data = $deviceFiles->read($path);
$data = $deviceForFiles->read($path);
}
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
@ -567,12 +567,12 @@ App::post('/v1/storage/buckets/:bucketId/files')
}
if (!empty($data)) {
if (!$deviceFiles->write($path, $data, $mimeType)) {
if (!$deviceForFiles->write($path, $data, $mimeType)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to save file');
}
}
$sizeActual = $deviceFiles->getFileSize($path);
$sizeActual = $deviceForFiles->getFileSize($path);
$openSSLVersion = null;
$openSSLCipher = null;
@ -872,9 +872,9 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
->inject('project')
->inject('dbForProject')
->inject('mode')
->inject('deviceFiles')
->inject('deviceLocal')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceFiles, Device $deviceLocal) {
->inject('deviceForFiles')
->inject('deviceForLocal')
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
if (!\extension_loaded('imagick')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
@ -931,10 +931,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$cipher = null;
$background = (empty($background)) ? 'eceff1' : $background;
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
$deviceFiles = $deviceLocal;
$deviceForFiles = $deviceForLocal;
}
if (!$deviceFiles->exists($path)) {
if (!$deviceForFiles->exists($path)) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
@ -950,7 +950,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type;
}
$source = $deviceFiles->read($path);
$source = $deviceForFiles->read($path);
if (!empty($cipher)) { // Decrypt
$source = OpenSSL::decrypt(
@ -1033,8 +1033,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
->inject('response')
->inject('dbForProject')
->inject('mode')
->inject('deviceFiles')
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceFiles) {
->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -1064,7 +1064,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
$path = $file->getAttribute('path', '');
if (!$deviceFiles->exists($path)) {
if (!$deviceForFiles->exists($path)) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
}
@ -1100,7 +1100,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
$source = '';
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
$source = $deviceFiles->read($path);
$source = $deviceForFiles->read($path);
$source = OpenSSL::decrypt(
$source,
$file->getAttribute('openSSLCipher'),
@ -1114,14 +1114,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
switch ($file->getAttribute('algorithm', Compression::NONE)) {
case Compression::ZSTD:
if (empty($source)) {
$source = $deviceFiles->read($path);
$source = $deviceForFiles->read($path);
}
$compressor = new Zstd();
$source = $compressor->decompress($source);
break;
case Compression::GZIP:
if (empty($source)) {
$source = $deviceFiles->read($path);
$source = $deviceForFiles->read($path);
}
$compressor = new GZIP();
$source = $compressor->decompress($source);
@ -1136,13 +1136,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
}
if (!empty($rangeHeader)) {
$response->send($deviceFiles->read($path, $start, ($end - $start + 1)));
$response->send($deviceForFiles->read($path, $start, ($end - $start + 1)));
}
if ($size > APP_STORAGE_READ_BUFFER) {
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFiles->read(
$deviceForFiles->read(
$path,
($i * MAX_OUTPUT_CHUNK_SIZE),
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
@ -1151,7 +1151,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
);
}
} else {
$response->send($deviceFiles->read($path));
$response->send($deviceForFiles->read($path));
}
});
@ -1173,8 +1173,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
->inject('request')
->inject('dbForProject')
->inject('mode')
->inject('deviceFiles')
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceFiles) {
->inject('deviceForFiles')
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@ -1205,7 +1205,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$path = $file->getAttribute('path', '');
if (!$deviceFiles->exists($path)) {
if (!$deviceForFiles->exists($path)) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
}
@ -1249,7 +1249,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
$source = '';
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
$source = $deviceFiles->read($path);
$source = $deviceForFiles->read($path);
$source = OpenSSL::decrypt(
$source,
$file->getAttribute('openSSLCipher'),
@ -1263,14 +1263,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
switch ($file->getAttribute('algorithm', Compression::NONE)) {
case Compression::ZSTD:
if (empty($source)) {
$source = $deviceFiles->read($path);
$source = $deviceForFiles->read($path);
}
$compressor = new Zstd();
$source = $compressor->decompress($source);
break;
case Compression::GZIP:
if (empty($source)) {
$source = $deviceFiles->read($path);
$source = $deviceForFiles->read($path);
}
$compressor = new GZIP();
$source = $compressor->decompress($source);
@ -1286,15 +1286,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
}
if (!empty($rangeHeader)) {
$response->send($deviceFiles->read($path, $start, ($end - $start + 1)));
$response->send($deviceForFiles->read($path, $start, ($end - $start + 1)));
return;
}
$size = $deviceFiles->getFileSize($path);
$size = $deviceForFiles->getFileSize($path);
if ($size > APP_STORAGE_READ_BUFFER) {
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFiles->read(
$deviceForFiles->read(
$path,
($i * MAX_OUTPUT_CHUNK_SIZE),
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
@ -1303,7 +1303,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
);
}
} else {
$response->send($deviceFiles->read($path));
$response->send($deviceForFiles->read($path));
}
});
@ -1438,9 +1438,9 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->inject('dbForProject')
->inject('queueForEvents')
->inject('mode')
->inject('deviceFiles')
->inject('deviceForFiles')
->inject('queueForDeletes')
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceFiles, Delete $queueForDeletes) {
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
@ -1471,17 +1471,18 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
$deviceDeleted = false;
if ($file->getAttribute('chunksTotal') !== $file->getAttribute('chunksUploaded')) {
$deviceDeleted = $deviceFiles->abort(
$deviceDeleted = $deviceForFiles->abort(
$file->getAttribute('path'),
($file->getAttribute('metadata', [])['uploadId'] ?? '')
);
} else {
$deviceDeleted = $deviceFiles->delete($file->getAttribute('path'));
$deviceDeleted = $deviceForFiles->delete($file->getAttribute('path'));
}
if ($deviceDeleted) {
$queueForDeletes
->setType(DELETE_TYPE_CACHE_BY_RESOURCE)
->setResourceType('bucket/' . $bucket->getId())
->setResource('file/' . $fileId)
;

View file

@ -658,10 +658,10 @@ App::post('/v1/teams/:teamId/memberships')
]);
$queueForMessaging
->setType(MESSAGE_SEND_TYPE_INTERNAL)
->setMessage($messageDoc)
->setRecipients([$phone])
->setProviderType('SMS')
->trigger();
->setProviderType('SMS');
}
}
@ -995,7 +995,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$dbForProject->purgeCachedDocument('users', $user->getId());
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('total', $team->getAttribute('total', 0) + 1)));
Authorization::skip(fn() => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$queueForEvents
->setParam('teamId', $team->getId())
@ -1075,8 +1075,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$dbForProject->purgeCachedDocument('users', $user->getId());
if ($membership->getAttribute('confirm')) { // Count only confirmed members
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0));
Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
Authorization::skip(fn() => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
}
$queueForEvents

View file

@ -192,7 +192,6 @@ App::post('/v1/users')
->inject('hooks')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
@ -806,6 +805,9 @@ App::get('/v1/users/:userId/logs')
$output[$i] = new Document([
'event' => $log['event'],
'userId' => ID::custom($log['data']['userId']),
'userEmail' => $log['data']['userEmail'] ?? null,
'userName' => $log['data']['userName'] ?? null,
'ip' => $log['ip'],
'time' => $log['time'],
'osCode' => $os['osCode'],
@ -834,7 +836,7 @@ App::get('/v1/users/:userId/logs')
}
$response->dynamic(new Document([
'total' => $audit->countLogsByUser($user->getId()),
'total' => $audit->countLogsByUser($user->getInternalId()),
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});

View file

@ -238,9 +238,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->trigger();
->setDeployment($deployment);
//TODO: Add event?
}

View file

@ -2,6 +2,7 @@
use Appwrite\Auth\Auth;
use Appwrite\Event\Audit;
use Appwrite\Event\Build;
use Appwrite\Event\Database as EventDatabase;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
@ -305,10 +306,11 @@ App::init()
->inject('queueForAudits')
->inject('queueForDeletes')
->inject('queueForDatabase')
->inject('queueForBuilds')
->inject('queueForUsage')
->inject('dbForProject')
->inject('mode')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) {
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) {
$route = $utopia->getRoute();
@ -382,9 +384,6 @@ App::init()
->setProject($project)
->setUser($user);
$queueForMessaging
->setProject($project);
$queueForAudits
->setMode($mode)
->setUserAgent($request->getUserAgent(''))
@ -393,9 +392,10 @@ App::init()
->setProject($project)
->setUser($user);
$queueForDeletes->setProject($project);
$queueForDatabase->setProject($project);
$queueForBuilds->setProject($project);
$queueForMessaging->setProject($project);
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject))
@ -403,24 +403,22 @@ App::init()
;
$useCache = $route->getLabel('cache', false);
if ($useCache) {
$key = md5($request->getURI() . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
$timestamp = 60 * 60 * 24 * 30;
$data = $cache->load($key, $timestamp);
if (!empty($data)) {
$data = json_decode($data, true);
$parts = explode('/', $data['resourceType']);
if (!empty($data) && !$cacheLog->isEmpty()) {
$parts = explode('/', $cacheLog->getAttribute('resourceType'));
$type = $parts[0] ?? null;
if ($type === 'bucket') {
$bucketId = $parts[1] ?? null;
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
@ -432,11 +430,12 @@ App::init()
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$parts = explode('/', $data['resource']);
$parts = explode('/', $cacheLog->getAttribute('resource'));
$fileId = $parts[1] ?? null;
if ($fileSecurity && !$valid) {
@ -453,8 +452,8 @@ App::init()
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT')
->addHeader('X-Appwrite-Cache', 'hit')
->setContentType($data['contentType'])
->send(base64_decode($data['payload']))
->setContentType($cacheLog->getAttribute('mimeType'))
->send($data)
;
} else {
$response->addHeader('X-Appwrite-Cache', 'miss');
@ -513,11 +512,13 @@ App::shutdown()
->inject('queueForUsage')
->inject('queueForDeletes')
->inject('queueForDatabase')
->inject('queueForBuilds')
->inject('queueForMessaging')
->inject('dbForProject')
->inject('queueForFunctions')
->inject('mode')
->inject('dbForConsole')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) {
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) {
$responsePayload = $response->getPayload();
@ -618,6 +619,14 @@ App::shutdown()
$queueForDatabase->trigger();
}
if (!empty($queueForBuilds->getType())) {
$queueForBuilds->trigger();
}
if (!empty($queueForMessaging->getType())) {
$queueForMessaging->trigger();
}
/**
* Cache label
*/
@ -625,7 +634,6 @@ App::shutdown()
if ($useCache) {
$resource = $resourceType = null;
$data = $response->getPayload();
if (!empty($data['payload'])) {
$pattern = $route->getLabel('cache.resource', null);
if (!empty($pattern)) {
@ -637,24 +645,19 @@ App::shutdown()
$resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user);
}
$key = md5($request->getURI() . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER);
$data = json_encode([
'resourceType' => $resourceType,
'resource' => $resource,
'contentType' => $response->getContentType(),
'payload' => base64_encode($data['payload']),
]);
$signature = md5($data);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$key = md5($request->getURI() . '*' . implode('*', $request->getParams())) . '*' . APP_CACHE_BUSTER;
$signature = md5($data['payload']);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
$now = DateTime::now();
if ($cacheLog->isEmpty()) {
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
'$id' => $key,
'resource' => $resource,
'accessedAt' => $now,
'signature' => $signature,
'$id' => $key,
'resource' => $resource,
'resourceType' => $resourceType,
'mimeType' => $response->getContentType(),
'accessedAt' => $now,
'signature' => $signature,
])));
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
$cacheLog->setAttribute('accessedAt', $now);
@ -665,7 +668,7 @@ App::shutdown()
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
$cache->save($key, $data);
$cache->save($key, $data['payload']);
}
}
}

View file

@ -3,6 +3,7 @@
require_once __DIR__ . '/../vendor/autoload.php';
use Appwrite\Utopia\Response;
use Swoole\Constant;
use Swoole\Process;
use Swoole\Http\Server;
use Swoole\Http\Request as SwooleRequest;
@ -20,12 +21,15 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Swoole\Files;
use Appwrite\Utopia\Request;
use Swoole\Coroutine;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Pools\Group;
$http = new Server("0.0.0.0", App::getEnv('PORT', 80));
$http = new Server(
host: "0.0.0.0",
port: App::getEnv('PORT', 80),
mode: SWOOLE_PROCESS,
);
$payloadSize = 6 * (1024 * 1024); // 6MB
$workerNumber = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6));
@ -34,23 +38,21 @@ $http
->set([
'worker_num' => $workerNumber,
'open_http2_protocol' => true,
// 'document_root' => __DIR__.'/../public',
// 'enable_static_handler' => true,
'http_compression' => true,
'http_compression_level' => 6,
'package_max_length' => $payloadSize,
'buffer_output_size' => $payloadSize,
]);
$http->on('WorkerStart', function ($server, $workerId) {
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
Console::success('Worker ' . ++$workerId . ' started successfully');
});
$http->on('BeforeReload', function ($server, $workerId) {
$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) {
Console::success('Starting reload...');
});
$http->on('AfterReload', function ($server, $workerId) {
$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
@ -58,7 +60,7 @@ Files::load(__DIR__ . '/../console');
include __DIR__ . '/controllers/general.php';
$http->on('start', function (Server $http) use ($payloadSize, $register) {
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
$app = new App('UTC');
go(function () use ($register, $app) {

View file

@ -142,9 +142,11 @@ const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
const APP_HOSTNAME_INTERNAL = 'appwrite';
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
// Database Worker Types
const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute';
const DATABASE_TYPE_CREATE_INDEX = 'createIndex';
@ -152,9 +154,11 @@ const DATABASE_TYPE_DELETE_ATTRIBUTE = 'deleteAttribute';
const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex';
const DATABASE_TYPE_DELETE_COLLECTION = 'deleteCollection';
const DATABASE_TYPE_DELETE_DATABASE = 'deleteDatabase';
// Build Worker Types
const BUILD_TYPE_DEPLOYMENT = 'deployment';
const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
@ -180,6 +184,10 @@ const DELETE_TYPE_TOPIC = 'topic';
const DELETE_TYPE_TARGET = 'target';
const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
// Message types
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
const MESSAGE_SEND_TYPE_EXTERNAL = 'external';
// Mail Types
const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
@ -1375,19 +1383,19 @@ App::setResource('cache', function (Group $pools) {
return new Cache(new Sharding($adapters));
}, ['pools']);
App::setResource('deviceLocal', function () {
App::setResource('deviceForLocal', function () {
return new Local();
});
App::setResource('deviceFiles', function ($project) {
App::setResource('deviceForFiles', function ($project) {
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceFunctions', function ($project) {
App::setResource('deviceForFunctions', function ($project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceBuilds', function ($project) {
App::setResource('deviceForBuilds', function ($project) {
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
}, ['project']);

View file

@ -15,6 +15,7 @@ use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Phone;
use Appwrite\Event\Usage;
use Appwrite\Event\UsageDump;
use Appwrite\Platform\Appwrite;
use Swoole\Runtime;
use Utopia\App;
@ -34,6 +35,7 @@ use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Pools\Group;
use Utopia\Queue\Connection;
use Utopia\Storage\Device\Local;
Authorization::disable();
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
@ -45,8 +47,7 @@ Server::setResource('dbForConsole', function (Cache $cache, Registry $register)
$database = $pools
->get('console')
->pop()
->getResource()
;
->getResource();
$adapter = new Database($database, $cache);
$adapter->setNamespace('_console');
@ -54,26 +55,6 @@ Server::setResource('dbForConsole', function (Cache $cache, Registry $register)
return $adapter;
}, ['cache', 'register']);
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Database $dbForConsole) {
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
}
$pools = $register->get('pools');
$database = $pools
->get($project->getAttribute('database'))
->pop()
->getResource()
;
$adapter = new Database($database, $cache);
$adapter->setNamespace('_' . $project->getInternalId());
return $adapter;
}, ['cache', 'register', 'message', 'dbForConsole']);
Server::setResource('project', function (Message $message, Database $dbForConsole) {
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
@ -81,10 +62,26 @@ Server::setResource('project', function (Message $message, Database $dbForConsol
if ($project->getId() === 'console') {
return $project;
}
return $dbForConsole->getDocument('projects', $project->getId());
;
}, ['message', 'dbForConsole']);
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForConsole) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
}
$pools = $register->get('pools');
$database = $pools
->get($project->getAttribute('database'))
->pop()
->getResource();
$adapter = new Database($database, $cache);
$adapter->setNamespace('_' . $project->getInternalId());
return $adapter;
}, ['cache', 'register', 'message', 'project', 'dbForConsole']);
Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
@ -143,72 +140,92 @@ Server::setResource('cache', function (Registry $register) {
return new Cache(new Sharding($adapters));
}, ['register']);
Server::setResource('log', fn() => new Log());
Server::setResource('queueForUsage', function (Connection $queue) {
return new Usage($queue);
}, ['queue']);
Server::setResource('queueForUsageDump', function (Connection $queue) {
return new UsageDump($queue);
}, ['queue']);
Server::setResource('queue', function (Group $pools) {
return $pools->get('queue')->pop()->getResource();
}, ['pools']);
Server::setResource('queueForDatabase', function (Connection $queue) {
return new EventDatabase($queue);
}, ['queue']);
Server::setResource('queueForMessaging', function (Connection $queue) {
return new Messaging($queue);
}, ['queue']);
Server::setResource('queueForMails', function (Connection $queue) {
return new Mail($queue);
}, ['queue']);
Server::setResource('queueForBuilds', function (Connection $queue) {
return new Build($queue);
}, ['queue']);
Server::setResource('queueForDeletes', function (Connection $queue) {
return new Delete($queue);
}, ['queue']);
Server::setResource('queueForEvents', function (Connection $queue) {
return new Event($queue);
}, ['queue']);
Server::setResource('queueForAudits', function (Connection $queue) {
return new Audit($queue);
}, ['queue']);
Server::setResource('queueForFunctions', function (Connection $queue) {
return new Func($queue);
}, ['queue']);
Server::setResource('queueForCertificates', function (Connection $queue) {
return new Certificate($queue);
}, ['queue']);
Server::setResource('queueForMigrations', function (Connection $queue) {
return new Migration($queue);
}, ['queue']);
Server::setResource('queueForHamster', function (Connection $queue) {
return new Hamster($queue);
}, ['queue']);
Server::setResource('logger', function (Registry $register) {
return $register->get('logger');
}, ['register']);
Server::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
Server::setResource('getFunctionsDevice', function () {
return function (string $projectId) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $projectId);
};
});
Server::setResource('getFilesDevice', function () {
return function (string $projectId) {
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $projectId);
};
});
Server::setResource('getBuildsDevice', function () {
return function (string $projectId) {
return getDevice(APP_STORAGE_BUILDS . '/app-' . $projectId);
};
});
Server::setResource('getCacheDevice', function () {
return function (string $projectId) {
return getDevice(APP_STORAGE_CACHE . '/app-' . $projectId);
};
});
Server::setResource('deviceForFunctions', function (Document $project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForFiles', function (Document $project) {
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForBuilds', function (Document $project) {
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForCache', function (Document $project) {
return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
}, ['project']);
Server::setResource('deviceForLocalFiles', function (Document $project) {
return new Local(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}, ['project']);
$pools = $register->get('pools');
$platform = new Appwrite();
@ -303,12 +320,9 @@ $worker
Console::error('[Error] Line: ' . $error->getLine());
});
try {
$workerStart = $worker->getWorkerStart();
} catch (\Throwable $error) {
$worker->workerStart();
} finally {
Console::info("Worker $workerName started");
}
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
});
$worker->start();

3
bin/worker-usage-dump Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php usage-dump $@

View file

@ -1,4 +1,5 @@
{
"name": "appwrite/server-ce",
"description": "End to end backend server for frontend and mobile apps.",
"type": "project",
@ -49,14 +50,14 @@
"utopia-php/cache": "0.9.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.48.*",
"utopia-php/database": "0.48.2",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.*",
"utopia-php/framework": "0.33.*",
"utopia-php/image": "0.6.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",
"utopia-php/messaging": "0.9.*",
"utopia-php/messaging": "0.10.*",
"utopia-php/migration": "0.3.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.5.*",
@ -70,7 +71,7 @@
"utopia-php/websocket": "0.1.*",
"matomo/device-detector": "6.1.*",
"dragonmantank/cron-expression": "3.3.2",
"phpmailer/phpmailer": "6.8.0",
"phpmailer/phpmailer": "6.9.1",
"chillerlan/php-qrcode": "4.3.4",
"adhocore/jwt": "1.1.2",
"spomky-labs/otphp": "^10.0",

75
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "609062319cc652e2760367f39604ac77",
"content-hash": "510f3498b35c5ec3289b7bed6a2ef9c9",
"packages": [
{
"name": "adhocore/jwt",
@ -885,16 +885,16 @@
},
{
"name": "phpmailer/phpmailer",
"version": "v6.8.0",
"version": "v6.9.1",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "df16b615e371d81fb79e506277faea67a1be18f1"
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/df16b615e371d81fb79e506277faea67a1be18f1",
"reference": "df16b615e371d81fb79e506277faea67a1be18f1",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18",
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18",
"shasum": ""
},
"require": {
@ -904,16 +904,17 @@
"php": ">=5.5.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.2",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"doctrine/annotations": "^1.2.6 || ^1.13.3",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.7.1",
"squizlabs/php_codesniffer": "^3.7.2",
"yoast/phpunit-polyfills": "^1.0.4"
},
"suggest": {
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
@ -953,7 +954,7 @@
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0"
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1"
},
"funding": [
{
@ -961,7 +962,7 @@
"type": "github"
}
],
"time": "2023-03-06T14:43:22+00:00"
"time": "2023-11-25T22:23:28+00:00"
},
{
"name": "spomky-labs/otphp",
@ -1911,28 +1912,28 @@
},
{
"name": "utopia-php/messaging",
"version": "0.9.1",
"version": "0.10.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/messaging.git",
"reference": "7beec07684e9e1dfcf4ab5b1ba731fa396dccbdf"
"reference": "71dce00ad43eb278a877cb2c329f7b8d677adfeb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/7beec07684e9e1dfcf4ab5b1ba731fa396dccbdf",
"reference": "7beec07684e9e1dfcf4ab5b1ba731fa396dccbdf",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/71dce00ad43eb278a877cb2c329f7b8d677adfeb",
"reference": "71dce00ad43eb278a877cb2c329f7b8d677adfeb",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.0.0"
"php": ">=8.0.0",
"phpmailer/phpmailer": "6.9.1"
},
"require-dev": {
"laravel/pint": "1.13.*",
"phpmailer/phpmailer": "6.8.*",
"phpstan/phpstan": "1.10.*",
"phpunit/phpunit": "9.6.10"
"laravel/pint": "1.13.11",
"phpstan/phpstan": "1.10.58",
"phpunit/phpunit": "10.5.10"
},
"type": "library",
"autoload": {
@ -1955,9 +1956,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/messaging/issues",
"source": "https://github.com/utopia-php/messaging/tree/0.9.1"
"source": "https://github.com/utopia-php/messaging/tree/0.10.0"
},
"time": "2024-02-15T03:44:44+00:00"
"time": "2024-02-20T07:30:15+00:00"
},
{
"name": "utopia-php/migration",
@ -2779,16 +2780,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.36.3",
"version": "0.36.4",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "8d308f7f492545da3e51ea5b91c0778392c40b93"
"reference": "8d932098009d62d37dda73cfe4ebc11f83e21405"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/8d308f7f492545da3e51ea5b91c0778392c40b93",
"reference": "8d308f7f492545da3e51ea5b91c0778392c40b93",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/8d932098009d62d37dda73cfe4ebc11f83e21405",
"reference": "8d932098009d62d37dda73cfe4ebc11f83e21405",
"shasum": ""
},
"require": {
@ -2824,9 +2825,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.36.3"
"source": "https://github.com/appwrite/sdk-generator/tree/0.36.4"
},
"time": "2024-02-14T06:33:38+00:00"
"time": "2024-02-20T16:36:15+00:00"
},
{
"name": "doctrine/deprecations",
@ -3409,16 +3410,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.8.0",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc"
"reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fad452781b3d774e3337b0c0b245dd8e5a4455fc",
"reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353",
"reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353",
"shasum": ""
},
"require": {
@ -3461,9 +3462,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.0"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.1"
},
"time": "2024-01-11T11:49:22+00:00"
"time": "2024-01-18T19:15:27+00:00"
},
{
"name": "phpspec/prophecy",
@ -5019,16 +5020,16 @@
},
{
"name": "squizlabs/php_codesniffer",
"version": "3.8.1",
"version": "3.9.0",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
"reference": "14f5fff1e64118595db5408e946f3a22c75807f7"
"reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7",
"reference": "14f5fff1e64118595db5408e946f3a22c75807f7",
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b",
"reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b",
"shasum": ""
},
"require": {
@ -5095,7 +5096,7 @@
"type": "open_collective"
}
],
"time": "2024-01-11T20:47:48+00:00"
"time": "2024-02-16T15:06:51+00:00"
},
{
"name": "swoole/ide-helper",

View file

@ -582,7 +582,6 @@ services:
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMS_PROJECTS_DENY_LIST
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
@ -592,6 +591,7 @@ services:
- _APP_LOGGING_CONFIG
- _APP_SMS_FROM
- _APP_SMS_PROVIDER
- _APP_SMS_PROJECTS_DENY_LIST
appwrite-worker-migrations:
entrypoint: worker-migrations
@ -696,6 +696,37 @@ services:
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-worker-usage-dump:
entrypoint: worker-usage-dump
<<: *x-logging
container_name: appwrite-worker-usage-dump
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_USAGE_AGGREGATION_INTERVAL
appwrite-scheduler-functions:
entrypoint: schedule-functions
<<: *x-logging
@ -941,6 +972,7 @@ services:
# - appwrite
# volumes:
# - appwrite-uploads:/storage/uploads
# Dev Tools Start ------------------------------------------------------------------------------------------
#
# The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack
@ -949,8 +981,9 @@ services:
#
# MailCatcher - An SMTP server. Catches all system emails and displays them in a nice UI.
# RequestCatcher - An HTTP server. Catches all system https calls and displays them using a simple HTTP API. Used to debug & tests webhooks and HTTP tasks
# RedisCommander - A nice UI for exploring Redis data
# Webgrind - A nice UI for exploring and debugging code-level stuff
# Redis Insight - A nice UI for exploring Redis data
# Adminer - A nice UI for exploring MariaDB data
# GraphQl Explorer - A nice UI for exploring GraphQL API
maildev: # used mainly for dev tests
image: appwrite/mailcatcher:1.0.0
@ -980,21 +1013,15 @@ services:
networks:
- appwrite
# redis-commander:
# image: rediscommander/redis-commander:latest
# restart: unless-stopped
# networks:
# - appwrite
# environment:
# - REDIS_HOSTS=redis
# ports:
# - "8081:8081"
# webgrind:
# image: 'jokkedk/webgrind:latest'
# volumes:
# - './debug:/tmp'
# ports:
# - '3001:80'
redis-insight:
image: redis/redisinsight:latest
restart: unless-stopped
networks:
- appwrite
environment:
- REDIS_HOSTS=redis
ports:
- "8081:5540"
graphql-explorer:
container_name: appwrite-graphql-explorer

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.create_phone_verification()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.create_recovery(email: 'email@example.com', url: 'https://example.com')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.create_verification(url: 'https://example.com')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.delete_identity(identity_id: '[IDENTITY_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.delete_session(session_id: '[SESSION_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.delete_sessions()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.get_prefs()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.get_session(session_id: '[SESSION_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.get()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.list_identities()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.list_logs()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.list_sessions()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_email(email: 'email@example.com', password: 'password')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_name(name: '[NAME]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_password(password: '')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_phone_verification(user_id: '[USER_ID]', secret: '[SECRET]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_phone(phone: '+12065550100', password: 'password')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_prefs(prefs: {})
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_recovery(user_id: '[USER_ID]', secret: '[SECRET]', password: 'password', password_again: 'password')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_session(session_id: '[SESSION_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_status()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ account = Account.new(client)
response = account.update_verification(user_id: '[USER_ID]', secret: '[SECRET]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_browser(code: 'aa')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_credit_card(code: 'amex')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_favicon(url: 'https://example.com')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_flag(code: 'af')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_image(url: 'https://example.com')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_initials()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ avatars = Avatars.new(client)
response = avatars.get_qr(text: '[TEXT]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_boolean_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', name: '[NAME]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_datetime_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]', data: {})
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_email_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_enum_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', elements: [], required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_float_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_index(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', type: 'key', attributes: [])
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_integer_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_ip_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_relationship_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', related_collection_id: '[RELATED_COLLECTION_ID]', type: 'oneToOne')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_string_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', size: 1, required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create_url_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.create(database_id: '[DATABASE_ID]', name: '[NAME]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete_index(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.delete(database_id: '[DATABASE_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.get_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.get_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.get_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.get_index(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.get(database_id: '[DATABASE_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.list_attributes(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.list_collections(database_id: '[DATABASE_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.list_documents(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.list_indexes(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.list()
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_boolean_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false, default: false)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_collection(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', name: '[NAME]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_datetime_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false, default: '')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_document(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', document_id: '[DOCUMENT_ID]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_email_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false, default: 'email@example.com')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_enum_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', elements: [], required: false, default: '[DEFAULT]')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_float_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false, min: null, max: null, default: null)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_integer_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false, min: null, max: null, default: null)
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_ip_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '', required: false, default: '')
puts response.inspect
puts response.inspect

View file

@ -1,4 +1,4 @@
require 'Appwrite'
require 'appwrite'
include Appwrite
@ -11,4 +11,4 @@ databases = Databases.new(client)
response = databases.update_relationship_attribute(database_id: '[DATABASE_ID]', collection_id: '[COLLECTION_ID]', key: '')
puts response.inspect
puts response.inspect

Some files were not shown because too many files have changed in this diff Show more