fix: new console
This commit is contained in:
commit
756993eea2
2
.env
2
.env
|
@ -59,6 +59,7 @@ _APP_SMTP_USERNAME=
|
|||
_APP_SMTP_PASSWORD=
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_SMS_PROJECTS_DENY_LIST=
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
_APP_STORAGE_PREVIEW_LIMIT=20000000
|
||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
||||
|
@ -73,6 +74,7 @@ _APP_EXECUTOR_SECRET=your-secret-key
|
|||
_APP_EXECUTOR_HOST=http://proxy/v1
|
||||
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
|
||||
_APP_MAINTENANCE_INTERVAL=86400
|
||||
_APP_MAINTENANCE_DELAY=
|
||||
_APP_MAINTENANCE_RETENTION_CACHE=2592000
|
||||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
|
||||
_APP_MAINTENANCE_RETENTION_ABUSE=86400
|
||||
|
|
|
@ -89,6 +89,10 @@ RUN chmod +x /usr/local/bin/doctor && \
|
|||
chmod +x /usr/local/bin/test && \
|
||||
chmod +x /usr/local/bin/upgrade && \
|
||||
chmod +x /usr/local/bin/vars && \
|
||||
chmod +x /usr/local/bin/queue-retry && \
|
||||
chmod +x /usr/local/bin/queue-count-failed && \
|
||||
chmod +x /usr/local/bin/queue-count-processing && \
|
||||
chmod +x /usr/local/bin/queue-count-success && \
|
||||
chmod +x /usr/local/bin/worker-audits && \
|
||||
chmod +x /usr/local/bin/worker-builds && \
|
||||
chmod +x /usr/local/bin/worker-certificates && \
|
||||
|
|
|
@ -77,7 +77,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
|||
}
|
||||
|
||||
$ready = true;
|
||||
} catch (\Exception $err) {
|
||||
} catch (\Throwable $err) {
|
||||
Console::warning($err->getMessage());
|
||||
$pools->get('console')->reclaim();
|
||||
sleep($sleep);
|
||||
|
|
|
@ -711,6 +711,7 @@ return [
|
|||
'name' => Exception::RULE_VERIFICATION_FAILED,
|
||||
'description' => 'Domain verification failed. Please check if your DNS records are correct and try again.',
|
||||
'code' => 401,
|
||||
'publish' => true
|
||||
],
|
||||
Exception::PROJECT_SMTP_CONFIG_INVALID => [
|
||||
'name' => Exception::PROJECT_SMTP_CONFIG_INVALID,
|
||||
|
@ -798,13 +799,25 @@ return [
|
|||
],
|
||||
|
||||
/** Health */
|
||||
Exception::QUEUE_SIZE_EXCEEDED => [
|
||||
'name' => Exception::QUEUE_SIZE_EXCEEDED,
|
||||
Exception::HEALTH_QUEUE_SIZE_EXCEEDED => [
|
||||
'name' => Exception::HEALTH_QUEUE_SIZE_EXCEEDED,
|
||||
'description' => 'Queue size threshold hit.',
|
||||
'code' => 503,
|
||||
'publish' => false
|
||||
],
|
||||
|
||||
Exception::HEALTH_CERTIFICATE_EXPIRED => [
|
||||
'name' => Exception::HEALTH_CERTIFICATE_EXPIRED,
|
||||
'description' => 'The SSL certificate for the specified domain has expired and is no longer valid.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
Exception::HEALTH_INVALID_HOST => [
|
||||
'name' => Exception::HEALTH_INVALID_HOST,
|
||||
'description' => 'Failed to establish a connection to the specified domain. Please verify the domain name and ensure that the server is running and accessible.',
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Providers */
|
||||
Exception::PROVIDER_NOT_FOUND => [
|
||||
'name' => Exception::PROVIDER_NOT_FOUND,
|
||||
|
@ -867,6 +880,16 @@ return [
|
|||
'description' => 'Message with the requested ID has already been sent.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_ALREADY_PROCESSING => [
|
||||
'name' => Exception::MESSAGE_ALREADY_PROCESSING,
|
||||
'description' => 'Message with the requested ID is already being processed.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_ALREADY_FAILED => [
|
||||
'name' => Exception::MESSAGE_ALREADY_FAILED,
|
||||
'description' => 'Message with the requested ID has already failed.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_ALREADY_SCHEDULED => [
|
||||
'name' => Exception::MESSAGE_ALREADY_SCHEDULED,
|
||||
'description' => 'Message with the requested ID has already been scheduled for delivery.',
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -948,6 +948,15 @@ return [
|
|||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_DELAY',
|
||||
'description' => 'Delay value containing the number of seconds that the Appwrite maintenance process should wait before executing system cleanups and optimizations. The default value is 0 seconds.',
|
||||
'introduction' => '1.5.0',
|
||||
'default' => '0',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MAINTENANCE_RETENTION_CACHE',
|
||||
'description' => 'The maximum duration (in seconds) upto which to retain cached files. The default value is 2592000 seconds (30 days).',
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 01aa032daef600cc5e07f4be5019c2fbf8f3420b
|
||||
Subproject commit 74f09e341a9e8b10f66e1cfc5d990158b2f952b6
|
|
@ -14,6 +14,7 @@ use Appwrite\Event\Mail;
|
|||
use Appwrite\Auth\Phrase;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
@ -505,7 +506,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
if (!empty($state)) {
|
||||
try {
|
||||
$state = \array_merge($defaultState, $oauth2->parseState($state));
|
||||
} catch (\Exception $exception) {
|
||||
} catch (\Throwable $exception) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to parse login state params as passed from OAuth2 provider');
|
||||
}
|
||||
} else {
|
||||
|
@ -907,11 +908,17 @@ App::get('/v1/account/identities')
|
|||
->inject('dbForProject')
|
||||
->action(function (array $queries, Response $response, Document $user, Database $dbForProject) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$queries[] = Query::equal('userInternalId', [$user->getInternalId()]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -978,7 +985,7 @@ App::delete('/v1/account/identities/:identityId')
|
|||
App::post('/v1/account/tokens/magic-url')
|
||||
->alias('/v1/account/sessions/magic-url')
|
||||
->desc('Create magic URL token')
|
||||
->groups(['api', 'account'])
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->label('scope', 'sessions.write')
|
||||
->label('auth.type', 'magic-url')
|
||||
->label('audits.event', 'session.create')
|
||||
|
@ -986,14 +993,14 @@ App::post('/v1/account/tokens/magic-url')
|
|||
->label('audits.userId', '{response.userId}')
|
||||
->label('sdk.auth', [])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', ['createMagicURLToken', 'createMagicURLSession'])
|
||||
->label('sdk.method', 'createMagicURLToken')
|
||||
->label('sdk.description', '/docs/references/account/create-token-magic-url.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},email:{param-email}')
|
||||
->param('userId', '', new CustomId(), 'User 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.')
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
|
||||
|
@ -1623,13 +1630,13 @@ App::post('/v1/account/tokens/phone')
|
|||
->label('audits.userId', '{response.userId}')
|
||||
->label('sdk.auth', [])
|
||||
->label('sdk.namespace', 'account')
|
||||
->label('sdk.method', ['createPhoneToken', 'createPhoneSession'])
|
||||
->label('sdk.method', 'createPhoneToken')
|
||||
->label('sdk.description', '/docs/references/account/create-token-phone.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},phone:{param-phone}')
|
||||
->label('abuse-key', ['url:{url},phone:{param-phone}', 'url:{url},ip:{ip}'])
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
|
||||
->inject('request')
|
||||
|
@ -2066,7 +2073,12 @@ App::get('/v1/account/logs')
|
|||
->inject('dbForProject')
|
||||
->action(function (array $queries, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
@ -2736,7 +2748,7 @@ App::post('/v1/account/recovery')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'ip:{ip}'])
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients'])
|
||||
->inject('request')
|
||||
|
@ -3204,8 +3216,9 @@ App::put('/v1/account/verification')
|
|||
|
||||
App::post('/v1/account/verification/phone')
|
||||
->desc('Create phone verification')
|
||||
->groups(['api', 'account'])
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->label('scope', 'accounts.write')
|
||||
->label('auth.type', 'phone')
|
||||
->label('event', 'users.[userId].verification.[tokenId].create')
|
||||
->label('audits.event', 'verification.create')
|
||||
->label('audits.resource', 'user/{response.userId}')
|
||||
|
@ -3217,7 +3230,7 @@ App::post('/v1/account/verification/phone')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TOKEN)
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'userId:{userId}')
|
||||
->label('abuse-key', ['url:{url},userId:{userId}', 'url:{url},ip:{ip}'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
|
|
|
@ -291,7 +291,7 @@ App::get('/v1/avatars/image')
|
|||
|
||||
try {
|
||||
$image = new Image($fetch);
|
||||
} catch (\Exception $exception) {
|
||||
} catch (\Throwable $exception) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unable to parse image');
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ use Utopia\App;
|
|||
use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
|
@ -151,7 +150,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
|
|||
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
|
||||
} catch (LimitException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||
throw $e;
|
||||
|
@ -195,7 +194,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
|
|||
} catch (LimitException) {
|
||||
$dbForProject->deleteDocument('attributes', $attribute->getId());
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
|
||||
throw $e;
|
||||
|
@ -487,13 +486,19 @@ App::get('/v1/databases')
|
|||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -567,7 +572,12 @@ App::get('/v1/databases/:databaseId/logs')
|
|||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
@ -809,13 +819,19 @@ App::get('/v1/databases/:databaseId/collections')
|
|||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -908,7 +924,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
|
|||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
@ -1649,7 +1670,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject) {
|
||||
|
||||
/** @var Document $database */
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
|
@ -1662,26 +1683,31 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
|||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
\array_push(
|
||||
$queries,
|
||||
Query::equal('collectionId', [$collectionId]),
|
||||
Query::equal('databaseId', [$databaseId])
|
||||
Query::equal('collectionInternalId', [$collection->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$database->getInternalId()])
|
||||
);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$attributeId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn() => $dbForProject->find('attributes', [
|
||||
Query::equal('collectionId', [$collectionId]),
|
||||
Query::equal('databaseId', [$databaseId]),
|
||||
Query::equal('collectionInternalId', [$collection->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$database->getInternalId()]),
|
||||
Query::equal('key', [$attributeId]),
|
||||
Query::limit(1),
|
||||
]));
|
||||
|
@ -2411,6 +2437,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
|
||||
$attributeType = $oldAttributes[$attributeIndex]['type'];
|
||||
$attributeSize = $oldAttributes[$attributeIndex]['size'];
|
||||
$attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false;
|
||||
|
||||
if ($attributeType === Database::VAR_RELATIONSHIP) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID, 'Cannot create an index for a relationship attribute: ' . $oldAttributes[$attributeIndex]['key']);
|
||||
|
@ -2421,8 +2448,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
throw new Exception(Exception::ATTRIBUTE_NOT_AVAILABLE, 'Attribute not available: ' . $oldAttributes[$attributeIndex]['key']);
|
||||
}
|
||||
|
||||
// set attribute size as index length only for strings
|
||||
$lengths[$i] = ($attributeType === Database::VAR_STRING) ? $attributeSize : null;
|
||||
$lengths[$i] = null;
|
||||
|
||||
if ($attributeType === Database::VAR_STRING) {
|
||||
$lengths[$i] = $attributeSize; // set attribute size as index length only for strings
|
||||
}
|
||||
|
||||
if ($attributeArray === true) {
|
||||
$lengths[$i] = Database::ARRAY_INDEX_LENGTH;
|
||||
$orders[$i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$index = new Document([
|
||||
|
@ -2491,7 +2526,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject) {
|
||||
|
||||
/** @var Document $database */
|
||||
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($database->isEmpty()) {
|
||||
|
@ -2504,10 +2539,17 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
\array_push($queries, Query::equal('collectionId', [$collectionId]), Query::equal('databaseId', [$databaseId]));
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -2516,8 +2558,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
if ($cursor) {
|
||||
$indexId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn() => $dbForProject->find('indexes', [
|
||||
Query::equal('collectionId', [$collectionId]),
|
||||
Query::equal('databaseId', [$databaseId]),
|
||||
Query::equal('collectionInternalId', [$collection->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$database->getInternalId()]),
|
||||
Query::equal('key', [$indexId]),
|
||||
Query::limit(1)
|
||||
]));
|
||||
|
@ -2912,9 +2954,15 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -2933,14 +2981,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage());
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
|
@ -3038,14 +3085,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
|||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $e->getMessage());
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
|
@ -3134,7 +3180,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
|||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
|
|
@ -12,6 +12,7 @@ use Appwrite\Utopia\Response\Model\Rule;
|
|||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
|
@ -366,13 +367,19 @@ App::get('/v1/functions')
|
|||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -1256,7 +1263,11 @@ App::get('/v1/functions/:functionId/deployments')
|
|||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
|
@ -1266,7 +1277,9 @@ App::get('/v1/functions/:functionId/deployments')
|
|||
$queries[] = Query::equal('resourceId', [$function->getId()]);
|
||||
$queries[] = Query::equal('resourceType', ['functions']);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -1794,7 +1807,11 @@ App::get('/v1/functions/:functionId/executions')
|
|||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
|
@ -1803,7 +1820,9 @@ App::get('/v1/functions/:functionId/executions')
|
|||
// Set internal queries
|
||||
$queries[] = Query::equal('functionId', [$function->getId()]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ use Appwrite\Utopia\Response;
|
|||
use Utopia\App;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
|
@ -14,8 +15,11 @@ use Utopia\Registry\Registry;
|
|||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Validator\Domain;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::get('/v1/health')
|
||||
->desc('Get HTTP')
|
||||
|
@ -355,7 +359,7 @@ App::get('/v1/health/queue/webhooks')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -382,12 +386,62 @@ App::get('/v1/health/queue/logs')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/certificate')
|
||||
->desc('Get the SSL certificate for a domain')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getCertificate')
|
||||
->label('sdk.description', '/docs/references/health/get-certificate.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_CERTIFICATE)
|
||||
->param('domain', null, new Multiple([new Domain(), new PublicDomain()]), Multiple::TYPE_STRING, 'Domain name')
|
||||
->inject('response')
|
||||
->action(function (string $domain, Response $response) {
|
||||
if (filter_var($domain, FILTER_VALIDATE_URL)) {
|
||||
$domain = parse_url($domain, PHP_URL_HOST);
|
||||
}
|
||||
|
||||
$sslContext = stream_context_create([
|
||||
"ssl" => [
|
||||
"capture_peer_cert" => true
|
||||
]
|
||||
]);
|
||||
$sslSocket = stream_socket_client("ssl://" . $domain . ":443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $sslContext);
|
||||
if (!$sslSocket) {
|
||||
throw new Exception(Exception::HEALTH_INVALID_HOST);
|
||||
}
|
||||
|
||||
$streamContextParams = stream_context_get_params($sslSocket);
|
||||
$peerCertificate = $streamContextParams['options']['ssl']['peer_certificate'];
|
||||
$certificatePayload = openssl_x509_parse($peerCertificate);
|
||||
|
||||
|
||||
$sslExpiration = $certificatePayload['validTo_time_t'];
|
||||
$status = $sslExpiration < time() ? 'fail' : 'pass';
|
||||
|
||||
if ($status === 'fail') {
|
||||
throw new Exception(Exception::HEALTH_CERTIFICATE_EXPIRED);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'name' => $certificatePayload['name'],
|
||||
'subjectSN' => $certificatePayload['subject']['CN'],
|
||||
'issuerOrganisation' => $certificatePayload['issuer']['O'],
|
||||
'validFrom' => $certificatePayload['validFrom_time_t'],
|
||||
'validTo' => $certificatePayload['validTo_time_t'],
|
||||
'signatureTypeSN' => $certificatePayload['signatureTypeSN'],
|
||||
]), Response::MODEL_HEALTH_CERTIFICATE);
|
||||
}, ['response']);
|
||||
|
||||
App::get('/v1/health/queue/certificates')
|
||||
->desc('Get certificates queue')
|
||||
->groups(['api', 'health'])
|
||||
|
@ -409,7 +463,7 @@ App::get('/v1/health/queue/certificates')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -436,7 +490,7 @@ App::get('/v1/health/queue/builds')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -464,7 +518,7 @@ App::get('/v1/health/queue/databases')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -491,7 +545,7 @@ App::get('/v1/health/queue/deletes')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -518,7 +572,7 @@ App::get('/v1/health/queue/mails')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -545,7 +599,7 @@ App::get('/v1/health/queue/messaging')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -572,7 +626,7 @@ App::get('/v1/health/queue/migrations')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -599,7 +653,7 @@ App::get('/v1/health/queue/functions')
|
|||
$size = $client->getQueueSize();
|
||||
|
||||
if ($size >= $threshold) {
|
||||
throw new Exception(Exception::QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue size threshold hit. Current size is {$size} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $size ]), Response::MODEL_HEALTH_QUEUE);
|
||||
|
@ -679,7 +733,7 @@ App::get('/v1/health/anti-virus')
|
|||
try {
|
||||
$output['version'] = @$antivirus->version();
|
||||
$output['status'] = (@$antivirus->ping()) ? 'pass' : 'fail';
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Antivirus is not available');
|
||||
}
|
||||
}
|
||||
|
@ -687,6 +741,47 @@ App::get('/v1/health/anti-virus')
|
|||
$response->dynamic(new Document($output), Response::MODEL_HEALTH_ANTIVIRUS);
|
||||
});
|
||||
|
||||
App::get('/v1/health/queue/failed/:name')
|
||||
->desc('Get number of failed queue jobs')
|
||||
->groups(['api', 'health'])
|
||||
->label('scope', 'health.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'health')
|
||||
->label('sdk.method', 'getFailedJobs')
|
||||
->param('name', '', new WhiteList([
|
||||
Event::DATABASE_QUEUE_NAME,
|
||||
Event::DELETE_QUEUE_NAME,
|
||||
Event::AUDITS_QUEUE_NAME,
|
||||
Event::MAILS_QUEUE_NAME,
|
||||
Event::FUNCTIONS_QUEUE_NAME,
|
||||
Event::USAGE_QUEUE_NAME,
|
||||
Event::WEBHOOK_CLASS_NAME,
|
||||
Event::CERTIFICATES_QUEUE_NAME,
|
||||
Event::BUILDS_QUEUE_NAME,
|
||||
Event::MESSAGING_QUEUE_NAME,
|
||||
Event::MIGRATIONS_QUEUE_NAME,
|
||||
Event::HAMSTER_CLASS_NAME
|
||||
]), 'The name of the queue')
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->label('sdk.description', '/docs/references/health/get-failed-queue-jobs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_HEALTH_QUEUE)
|
||||
->inject('response')
|
||||
->inject('queue')
|
||||
->action(function (string $name, int|string $threshold, Response $response, Connection $queue) {
|
||||
$threshold = \intval($threshold);
|
||||
|
||||
$client = new Client($name, $queue);
|
||||
$failed = $client->countFailedJobs();
|
||||
|
||||
if ($failed >= $threshold) {
|
||||
throw new Exception(Exception::HEALTH_QUEUE_SIZE_EXCEEDED, "Queue failed jobs threshold hit. Current size is {$failed} and threshold is {$threshold}.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([ 'size' => $failed ]), Response::MODEL_HEALTH_QUEUE);
|
||||
});
|
||||
|
||||
App::get('/v1/health/stats') // Currently only used internally
|
||||
->desc('Get system stats')
|
||||
->groups(['api', 'health'])
|
||||
|
|
|
@ -22,6 +22,7 @@ use Utopia\Audit\Audit;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
@ -387,13 +388,13 @@ App::post('/v1/messaging/providers/telesign')
|
|||
->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('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('username', '', new Text(0), 'Telesign username.', true)
|
||||
->param('password', '', new Text(0), 'Telesign password.', true)
|
||||
->param('customerId', '', new Text(0), 'Telesign customer ID.', true)
|
||||
->param('apiKey', '', new Text(0), 'Telesign API key.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (string $providerId, string $name, string $from, string $username, string $password, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
->action(function (string $providerId, string $name, string $from, string $customerId, string $apiKey, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$providerId = $providerId == 'unique()' ? ID::unique() : $providerId;
|
||||
|
||||
$options = [];
|
||||
|
@ -404,18 +405,18 @@ App::post('/v1/messaging/providers/telesign')
|
|||
|
||||
$credentials = [];
|
||||
|
||||
if (!empty($username)) {
|
||||
$credentials['username'] = $username;
|
||||
if (!empty($customerId)) {
|
||||
$credentials['customerId'] = $customerId;
|
||||
}
|
||||
|
||||
if (!empty($password)) {
|
||||
$credentials['password'] = $password;
|
||||
if (!empty($apiKey)) {
|
||||
$credentials['apiKey'] = $apiKey;
|
||||
}
|
||||
|
||||
if (
|
||||
$enabled === true
|
||||
&& \array_key_exists('username', $credentials)
|
||||
&& \array_key_exists('password', $credentials)
|
||||
&& \array_key_exists('customerId', $credentials)
|
||||
&& \array_key_exists('apiKey', $credentials)
|
||||
&& \array_key_exists('from', $options)
|
||||
) {
|
||||
$enabled = true;
|
||||
|
@ -837,14 +838,22 @@ App::get('/v1/messaging/providers')
|
|||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
|
@ -888,7 +897,12 @@ App::get('/v1/messaging/providers/:providerId/logs')
|
|||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
@ -1386,13 +1400,13 @@ App::patch('/v1/messaging/providers/telesign/:providerId')
|
|||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('username', '', new Text(0), 'Telesign username.', true)
|
||||
->param('password', '', new Text(0), 'Telesign password.', true)
|
||||
->param('customerId', '', new Text(0), 'Telesign customer ID.', true)
|
||||
->param('apiKey', '', new Text(0), 'Telesign API key.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (string $providerId, string $name, ?bool $enabled, string $username, string $password, string $from, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
->action(function (string $providerId, string $name, ?bool $enabled, string $customerId, string $apiKey, string $from, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
|
@ -1416,12 +1430,12 @@ App::patch('/v1/messaging/providers/telesign/:providerId')
|
|||
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
|
||||
if (!empty($username)) {
|
||||
$credentials['username'] = $username;
|
||||
if (!empty($customerId)) {
|
||||
$credentials['customerId'] = $customerId;
|
||||
}
|
||||
|
||||
if (!empty($password)) {
|
||||
$credentials['password'] = $password;
|
||||
if (!empty($apiKey)) {
|
||||
$credentials['apiKey'] = $apiKey;
|
||||
}
|
||||
|
||||
$provider->setAttribute('credentials', $credentials);
|
||||
|
@ -1429,8 +1443,8 @@ App::patch('/v1/messaging/providers/telesign/:providerId')
|
|||
if (!\is_null($enabled)) {
|
||||
if ($enabled) {
|
||||
if (
|
||||
\array_key_exists('username', $credentials) &&
|
||||
\array_key_exists('password', $credentials) &&
|
||||
\array_key_exists('customerId', $credentials) &&
|
||||
\array_key_exists('apiKey', $credentials) &&
|
||||
\array_key_exists('from', $provider->getAttribute('options'))
|
||||
) {
|
||||
$provider->setAttribute('enabled', true);
|
||||
|
@ -1944,14 +1958,22 @@ App::get('/v1/messaging/topics')
|
|||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
|
@ -1995,7 +2017,12 @@ App::get('/v1/messaging/topics/:topicId/logs')
|
|||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
@ -2258,7 +2285,11 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
|||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
|
@ -2272,8 +2303,12 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
|||
|
||||
\array_push($queries, Query::equal('topicInternalId', [$topic->getInternalId()]));
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
|
@ -2331,7 +2366,12 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
|
|||
throw new Exception(Exception::SUBSCRIBER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
@ -2845,14 +2885,22 @@ App::get('/v1/messaging/messages')
|
|||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, Database $dbForProject, Response $response) {
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
|
@ -2896,7 +2944,12 @@ App::get('/v1/messaging/messages/:messageId/logs')
|
|||
throw new Exception(Exception::MESSAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
@ -2971,9 +3024,7 @@ App::get('/v1/messaging/messages/:messageId/targets')
|
|||
->param('queries', [], new Targets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Targets::ALLOWED_ATTRIBUTES), true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->action(function (string $messageId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
|
||||
->action(function (string $messageId, array $queries, Response $response, Database $dbForProject) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
|
@ -2990,12 +3041,20 @@ App::get('/v1/messaging/messages/:messageId/targets')
|
|||
return;
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$queries[] = Query::equal('$id', $targetIDs);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
|
@ -3077,8 +3136,13 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
|||
throw new Exception(Exception::MESSAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($message->getAttribute('status') === MessageStatus::SENT) {
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_SENT);
|
||||
switch ($message->getAttribute('status')) {
|
||||
case MessageStatus::PROCESSING:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_PROCESSING);
|
||||
case MessageStatus::SENT:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_SENT);
|
||||
case MessageStatus::FAILED:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_FAILED);
|
||||
}
|
||||
|
||||
if (!\is_null($message->getAttribute('scheduledAt')) && $message->getAttribute('scheduledAt') < new \DateTime()) {
|
||||
|
@ -3136,7 +3200,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
|||
'resourceUpdatedAt' => DateTime::now(),
|
||||
'projectId' => $project->getId(),
|
||||
'schedule' => $scheduledAt,
|
||||
'active' => $status === 'processing',
|
||||
'active' => $status === MessageStatus::SCHEDULED,
|
||||
]));
|
||||
|
||||
$message->setAttribute('scheduleId', $schedule->getId());
|
||||
|
@ -3150,7 +3214,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
|||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $scheduledAt)
|
||||
->setAttribute('active', $status === 'processing');
|
||||
->setAttribute('active', $status === MessageStatus::SCHEDULED);
|
||||
|
||||
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
}
|
||||
|
@ -3207,8 +3271,13 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
|||
throw new Exception(Exception::MESSAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($message->getAttribute('status') === 'sent') {
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_SENT);
|
||||
switch ($message->getAttribute('status')) {
|
||||
case MessageStatus::PROCESSING:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_PROCESSING);
|
||||
case MessageStatus::SENT:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_SENT);
|
||||
case MessageStatus::FAILED:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_FAILED);
|
||||
}
|
||||
|
||||
if (!is_null($message->getAttribute('scheduledAt')) && $message->getAttribute('scheduledAt') < new \DateTime()) {
|
||||
|
@ -3250,7 +3319,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
|||
'resourceUpdatedAt' => DateTime::now(),
|
||||
'projectId' => $project->getId(),
|
||||
'schedule' => $scheduledAt,
|
||||
'active' => $status === 'processing',
|
||||
'active' => $status === MessageStatus::SCHEDULED,
|
||||
]));
|
||||
|
||||
$message->setAttribute('scheduleId', $schedule->getId());
|
||||
|
@ -3264,7 +3333,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
|||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $scheduledAt)
|
||||
->setAttribute('active', $status === 'processing');
|
||||
->setAttribute('active', $status === MessageStatus::SCHEDULED);
|
||||
|
||||
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
}
|
||||
|
@ -3274,7 +3343,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
|||
|
||||
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
|
||||
if ($status === 'processing' && \is_null($message->getAttribute('scheduledAt'))) {
|
||||
if ($status === MessageStatus::PROCESSING) {
|
||||
$queueForMessaging
|
||||
->setMessageId($message->getId())
|
||||
->trigger();
|
||||
|
@ -3329,8 +3398,13 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
|||
throw new Exception(Exception::MESSAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($message->getAttribute('status') === 'sent') {
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_SENT);
|
||||
switch ($message->getAttribute('status')) {
|
||||
case MessageStatus::PROCESSING:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_PROCESSING);
|
||||
case MessageStatus::SENT:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_SENT);
|
||||
case MessageStatus::FAILED:
|
||||
throw new Exception(Exception::MESSAGE_ALREADY_FAILED);
|
||||
}
|
||||
|
||||
if (!is_null($message->getAttribute('scheduledAt')) && $message->getAttribute('scheduledAt') < new \DateTime()) {
|
||||
|
@ -3404,7 +3478,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
|||
'resourceUpdatedAt' => DateTime::now(),
|
||||
'projectId' => $project->getId(),
|
||||
'schedule' => $scheduledAt,
|
||||
'active' => $status === 'processing',
|
||||
'active' => $status === MessageStatus::SCHEDULED,
|
||||
]));
|
||||
|
||||
$message->setAttribute('scheduleId', $schedule->getId());
|
||||
|
@ -3418,7 +3492,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
|||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $scheduledAt)
|
||||
->setAttribute('active', $status === 'processing');
|
||||
->setAttribute('active', $status === MessageStatus::SCHEDULED);
|
||||
|
||||
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
}
|
||||
|
@ -3428,7 +3502,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
|||
|
||||
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
|
||||
if ($status === 'processing' && \is_null($message->getAttribute('scheduledAt'))) {
|
||||
if ($status === MessageStatus::PROCESSING) {
|
||||
$queueForMessaging
|
||||
->setMessageId($message->getId())
|
||||
->trigger();
|
||||
|
|
|
@ -14,6 +14,7 @@ use Utopia\App;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\UID;
|
||||
|
@ -384,13 +385,19 @@ App::get('/v1/migrations')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -610,7 +617,7 @@ App::get('/v1/migrations/firebase/report/oauth')
|
|||
|
||||
try {
|
||||
$report = $firebase->report($resources);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
|
@ -822,7 +829,7 @@ App::get('/v1/migrations/firebase/projects')
|
|||
if ($isExpired) {
|
||||
try {
|
||||
$firebase->refreshTokens($refreshToken);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
@ -852,7 +859,7 @@ App::get('/v1/migrations/firebase/projects')
|
|||
'projectId' => $project['projectId'],
|
||||
];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,20 +18,18 @@ use Utopia\Audit\Audit;
|
|||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
|
@ -241,13 +239,19 @@ App::get('/v1/projects')
|
|||
->inject('dbForConsole')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForConsole) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
|
|
@ -10,10 +10,12 @@ use Appwrite\Utopia\Response;
|
|||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Validator\Domain as ValidatorDomain;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
@ -89,7 +91,7 @@ App::post('/v1/proxy/rules')
|
|||
|
||||
try {
|
||||
$domain = new Domain($domain);
|
||||
} catch (\Exception) {
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
|
@ -155,7 +157,11 @@ App::get('/v1/proxy/rules')
|
|||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForConsole) {
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
|
@ -163,8 +169,12 @@ App::get('/v1/proxy/rules')
|
|||
|
||||
$queries[] = Query::equal('projectInternalId', [$project->getInternalId()]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
@ -278,7 +288,8 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
|
|||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForConsole) {
|
||||
->inject('log')
|
||||
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForConsole, Log $log) {
|
||||
$rule = $dbForConsole->getDocument('rules', $ruleId);
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
|
||||
|
@ -298,7 +309,14 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
|
|||
$validator = new CNAME($target->get()); // Verify Domain with DNS records
|
||||
$domain = new Domain($rule->getAttribute('domain', ''));
|
||||
|
||||
$validationStart = \microtime(true);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
||||
$error = $validator->getLogs();
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
|
||||
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,10 @@ use Utopia\App;
|
|||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
|
@ -42,7 +42,6 @@ use Utopia\Validator\HexColor;
|
|||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Swoole\Request;
|
||||
use Utopia\Storage\Compression\Compression;
|
||||
|
||||
|
@ -161,13 +160,19 @@ App::get('/v1/storage/buckets')
|
|||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -737,13 +742,19 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ use Appwrite\Event\Mail;
|
|||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Validator\Host;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
|
@ -146,13 +147,19 @@ App::get('/v1/teams')
|
|||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -534,8 +541,8 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
|
||||
}
|
||||
$team->setAttribute('total', $team->getAttribute('total', 0) + 1);
|
||||
$team = Authorization::skip(fn() => $dbForProject->updateDocument('teams', $team->getId(), $team));
|
||||
|
||||
Authorization::skip(fn() => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $invitee->getId());
|
||||
} else {
|
||||
|
@ -699,7 +706,11 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
|
@ -708,7 +719,9 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
// Set internal queries
|
||||
$queries[] = Query::equal('teamId', [$teamId]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -1055,7 +1068,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
|||
$dbForProject->deleteDocument('memberships', $membership->getId());
|
||||
} catch (AuthorizationException $exception) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (\Exception $exception) {
|
||||
} catch (\Throwable $exception) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove membership from DB');
|
||||
}
|
||||
|
||||
|
@ -1100,7 +1113,12 @@ App::get('/v1/teams/:teamId/logs')
|
|||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
|
|
@ -11,6 +11,7 @@ use Appwrite\Network\Validator\Email;
|
|||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Identities;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Targets;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
|
@ -536,13 +537,19 @@ App::get('/v1/users')
|
|||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
@ -756,7 +763,12 @@ App::get('/v1/users/:userId/logs')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
@ -834,13 +846,21 @@ App::get('/v1/users/:userId/targets')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$queries[] = Query::equal('userId', [$userId]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
$cursor = reset($cursor);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$targetId = $cursor->getValue();
|
||||
|
@ -876,13 +896,19 @@ App::get('/v1/users/identities')
|
|||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ use Appwrite\Auth\OAuth2\Github as OAuth2Github;
|
|||
use Utopia\App;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Delete;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
@ -969,7 +970,11 @@ App::get('/v1/vcs/installations')
|
|||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForConsole) {
|
||||
$queries = Query::parseQueries($queries);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$queries[] = Query::equal('projectInternalId', [$project->getInternalId()]);
|
||||
|
||||
|
@ -977,8 +982,12 @@ App::get('/v1/vcs/installations')
|
|||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
|
|
@ -617,66 +617,58 @@ App::error()
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('logger')
|
||||
->inject('loggerBreadcrumbs')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, array $loggerBreadcrumbs) {
|
||||
|
||||
->inject('log')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log) {
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$publish = true;
|
||||
|
||||
if ($error instanceof AppwriteException) {
|
||||
$publish = $error->isPublishable();
|
||||
} else {
|
||||
$publish = $error->getCode() === 0 || $error->getCode() >= 500;
|
||||
}
|
||||
|
||||
if ($logger && $publish) {
|
||||
if ($error->getCode() >= 500 || $error->getCode() === 0) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
} catch (\Throwable $th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$log = new Utopia\Logger\Log();
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('database', $project->getAttribute('database', 'console'));
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('projectId', $project->getId());
|
||||
$log->addTag('hostname', $request->getHostname());
|
||||
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
|
||||
$log->addBreadcrumb($loggerBreadcrumb);
|
||||
}
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
if ($logger && ($publish || $error->getCode() === 0)) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
} catch (\Throwable $th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('database', $project->getAttribute('database', 'console'));
|
||||
$log->addTag('method', $route->getMethod());
|
||||
$log->addTag('url', $route->getPath());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('projectId', $project->getId());
|
||||
$log->addTag('hostname', $request->getHostname());
|
||||
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('detailedTrace', $error->getTrace());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
||||
$code = $error->getCode();
|
||||
|
|
|
@ -179,6 +179,7 @@ App::init()
|
|||
$end = $request->getContentRangeEnd();
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit
|
||||
->setParam('{projectId}', $project->getId())
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
|
@ -350,7 +351,7 @@ App::init()
|
|||
break;
|
||||
|
||||
case 'magic-url':
|
||||
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||
if (($auths['usersAuthMagicURL'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
@ -361,6 +362,12 @@ App::init()
|
|||
}
|
||||
break;
|
||||
|
||||
case 'phone':
|
||||
if (($auths['phone'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invites':
|
||||
if (($auths['invites'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
|
||||
|
@ -373,12 +380,6 @@ App::init()
|
|||
}
|
||||
break;
|
||||
|
||||
case 'phone':
|
||||
if (($auths['phone'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'email-otp':
|
||||
if (($auths['emailOTP'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email OTP authentication is disabled for this project');
|
||||
|
@ -386,8 +387,7 @@ App::init()
|
|||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route');
|
||||
break;
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication type: ' . $route->getLabel('auth.type', ''));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ App::init()
|
|||
break;
|
||||
|
||||
case 'magic-url':
|
||||
if ($project->getAttribute('usersAuthMagicURL', true) === false) {
|
||||
if (($auths['usersAuthMagicURL'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Magic URL authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
@ -54,6 +54,12 @@ App::init()
|
|||
}
|
||||
break;
|
||||
|
||||
case 'phone':
|
||||
if (($auths['phone'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'invites':
|
||||
if (($auths['invites'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Invites authentication is disabled for this project');
|
||||
|
@ -66,12 +72,6 @@ App::init()
|
|||
}
|
||||
break;
|
||||
|
||||
case 'phone':
|
||||
if (($auths['phone'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Phone authentication is disabled for this project');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'email-otp':
|
||||
if (($auths['emailOTP'] ?? true) === false) {
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Email OTP authentication is disabled for this project');
|
||||
|
@ -80,6 +80,5 @@ App::init()
|
|||
|
||||
default:
|
||||
throw new Exception(Exception::USER_AUTH_METHOD_UNSUPPORTED, 'Unsupported authentication route');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
|
11
app/http.php
11
app/http.php
|
@ -77,7 +77,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
$dbForConsole = $app->getResource('dbForConsole');
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
break; // leave the do-while if successful
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
if ($attempts >= $max) {
|
||||
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
|
||||
|
@ -91,7 +91,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
try {
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
|
@ -264,10 +264,9 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$loggerBreadcrumbs = $app->getResource("loggerBreadcrumbs");
|
||||
$route = $app->getRoute();
|
||||
|
||||
$log = new Utopia\Logger\Log();
|
||||
$log = $app->getResource("log");
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
|
@ -299,10 +298,6 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
|||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
foreach ($loggerBreadcrumbs as $loggerBreadcrumb) {
|
||||
$log->addBreadcrumb($loggerBreadcrumb);
|
||||
}
|
||||
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Log pushed with status code: ' . $responseCode);
|
||||
}
|
||||
|
|
10
app/init.php
10
app/init.php
|
@ -35,6 +35,7 @@ use Appwrite\OpenSSL\OpenSSL;
|
|||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Adapter\SQL;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
|
@ -71,6 +72,7 @@ use Appwrite\Hooks\Hooks;
|
|||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Database\PDOProxy;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Queue;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Storage\Storage;
|
||||
|
@ -201,6 +203,7 @@ const MESSAGE_TYPE_PUSH = 'push';
|
|||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
const METRIC_MESSAGES = 'messages';
|
||||
const METRIC_SESSIONS = 'sessions';
|
||||
const METRIC_DATABASES = 'databases';
|
||||
const METRIC_COLLECTIONS = 'collections';
|
||||
|
@ -986,6 +989,7 @@ foreach ($locales as $locale) {
|
|||
]);
|
||||
|
||||
// Runtime Execution
|
||||
App::setResource('log', fn() => new Log());
|
||||
App::setResource('logger', function ($register) {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
|
@ -994,10 +998,6 @@ App::setResource('hooks', function ($register) {
|
|||
return $register->get('hooks');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('loggerBreadcrumbs', function () {
|
||||
return [];
|
||||
});
|
||||
|
||||
App::setResource('register', fn() => $register);
|
||||
App::setResource('locale', fn() => new Locale(App::getEnv('_APP_LOCALE', 'en')));
|
||||
|
||||
|
@ -1410,7 +1410,7 @@ function getDevice($root): Device
|
|||
$accessSecret = $dsn->getPassword() ?? '';
|
||||
$bucket = $dsn->getPath() ?? '';
|
||||
$region = $dsn->getParam('region');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.');
|
||||
}
|
||||
|
||||
|
|
|
@ -143,6 +143,7 @@ services:
|
|||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_MAINTENANCE_INTERVAL
|
||||
- _APP_MAINTENANCE_DELAY
|
||||
- _APP_MAINTENANCE_RETENTION_EXECUTION
|
||||
- _APP_MAINTENANCE_RETENTION_CACHE
|
||||
- _APP_MAINTENANCE_RETENTION_ABUSE
|
||||
|
|
|
@ -246,7 +246,7 @@ try {
|
|||
'workerName' => strtolower($workerName) ?? null,
|
||||
'queueName' => $queueName
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
|
||||
}
|
||||
|
||||
|
|
3
bin/queue-count-failed
Normal file
3
bin/queue-count-failed
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=failed $@
|
3
bin/queue-count-processing
Normal file
3
bin/queue-count-processing
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=processing $@
|
3
bin/queue-count-success
Normal file
3
bin/queue-count-success
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-count --type=success $@
|
3
bin/queue-retry
Normal file
3
bin/queue-retry
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php queue-retry $@
|
|
@ -62,7 +62,7 @@
|
|||
"utopia-php/platform": "0.5.*",
|
||||
"utopia-php/pools": "0.4.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.6.*",
|
||||
"utopia-php/queue": "0.7.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.18.*",
|
||||
"utopia-php/swoole": "0.8.*",
|
||||
|
@ -75,7 +75,7 @@
|
|||
"adhocore/jwt": "1.1.2",
|
||||
"spomky-labs/otphp": "^10.0",
|
||||
"webonyx/graphql-php": "14.11.*",
|
||||
"league/csv": "^9.14"
|
||||
"league/csv": "9.14.*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
|
|
71
composer.lock
generated
71
composer.lock
generated
|
@ -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": "e6e0d6874f4d718d96396d71a66864da",
|
||||
"content-hash": "55d2ed1081591f2b3b3af9999236b109",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
@ -1029,16 +1029,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1046,9 +1046,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -1092,7 +1089,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1108,7 +1105,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "thecodingmachine/safe",
|
||||
|
@ -1751,16 +1748,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/image.git",
|
||||
"reference": "88f7209172bdabd81e76ac981c95fac117dc6e08"
|
||||
"reference": "2d74c27e69e65a93cf94a16586598a04fe435bf0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/image/zipball/88f7209172bdabd81e76ac981c95fac117dc6e08",
|
||||
"reference": "88f7209172bdabd81e76ac981c95fac117dc6e08",
|
||||
"url": "https://api.github.com/repos/utopia-php/image/zipball/2d74c27e69e65a93cf94a16586598a04fe435bf0",
|
||||
"reference": "2d74c27e69e65a93cf94a16586598a04fe435bf0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1793,9 +1790,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/image/issues",
|
||||
"source": "https://github.com/utopia-php/image/tree/0.6.0"
|
||||
"source": "https://github.com/utopia-php/image/tree/0.6.1"
|
||||
},
|
||||
"time": "2024-01-24T06:59:44+00:00"
|
||||
"time": "2024-02-05T13:31:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/locale",
|
||||
|
@ -2264,16 +2261,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/queue",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/queue.git",
|
||||
"reference": "0120bd21904cb2bee34e4571b1737589ffff0eb1"
|
||||
"reference": "917565256eb94bcab7246f7a746b1a486813761b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/0120bd21904cb2bee34e4571b1737589ffff0eb1",
|
||||
"reference": "0120bd21904cb2bee34e4571b1737589ffff0eb1",
|
||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/917565256eb94bcab7246f7a746b1a486813761b",
|
||||
"reference": "917565256eb94bcab7246f7a746b1a486813761b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2319,9 +2316,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/queue/issues",
|
||||
"source": "https://github.com/utopia-php/queue/tree/0.6.0"
|
||||
"source": "https://github.com/utopia-php/queue/tree/0.7.0"
|
||||
},
|
||||
"time": "2023-10-16T16:59:45+00:00"
|
||||
"time": "2024-01-17T19:00:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/registry",
|
||||
|
@ -5123,16 +5120,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -5146,9 +5143,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -5185,7 +5179,7 @@
|
|||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -5201,20 +5195,20 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-26T09:26:14+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.28.0",
|
||||
"version": "v1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "42292d99c55abe617799667f454222c54c60e229"
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
|
||||
"reference": "42292d99c55abe617799667f454222c54c60e229",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -5228,9 +5222,6 @@
|
|||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.28-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
|
@ -5268,7 +5259,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -5284,7 +5275,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-28T09:04:16+00:00"
|
||||
"time": "2024-01-29T20:11:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "textalk/websocket",
|
||||
|
|
|
@ -582,6 +582,7 @@ services:
|
|||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_SMS_PROJECTS_DENY_LIST
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
|
@ -662,6 +663,7 @@ services:
|
|||
- _APP_MAINTENANCE_RETENTION_AUDIT
|
||||
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
|
||||
- _APP_MAINTENANCE_RETENTION_SCHEDULES
|
||||
- _APP_MAINTENANCE_DELAY
|
||||
|
||||
appwrite-worker-usage:
|
||||
entrypoint: worker-usage
|
||||
|
|
1
docs/references/health/get-certificate.md
Normal file
1
docs/references/health/get-certificate.md
Normal file
|
@ -0,0 +1 @@
|
|||
Get the SSL certificate for a domain
|
1
docs/references/health/get-failed-queue-jobs.md
Normal file
1
docs/references/health/get-failed-queue-jobs.md
Normal file
|
@ -0,0 +1 @@
|
|||
Returns the amount of failed jobs in a given queue.
|
|
@ -279,7 +279,7 @@ class Firebase extends OAuth2
|
|||
$role = \json_decode($role, true);
|
||||
|
||||
return $role;
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
if ($e->getCode() !== 404) {
|
||||
throw $e;
|
||||
}
|
||||
|
|
|
@ -244,7 +244,9 @@ class Exception extends \Exception
|
|||
public const REALTIME_POLICY_VIOLATION = 'realtime_policy_violation';
|
||||
|
||||
/** Health */
|
||||
public const QUEUE_SIZE_EXCEEDED = 'queue_size_exceeded';
|
||||
public const HEALTH_QUEUE_SIZE_EXCEEDED = 'health_queue_size_exceeded';
|
||||
public const HEALTH_CERTIFICATE_EXPIRED = 'health_certificate_expired';
|
||||
public const HEALTH_INVALID_HOST = 'health_invalid_host';
|
||||
|
||||
/** Provider */
|
||||
public const PROVIDER_NOT_FOUND = 'provider_not_found';
|
||||
|
@ -264,6 +266,8 @@ class Exception extends \Exception
|
|||
public const MESSAGE_NOT_FOUND = 'message_not_found';
|
||||
public const MESSAGE_MISSING_TARGET = 'message_missing_target';
|
||||
public const MESSAGE_ALREADY_SENT = 'message_already_sent';
|
||||
public const MESSAGE_ALREADY_PROCESSING = 'message_already_processing';
|
||||
public const MESSAGE_ALREADY_FAILED = 'message_already_failed';
|
||||
public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled';
|
||||
public const MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email';
|
||||
public const MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms';
|
||||
|
@ -276,21 +280,16 @@ class Exception extends \Exception
|
|||
|
||||
protected string $type = '';
|
||||
protected array $errors = [];
|
||||
protected bool $publish = true;
|
||||
protected bool $publish;
|
||||
|
||||
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
|
||||
{
|
||||
$this->errors = Config::getParam('errors');
|
||||
$this->type = $type;
|
||||
$this->code = $code ?? $this->errors[$type]['code'];
|
||||
$this->message = $message ?? $this->errors[$type]['description'];
|
||||
|
||||
if (isset($this->errors[$type])) {
|
||||
$this->code = $this->errors[$type]['code'];
|
||||
$this->message = $this->errors[$type]['description'];
|
||||
$this->publish = $this->errors[$type]['publish'] ?? true;
|
||||
}
|
||||
|
||||
$this->message = $message ?? $this->message;
|
||||
$this->code = $code ?? $this->code;
|
||||
$this->publish = $this->errors[$type]['publish'] ?? ($this->code >= 500);
|
||||
|
||||
parent::__construct($this->message, $this->code, $previous);
|
||||
}
|
||||
|
|
|
@ -401,7 +401,7 @@ abstract class Migration
|
|||
|
||||
try {
|
||||
$stmt->execute();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,11 @@ use Utopia\Validator;
|
|||
|
||||
class CNAME extends Validator
|
||||
{
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected mixed $logs;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -27,6 +32,14 @@ class CNAME extends Validator
|
|||
return 'Invalid CNAME record';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLogs(): mixed
|
||||
{
|
||||
return $this->logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CNAME record target value matches selected target
|
||||
*
|
||||
|
@ -42,6 +55,7 @@ class CNAME extends Validator
|
|||
|
||||
try {
|
||||
$records = \dns_get_record($domain, DNS_CNAME);
|
||||
$this->logs = $records;
|
||||
} catch (\Throwable $th) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Appwrite\Platform\Services;
|
||||
|
||||
use Appwrite\Platform\Tasks\CalcTierStats;
|
||||
use Appwrite\Platform\Tasks\CreateInfMetric;
|
||||
use Appwrite\Platform\Tasks\DeleteOrphanedProjects;
|
||||
use Appwrite\Platform\Tasks\DevGenerateTranslations;
|
||||
use Appwrite\Platform\Tasks\Doctor;
|
||||
|
@ -12,6 +13,8 @@ use Appwrite\Platform\Tasks\Install;
|
|||
use Appwrite\Platform\Tasks\Maintenance;
|
||||
use Appwrite\Platform\Tasks\Migrate;
|
||||
use Appwrite\Platform\Tasks\PatchRecreateRepositoriesDocuments;
|
||||
use Appwrite\Platform\Tasks\QueueCount;
|
||||
use Appwrite\Platform\Tasks\QueueRetry;
|
||||
use Appwrite\Platform\Tasks\SDKs;
|
||||
use Appwrite\Platform\Tasks\SSL;
|
||||
use Appwrite\Platform\Tasks\ScheduleFunctions;
|
||||
|
@ -21,7 +24,6 @@ use Appwrite\Platform\Tasks\Upgrade;
|
|||
use Appwrite\Platform\Tasks\Vars;
|
||||
use Appwrite\Platform\Tasks\Version;
|
||||
use Appwrite\Platform\Tasks\VolumeSync;
|
||||
use Appwrite\Platform\Tasks\CreateInfMetric;
|
||||
use Utopia\Platform\Service;
|
||||
|
||||
class Tasks extends Service
|
||||
|
@ -30,19 +32,8 @@ class Tasks extends Service
|
|||
{
|
||||
$this->type = self::TYPE_CLI;
|
||||
$this
|
||||
->addAction(Version::getName(), new Version())
|
||||
->addAction(Vars::getName(), new Vars())
|
||||
->addAction(SSL::getName(), new SSL())
|
||||
->addAction(Hamster::getName(), new Hamster())
|
||||
->addAction(Doctor::getName(), new Doctor())
|
||||
->addAction(Install::getName(), new Install())
|
||||
->addAction(Upgrade::getName(), new Upgrade())
|
||||
->addAction(Maintenance::getName(), new Maintenance())
|
||||
->addAction(Migrate::getName(), new Migrate())
|
||||
->addAction(SDKs::getName(), new SDKs())
|
||||
->addAction(VolumeSync::getName(), new VolumeSync())
|
||||
->addAction(Specs::getName(), new Specs())
|
||||
->addAction(CalcTierStats::getName(), new CalcTierStats())
|
||||
->addAction(CreateInfMetric::getName(), new CreateInfMetric())
|
||||
->addAction(DeleteOrphanedProjects::getName(), new DeleteOrphanedProjects())
|
||||
->addAction(DevGenerateTranslations::getName(), new DevGenerateTranslations())
|
||||
->addAction(Doctor::getName(), new Doctor())
|
||||
|
@ -51,7 +42,10 @@ class Tasks extends Service
|
|||
->addAction(Install::getName(), new Install())
|
||||
->addAction(Maintenance::getName(), new Maintenance())
|
||||
->addAction(Migrate::getName(), new Migrate())
|
||||
->addAction(Migrate::getName(), new Migrate())
|
||||
->addAction(PatchRecreateRepositoriesDocuments::getName(), new PatchRecreateRepositoriesDocuments())
|
||||
->addAction(QueueCount::getName(), new QueueCount())
|
||||
->addAction(QueueRetry::getName(), new QueueRetry())
|
||||
->addAction(SDKs::getName(), new SDKs())
|
||||
->addAction(SSL::getName(), new SSL())
|
||||
->addAction(ScheduleFunctions::getName(), new ScheduleFunctions())
|
||||
|
@ -61,7 +55,6 @@ class Tasks extends Service
|
|||
->addAction(Vars::getName(), new Vars())
|
||||
->addAction(Version::getName(), new Version())
|
||||
->addAction(VolumeSync::getName(), new VolumeSync())
|
||||
->addAction(CreateInfMetric::getName(), new CreateInfMetric())
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Exception;
|
||||
use League\Csv\CannotInsertRecord;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
|
@ -200,7 +199,7 @@ class CalcTierStats extends Action
|
|||
$mail->Body = "Please find the daily cloud report atttached";
|
||||
$mail->send();
|
||||
Console::success('Email has been sent!');
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Exception;
|
||||
use League\Csv\CannotInsertRecord;
|
||||
use Utopia\App;
|
||||
use Utopia\Platform\Action;
|
||||
|
@ -180,7 +179,7 @@ class GetMigrationStats extends Action
|
|||
$mail->Body = "Please find the migration report atttached";
|
||||
$mail->send();
|
||||
Console::success('Email has been sent!');
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Hamster as EventHamster;
|
||||
use Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\CLI\Console;
|
||||
|
@ -120,7 +119,7 @@ class Hamster extends Action
|
|||
->setType(EventHamster::TYPE_ORGANISATION)
|
||||
->setOrganization($organization)
|
||||
->trigger();
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
});
|
||||
|
@ -135,7 +134,7 @@ class Hamster extends Action
|
|||
->setType(EventHamster::TYPE_PROJECT)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
});
|
||||
|
@ -150,7 +149,7 @@ class Hamster extends Action
|
|||
->setType(EventHamster::TYPE_USER)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
});
|
||||
|
|
|
@ -36,6 +36,7 @@ class Maintenance extends Action
|
|||
|
||||
// # of days in seconds (1 day = 86400s)
|
||||
$interval = (int) App::getEnv('_APP_MAINTENANCE_INTERVAL', '86400');
|
||||
$delay = (int) App::getEnv('_APP_MAINTENANCE_DELAY', '0');
|
||||
$usageStatsRetentionHourly = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_HOURLY', '8640000'); //100 days
|
||||
$cacheRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days
|
||||
$schedulesDeletionRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_SCHEDULES', '86400'); // 1 Day
|
||||
|
@ -60,7 +61,7 @@ class Maintenance extends Action
|
|||
$this->notifyDeleteCache($cacheRetention, $queueForDeletes);
|
||||
$this->notifyDeleteSchedules($schedulesDeletionRetention, $queueForDeletes);
|
||||
$this->notifyDeleteTargets($queueForDeletes);
|
||||
}, $interval);
|
||||
}, $interval, $delay);
|
||||
}
|
||||
|
||||
protected function foreachProject(Database $dbForConsole, callable $callback): void
|
||||
|
|
70
src/Appwrite/Platform/Tasks/QueueCount.php
Normal file
70
src/Appwrite/Platform/Tasks/QueueCount.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class QueueCount extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'queue-count';
|
||||
}
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Return the number of from a specific queue identified by the name parameter with a specific type')
|
||||
->param('name', '', new WhiteList([
|
||||
Event::DATABASE_QUEUE_NAME,
|
||||
Event::DELETE_QUEUE_NAME,
|
||||
Event::AUDITS_QUEUE_NAME,
|
||||
Event::MAILS_QUEUE_NAME,
|
||||
Event::FUNCTIONS_QUEUE_NAME,
|
||||
Event::USAGE_QUEUE_NAME,
|
||||
Event::WEBHOOK_QUEUE_NAME,
|
||||
Event::CERTIFICATES_QUEUE_NAME,
|
||||
Event::BUILDS_QUEUE_NAME,
|
||||
Event::MESSAGING_QUEUE_NAME,
|
||||
Event::MIGRATIONS_QUEUE_NAME,
|
||||
Event::HAMSTER_QUEUE_NAME
|
||||
]), 'Queue name')
|
||||
->param('type', '', new WhiteList([
|
||||
'success',
|
||||
'failed',
|
||||
'processing',
|
||||
]), 'Queue type')
|
||||
->inject('queue')
|
||||
->callback(fn ($name, $type, $queue) => $this->action($name, $type, $queue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name The name of the queue to count the jobs from
|
||||
* @param string $type The type of jobs to count
|
||||
* @param Connection $queue
|
||||
*/
|
||||
public function action(string $name, string $type, Connection $queue): void
|
||||
{
|
||||
if (!$name) {
|
||||
Console::error('Missing required parameter $name');
|
||||
return;
|
||||
}
|
||||
|
||||
$queueClient = new Client($name, $queue);
|
||||
|
||||
$count = match ($type) {
|
||||
'success' => $queueClient->countSuccessfulJobs(),
|
||||
'failed' => $queueClient->countFailedJobs(),
|
||||
'processing' => $queueClient->countProcessingJobs(),
|
||||
default => 0
|
||||
};
|
||||
|
||||
Console::log("Queue: '{$name}' has {$count} {$type} jobs.");
|
||||
}
|
||||
}
|
64
src/Appwrite/Platform/Tasks/QueueRetry.php
Normal file
64
src/Appwrite/Platform/Tasks/QueueRetry.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class QueueRetry extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'queue-retry';
|
||||
}
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Retry failed jobs from a specific queue identified by the name parameter')
|
||||
->param('name', '', new WhiteList([
|
||||
Event::DATABASE_QUEUE_NAME,
|
||||
Event::DELETE_QUEUE_NAME,
|
||||
Event::AUDITS_QUEUE_NAME,
|
||||
Event::MAILS_QUEUE_NAME,
|
||||
Event::FUNCTIONS_QUEUE_NAME,
|
||||
Event::USAGE_QUEUE_NAME,
|
||||
Event::WEBHOOK_CLASS_NAME,
|
||||
Event::CERTIFICATES_QUEUE_NAME,
|
||||
Event::BUILDS_QUEUE_NAME,
|
||||
Event::MESSAGING_QUEUE_NAME,
|
||||
Event::MIGRATIONS_QUEUE_NAME,
|
||||
Event::HAMSTER_CLASS_NAME
|
||||
]), 'Queue name')
|
||||
->inject('queue')
|
||||
->callback(fn ($name, $queue) => $this->action($name, $queue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name The name of the queue to retry jobs from
|
||||
* @param Connection $queue
|
||||
*/
|
||||
public function action(string $name, Connection $queue): void
|
||||
{
|
||||
if (!$name) {
|
||||
Console::error('Missing required parameter $name');
|
||||
return;
|
||||
}
|
||||
|
||||
$queueClient = new Client($name, $queue);
|
||||
|
||||
if ($queueClient->countFailedJobs() === 0) {
|
||||
Console::error('No failed jobs found.');
|
||||
return;
|
||||
}
|
||||
|
||||
Console::log('Retrying failed jobs...');
|
||||
|
||||
$queueClient->retry();
|
||||
}
|
||||
}
|
|
@ -18,8 +18,6 @@ use Appwrite\SDK\Language\Python;
|
|||
use Appwrite\SDK\Language\REST;
|
||||
use Appwrite\SDK\Language\Ruby;
|
||||
use Appwrite\SDK\Language\Swift;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use Appwrite\SDK\Language\Apple;
|
||||
use Appwrite\SDK\Language\Web;
|
||||
use Appwrite\SDK\SDK;
|
||||
|
@ -242,9 +240,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
|
||||
try {
|
||||
$sdk->generate($result);
|
||||
} catch (Exception $exception) {
|
||||
Console::error($exception->getMessage());
|
||||
} catch (Throwable $exception) {
|
||||
} catch (\Throwable $exception) {
|
||||
Console::error($exception->getMessage());
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use Appwrite\Event\Certificate;
|
|||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
|
||||
class SSL extends Action
|
||||
|
@ -21,19 +22,22 @@ class SSL extends Action
|
|||
$this
|
||||
->desc('Validate server certificates')
|
||||
->param('domain', App::getEnv('_APP_DOMAIN', ''), new Hostname(), 'Domain to generate certificate for. If empty, main domain will be used.', true)
|
||||
->param('skip-check', true, new Boolean(true), 'If DNS and renew check should be skipped. Defaults to true, and when true, all jobs will result in certificate generation attempt.', true)
|
||||
->inject('queueForCertificates')
|
||||
->callback(fn (string $domain, Certificate $queueForCertificates) => $this->action($domain, $queueForCertificates));
|
||||
->callback(fn (string $domain, bool|string $skipCheck, Certificate $queueForCertificates) => $this->action($domain, $skipCheck, $queueForCertificates));
|
||||
}
|
||||
|
||||
public function action(string $domain, Certificate $queueForCertificates): void
|
||||
public function action(string $domain, bool|string $skipCheck, Certificate $queueForCertificates): void
|
||||
{
|
||||
$skipCheck = \strval($skipCheck) === 'true';
|
||||
|
||||
Console::success('Scheduling a job to issue a TLS certificate for domain: ' . $domain);
|
||||
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $domain
|
||||
]))
|
||||
->setSkipRenewCheck(true)
|
||||
->setSkipRenewCheck($skipCheck)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -264,7 +264,7 @@ class Specs extends Action
|
|||
$formatInstance
|
||||
->setParam('name', APP_NAME)
|
||||
->setParam('description', 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)')
|
||||
->setParam('endpoint', 'https://HOSTNAME/v1')
|
||||
->setParam('endpoint', 'https://cloud.appwrite.io/v1')
|
||||
->setParam('version', APP_VERSION_STABLE)
|
||||
->setParam('terms', $endpoint . '/policy/terms')
|
||||
->setParam('support.email', $email)
|
||||
|
|
|
@ -8,7 +8,6 @@ use Appwrite\Event\Usage;
|
|||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Utopia\Response\Model\Deployment;
|
||||
use Appwrite\Vcs\Comment;
|
||||
use Exception;
|
||||
use Swoole\Coroutine as Co;
|
||||
use Executor\Executor;
|
||||
use Utopia\App;
|
||||
|
@ -420,7 +419,7 @@ class Builds extends Action
|
|||
variables: $vars,
|
||||
command: $command
|
||||
);
|
||||
} catch (Exception $error) {
|
||||
} catch (\Throwable $error) {
|
||||
$err = $error;
|
||||
}
|
||||
}),
|
||||
|
@ -459,7 +458,7 @@ class Builds extends Action
|
|||
}
|
||||
}
|
||||
);
|
||||
} catch (Exception $error) {
|
||||
} catch (\Throwable $error) {
|
||||
if (empty($err)) {
|
||||
$err = $error;
|
||||
}
|
||||
|
@ -617,7 +616,7 @@ class Builds extends Action
|
|||
'$id' => $commentId
|
||||
]));
|
||||
break;
|
||||
} catch (Exception $err) {
|
||||
} catch (\Throwable $err) {
|
||||
if ($retries >= 9) {
|
||||
throw $err;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ class Certificates extends Action
|
|||
|
||||
$log->addTag('domain', $domain->get());
|
||||
|
||||
$this->execute($domain, $dbForConsole, $queueForMails, $queueForEvents, $queueForFunctions, $skipRenewCheck);
|
||||
$this->execute($domain, $dbForConsole, $queueForMails, $queueForEvents, $queueForFunctions, $log, $skipRenewCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,7 +89,7 @@ class Certificates extends Action
|
|||
* @throws Throwable
|
||||
* @throws \Utopia\Database\Exception
|
||||
*/
|
||||
private function execute(Domain $domain, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, bool $skipRenewCheck = false): void
|
||||
private function execute(Domain $domain, Database $dbForConsole, Mail $queueForMails, Event $queueForEvents, Func $queueForFunctions, Log $log, bool $skipRenewCheck = false): void
|
||||
{
|
||||
/**
|
||||
* 1. Read arguments and validate domain
|
||||
|
@ -143,11 +143,11 @@ class Certificates extends Action
|
|||
if (!$skipRenewCheck) {
|
||||
$mainDomain = $this->getMainDomain();
|
||||
$isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain;
|
||||
$this->validateDomain($domain, $isMainDomain);
|
||||
$this->validateDomain($domain, $isMainDomain, $log);
|
||||
}
|
||||
|
||||
// If certificate exists already, double-check expiry date. Skip if job is forced
|
||||
if (!$skipRenewCheck && !$this->isRenewRequired($domain->get())) {
|
||||
if (!$skipRenewCheck && !$this->isRenewRequired($domain->get(), $log)) {
|
||||
throw new Exception('Renew isn\'t required.');
|
||||
}
|
||||
|
||||
|
@ -185,6 +185,8 @@ class Certificates extends Action
|
|||
|
||||
// Send email to security email
|
||||
$this->notifyError($domain->get(), $e->getMessage(), $attempts, $queueForMails);
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
// All actions result in new updatedAt date
|
||||
$certificate->setAttribute('updated', DateTime::now());
|
||||
|
@ -252,7 +254,7 @@ class Certificates extends Action
|
|||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateDomain(Domain $domain, bool $isMainDomain): void
|
||||
private function validateDomain(Domain $domain, bool $isMainDomain, Log $log): void
|
||||
{
|
||||
if (empty($domain->get())) {
|
||||
throw new Exception('Missing certificate domain.');
|
||||
|
@ -272,8 +274,15 @@ class Certificates extends Action
|
|||
}
|
||||
|
||||
// Verify domain with DNS records
|
||||
$validationStart = \microtime(true);
|
||||
$validator = new CNAME($target->get());
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
||||
$error = $validator->getLogs();
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
|
||||
throw new Exception('Failed to verify domain DNS records.');
|
||||
}
|
||||
} else {
|
||||
|
@ -289,7 +298,7 @@ class Certificates extends Action
|
|||
* @return bool True, if certificate needs to be renewed
|
||||
* @throws Exception
|
||||
*/
|
||||
private function isRenewRequired(string $domain): bool
|
||||
private function isRenewRequired(string $domain, Log $log): bool
|
||||
{
|
||||
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
|
||||
if (\file_exists($certPath)) {
|
||||
|
@ -299,12 +308,15 @@ class Certificates extends Action
|
|||
$validTo = $certData['validTo_time_t'] ?? 0;
|
||||
|
||||
if (empty($validTo)) {
|
||||
$log->addTag('certificateDomain', $domain);
|
||||
throw new Exception('Unable to read certificate file (cert.pem).');
|
||||
}
|
||||
|
||||
// LetsEncrypt allows renewal 30 days before expiry
|
||||
$expiryInAdvance = (60 * 60 * 24 * 30);
|
||||
if ($validTo - $expiryInAdvance > \time()) {
|
||||
$log->addTag('certificateDomain', $domain);
|
||||
$log->addExtra('certificateData', \is_array($certData) ? \json_encode($certData) : \strval($certData));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@ class Databases extends Action
|
|||
}
|
||||
|
||||
$dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available'));
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// TODO: Send non DatabaseExceptions to Sentry
|
||||
Console::error($e->getMessage());
|
||||
|
||||
|
@ -269,7 +269,7 @@ class Databases extends Action
|
|||
if (!$relatedAttribute->isEmpty()) {
|
||||
$dbForProject->deleteDocument('attributes', $relatedAttribute->getId());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// TODO: Send non DatabaseExceptions to Sentry
|
||||
Console::error($e->getMessage());
|
||||
|
||||
|
@ -397,7 +397,7 @@ class Databases extends Action
|
|||
throw new DatabaseException('Failed to create Index');
|
||||
}
|
||||
$dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'available'));
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// TODO: Send non DatabaseExceptions to Sentry
|
||||
Console::error($e->getMessage());
|
||||
|
||||
|
@ -455,7 +455,7 @@ class Databases extends Action
|
|||
}
|
||||
$dbForProject->deleteDocument('indexes', $index->getId());
|
||||
$index->setAttribute('status', 'deleted');
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// TODO: Send non DatabaseExceptions to Sentry
|
||||
Console::error($e->getMessage());
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ use Utopia\Audit\Audit;
|
|||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Database;
|
||||
use Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\DateTime;
|
||||
|
@ -585,7 +584,7 @@ class Deletes extends Action
|
|||
// Delete metadata tables
|
||||
try {
|
||||
$dbForProject->deleteCollection('_metadata');
|
||||
} catch (Exception) {
|
||||
} catch (\Throwable) {
|
||||
// Ignore: deleteCollection tries to delete a metadata entry after the collection is deleted,
|
||||
// which will throw an exception here because the metadata collection is already deleted.
|
||||
}
|
||||
|
@ -630,12 +629,7 @@ class Deletes extends Action
|
|||
$teamId = $document->getAttribute('teamId');
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
if (!$team->isEmpty()) {
|
||||
$team = $dbForProject->updateDocument(
|
||||
'teams',
|
||||
$teamId,
|
||||
// Ensure that total >= 0
|
||||
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0))
|
||||
);
|
||||
$dbForProject->decreaseDocumentAttribute('teams', $teamId, 'total', 1, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -340,7 +340,7 @@ class Hamster extends Action
|
|||
if (!$res) {
|
||||
Console::error('Failed to create event for project: ' . $project->getId());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error('Failed to send stats for project: ' . $project->getId());
|
||||
Console::error($e->getMessage());
|
||||
} finally {
|
||||
|
@ -410,7 +410,7 @@ class Hamster extends Action
|
|||
if (!$res) {
|
||||
throw new \Exception('Failed to create event for organization : ' . $organization->getId());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -464,7 +464,7 @@ class Hamster extends Action
|
|||
if (!$res) {
|
||||
throw new \Exception('Failed to create user profile for user: ' . $user->getId());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ class Mails extends Action
|
|||
|
||||
try {
|
||||
$mail->send();
|
||||
} catch (\Exception $error) {
|
||||
} catch (\Throwable $error) {
|
||||
throw new Exception('Error sending mail: ' . $error->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,23 +2,22 @@
|
|||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Status as MessageStatus;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Messaging\Adapter\Email as EmailAdapter;
|
||||
use Utopia\Messaging\Adapter\Email\Mailgun;
|
||||
use Utopia\Messaging\Adapter\Email\Sendgrid;
|
||||
use Utopia\Messaging\Adapter\Email\SMTP;
|
||||
use Utopia\Messaging\Adapter\Email\Sendgrid;
|
||||
use Utopia\Messaging\Adapter\Push as PushAdapter;
|
||||
use Utopia\Messaging\Adapter\Push\APNS;
|
||||
use Utopia\Messaging\Adapter\Push\FCM;
|
||||
|
@ -32,7 +31,8 @@ use Utopia\Messaging\Adapter\SMS\Vonage;
|
|||
use Utopia\Messaging\Messages\Email;
|
||||
use Utopia\Messaging\Messages\Push;
|
||||
use Utopia\Messaging\Messages\SMS;
|
||||
use Utopia\Messaging\Response;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
|
@ -53,31 +53,40 @@ class Messaging extends Action
|
|||
->inject('message')
|
||||
->inject('log')
|
||||
->inject('dbForProject')
|
||||
->callback(fn(Message $message, Log $log, Database $dbForProject) => $this->action($message, $log, $dbForProject));
|
||||
->inject('queueForUsage')
|
||||
->callback(fn(Message $message, Log $log, Database $dbForProject, Usage $queueForUsage) => $this->action($message, $log, $dbForProject, $queueForUsage));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param Log $log
|
||||
* @param Database $dbForProject
|
||||
* @param Usage $queueForUsage
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function action(Message $message, Log $log, Database $dbForProject): void
|
||||
public function action(Message $message, Log $log, Database $dbForProject, Usage $queueForUsage): void
|
||||
{
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new \Exception('Payload not found.');
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
!\is_null($payload['message'])
|
||||
&& !\is_null($payload['recipients'])
|
||||
&& $payload['providerType'] === MESSAGE_TYPE_SMS
|
||||
) {
|
||||
// Message was triggered internally
|
||||
$this->processInternalSMSMessage($log, new Document($payload['message']), $payload['recipients']);
|
||||
$this->processInternalSMSMessage(
|
||||
new Document($payload['message']),
|
||||
new Document($payload['project'] ?? []),
|
||||
$payload['recipients'],
|
||||
$queueForUsage,
|
||||
$log,
|
||||
);
|
||||
} else {
|
||||
$message = $dbForProject->getDocument('messages', $payload['messageId']);
|
||||
|
||||
|
@ -254,7 +263,7 @@ class Messaging extends Action
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$deliveryErrors[] = 'Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage();
|
||||
} finally {
|
||||
$batchIndex++;
|
||||
|
@ -299,12 +308,26 @@ class Messaging extends Action
|
|||
$dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
}
|
||||
|
||||
private function processInternalSMSMessage(Log $log, Document $message, array $recipients): void
|
||||
private function processInternalSMSMessage(Document $message, Document $project, array $recipients, Usage $queueForUsage, Log $log): void
|
||||
{
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER')) || empty(App::getEnv('_APP_SMS_FROM'))) {
|
||||
throw new \Exception('Skipped SMS processing. Missing "_APP_SMS_PROVIDER" or "_APP_SMS_FROM" environment variables.');
|
||||
}
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception('Project not set in payload');
|
||||
}
|
||||
|
||||
Console::log('Project: ' . $project->getId());
|
||||
|
||||
$denyList = App::getEnv('_APP_SMS_PROJECTS_DENY_LIST', '');
|
||||
$denyList = explode(',', $denyList);
|
||||
|
||||
if (\in_array($project->getId(), $denyList)) {
|
||||
Console::error('Project is in the deny list. Skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
$smsDSN = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$host = $smsDSN->getHost();
|
||||
$password = $smsDSN->getPassword();
|
||||
|
@ -330,8 +353,8 @@ class Messaging extends Action
|
|||
'apiKey' => $password
|
||||
],
|
||||
'telesign' => [
|
||||
'username' => $user,
|
||||
'password' => $password
|
||||
'customerId' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'msg91' => [
|
||||
'senderId' => $user,
|
||||
|
@ -354,16 +377,21 @@ class Messaging extends Action
|
|||
$batches = \array_chunk($recipients, $maxBatchSize);
|
||||
$batchIndex = 0;
|
||||
|
||||
batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) {
|
||||
return function () use ($batch, $message, $provider, $adapter, $batchIndex) {
|
||||
batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex, $project, $queueForUsage) {
|
||||
return function () use ($batch, $message, $provider, $adapter, $batchIndex, $project, $queueForUsage) {
|
||||
$message->setAttribute('to', $batch);
|
||||
|
||||
$data = $this->buildSMSMessage($message, $provider);
|
||||
|
||||
try {
|
||||
$adapter->send($data);
|
||||
} catch (\Exception $e) {
|
||||
Console::error('Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage()); // TODO: Find a way to log into Sentry
|
||||
|
||||
$queueForUsage
|
||||
->setProject($project)
|
||||
->addMetric(METRIC_MESSAGES, 1)
|
||||
->trigger();
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception('Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage(), 500);
|
||||
}
|
||||
};
|
||||
}, $batches));
|
||||
|
@ -376,11 +404,12 @@ class Messaging extends Action
|
|||
private function sms(Document $provider): ?SMSAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'mock' => new Mock('username', 'password'),
|
||||
'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']),
|
||||
'textmagic' => new Textmagic($credentials['username'], $credentials['apiKey']),
|
||||
'telesign' => new Telesign($credentials['username'], $credentials['password']),
|
||||
'telesign' => new Telesign($credentials['customerId'], $credentials['apiKey']),
|
||||
'msg91' => new Msg91($credentials['senderId'], $credentials['authKey'], $credentials['templateId']),
|
||||
'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']),
|
||||
default => null
|
||||
|
@ -390,6 +419,7 @@ class Messaging extends Action
|
|||
private function push(Document $provider): ?PushAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'mock' => new Mock('username', 'password'),
|
||||
'apns' => new APNS(
|
||||
|
@ -407,6 +437,7 @@ class Messaging extends Action
|
|||
{
|
||||
$credentials = $provider->getAttribute('credentials', []);
|
||||
$options = $provider->getAttribute('options', []);
|
||||
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'mock' => new Mock('username', 'password'),
|
||||
'smtp' => new SMTP(
|
||||
|
|
|
@ -20,7 +20,6 @@ class Usage extends Action
|
|||
];
|
||||
|
||||
protected const INFINITY_PERIOD = '_inf_';
|
||||
protected const DEBUG_PROJECT_ID = 85293;
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'usage';
|
||||
|
@ -70,17 +69,6 @@ class Usage extends Action
|
|||
getProjectDB: $getProjectDB
|
||||
);
|
||||
}
|
||||
if ($project->getInternalId() == self::DEBUG_PROJECT_ID) {
|
||||
var_dump([
|
||||
'type' => 'payload',
|
||||
'project' => $project->getInternalId(),
|
||||
'database' => $project['database'] ?? '',
|
||||
$payload['metrics']
|
||||
]);
|
||||
|
||||
var_dump('==========================');
|
||||
}
|
||||
|
||||
self::$stats[$projectId]['project'] = $project;
|
||||
foreach ($payload['metrics'] ?? [] as $metric) {
|
||||
if (!isset(self::$stats[$projectId]['keys'][$metric['key']])) {
|
||||
|
@ -91,7 +79,6 @@ class Usage extends Action
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* On Documents that tied by relations like functions>deployments>build || documents>collection>database || buckets>files.
|
||||
* When we remove a parent document we need to deduct his children aggregation from the project scope.
|
||||
|
@ -231,7 +218,7 @@ class Usage extends Action
|
|||
default:
|
||||
break;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
console::error("[reducer] " . " {DateTime::now()} " . " {$project->getInternalId()} " . " {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,14 +57,6 @@ class UsageHook extends Usage
|
|||
|
||||
try {
|
||||
$dbForProject = $getProjectDB($data['project']);
|
||||
if ($projectInternalId == 85293) {
|
||||
var_dump([
|
||||
'project' => $projectInternalId,
|
||||
'database' => $database,
|
||||
'time' => DateTime::now(),
|
||||
'data' => $data['keys']
|
||||
]);
|
||||
}
|
||||
foreach ($data['keys'] ?? [] as $key => $value) {
|
||||
if ($value == 0) {
|
||||
continue;
|
||||
|
@ -75,15 +67,6 @@ class UsageHook extends Usage
|
|||
$id = \md5("{$time}_{$period}_{$key}");
|
||||
|
||||
try {
|
||||
if ($projectInternalId == self::DEBUG_PROJECT_ID) {
|
||||
var_dump([
|
||||
'type' => 'create',
|
||||
'period' => $period,
|
||||
'metric' => $key,
|
||||
'id' => $id,
|
||||
'value' => $value
|
||||
]);
|
||||
}
|
||||
$dbForProject->createDocument('stats_v2', new Document([
|
||||
'$id' => $id,
|
||||
'period' => $period,
|
||||
|
@ -94,15 +77,6 @@ class UsageHook extends Usage
|
|||
]));
|
||||
} catch (Duplicate $th) {
|
||||
if ($value < 0) {
|
||||
if ($projectInternalId == self::DEBUG_PROJECT_ID) {
|
||||
var_dump([
|
||||
'type' => 'decrease',
|
||||
'period' => $period,
|
||||
'metric' => $key,
|
||||
'id' => $id,
|
||||
'value' => $value
|
||||
]);
|
||||
}
|
||||
$dbForProject->decreaseDocumentAttribute(
|
||||
'stats_v2',
|
||||
$id,
|
||||
|
@ -110,15 +84,6 @@ class UsageHook extends Usage
|
|||
abs($value)
|
||||
);
|
||||
} else {
|
||||
if ($projectInternalId == self::DEBUG_PROJECT_ID) {
|
||||
var_dump([
|
||||
'type' => 'increase',
|
||||
'period' => $period,
|
||||
'metric' => $key,
|
||||
'id' => $id,
|
||||
'value' => $value
|
||||
]);
|
||||
}
|
||||
$dbForProject->increaseDocumentAttribute(
|
||||
'stats_v2',
|
||||
$id,
|
||||
|
@ -129,7 +94,7 @@ class UsageHook extends Usage
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
console::error(DateTime::now() . ' ' . $projectInternalId . ' ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,17 +240,24 @@ abstract class Format
|
|||
break;
|
||||
case 'projects':
|
||||
switch ($method) {
|
||||
case 'getSmsTemplate':
|
||||
case 'getEmailTemplate':
|
||||
case 'updateSmsTemplate':
|
||||
case 'updateEmailTemplate':
|
||||
case 'deleteSmsTemplate':
|
||||
case 'deleteEmailTemplate':
|
||||
switch ($param) {
|
||||
case 'type':
|
||||
return 'TemplateType';
|
||||
return 'EmailTemplateType';
|
||||
case 'locale':
|
||||
return 'TemplateLocale';
|
||||
return 'EmailTemplateLocale';
|
||||
}
|
||||
break;
|
||||
case 'getSmsTemplate':
|
||||
case 'updateSmsTemplate':
|
||||
case 'deleteSmsTemplate':
|
||||
switch ($param) {
|
||||
case 'type':
|
||||
return 'SMSTemplateType';
|
||||
case 'locale':
|
||||
return 'SMSTemplateLocale';
|
||||
}
|
||||
break;
|
||||
case 'createPlatform':
|
||||
|
|
|
@ -12,7 +12,8 @@ class Users extends Base
|
|||
'passwordUpdate',
|
||||
'registration',
|
||||
'emailVerification',
|
||||
'phoneVerification'
|
||||
'phoneVerification',
|
||||
'labels',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -70,6 +70,7 @@ use Appwrite\Utopia\Response\Model\Token;
|
|||
use Appwrite\Utopia\Response\Model\Webhook;
|
||||
use Appwrite\Utopia\Response\Model\Preferences;
|
||||
use Appwrite\Utopia\Response\Model\HealthAntivirus;
|
||||
use Appwrite\Utopia\Response\Model\HealthCertificate;
|
||||
use Appwrite\Utopia\Response\Model\HealthQueue;
|
||||
use Appwrite\Utopia\Response\Model\HealthStatus;
|
||||
use Appwrite\Utopia\Response\Model\HealthTime;
|
||||
|
@ -279,6 +280,7 @@ class Response extends SwooleResponse
|
|||
public const MODEL_HEALTH_QUEUE = 'healthQueue';
|
||||
public const MODEL_HEALTH_TIME = 'healthTime';
|
||||
public const MODEL_HEALTH_ANTIVIRUS = 'healthAntivirus';
|
||||
public const MODEL_HEALTH_CERTIFICATE = 'healthCertificate';
|
||||
public const MODEL_HEALTH_STATUS_LIST = 'healthStatusList';
|
||||
|
||||
// Console
|
||||
|
@ -421,6 +423,7 @@ class Response extends SwooleResponse
|
|||
->setModel(new HealthAntivirus())
|
||||
->setModel(new HealthQueue())
|
||||
->setModel(new HealthStatus())
|
||||
->setModel(new HealthCertificate())
|
||||
->setModel(new HealthTime())
|
||||
->setModel(new HealthVersion())
|
||||
->setModel(new Metric())
|
||||
|
|
71
src/Appwrite/Utopia/Response/Model/HealthCertificate.php
Normal file
71
src/Appwrite/Utopia/Response/Model/HealthCertificate.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class HealthCertificate extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Certificate name',
|
||||
'default' => '',
|
||||
'example' => '/CN=www.google.com',
|
||||
])
|
||||
->addRule('subjectSN', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Subject SN',
|
||||
'default' => 'www.google.com',
|
||||
'example' => '',
|
||||
])
|
||||
->addRule('issuerOrganisation', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Issuer organisation',
|
||||
'default' => 'Google Trust Services LLC',
|
||||
'example' => '',
|
||||
])
|
||||
->addRule('validFrom', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Valid from',
|
||||
'default' => '',
|
||||
'example' => '1704200998',
|
||||
])
|
||||
->addRule('validTo', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Valid to',
|
||||
'default' => '',
|
||||
'example' => '1711458597',
|
||||
])
|
||||
->addRule('signatureTypeSN', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Signature type SN',
|
||||
'default' => '',
|
||||
'example' => 'RSA-SHA256',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Health Certificate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_HEALTH_CERTIFICATE;
|
||||
}
|
||||
}
|
|
@ -2227,7 +2227,7 @@ class AccountCustomClientTest extends Scope
|
|||
/**
|
||||
* @depends testUpdatePhone
|
||||
*/
|
||||
#[Retry(count: 1)]
|
||||
#[Retry(count: 2)]
|
||||
public function testPhoneVerification(array $data): array
|
||||
{
|
||||
$session = $data['session'] ?? '';
|
||||
|
@ -2248,7 +2248,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
\sleep(5);
|
||||
\sleep(10);
|
||||
|
||||
$smsRequest = $this->getLastRequest();
|
||||
|
||||
|
|
|
@ -1268,7 +1268,7 @@ trait DatabasesBase
|
|||
$this->assertCount(1, $releaseYearIndex['body']['attributes']);
|
||||
$this->assertEquals('releaseYear', $releaseYearIndex['body']['attributes'][0]);
|
||||
|
||||
$releaseWithDate = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
$releaseWithDate1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
|
@ -1278,33 +1278,15 @@ trait DatabasesBase
|
|||
'attributes' => ['releaseYear', '$createdAt', '$updatedAt'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $releaseWithDate['headers']['status-code']);
|
||||
$this->assertEquals('releaseYearDated', $releaseWithDate['body']['key']);
|
||||
$this->assertEquals('key', $releaseWithDate['body']['type']);
|
||||
$this->assertCount(3, $releaseWithDate['body']['attributes']);
|
||||
$this->assertEquals('releaseYear', $releaseWithDate['body']['attributes'][0]);
|
||||
$this->assertEquals('$createdAt', $releaseWithDate['body']['attributes'][1]);
|
||||
$this->assertEquals('$updatedAt', $releaseWithDate['body']['attributes'][2]);
|
||||
$this->assertEquals(202, $releaseWithDate1['headers']['status-code']);
|
||||
$this->assertEquals('releaseYearDated', $releaseWithDate1['body']['key']);
|
||||
$this->assertEquals('key', $releaseWithDate1['body']['type']);
|
||||
$this->assertCount(3, $releaseWithDate1['body']['attributes']);
|
||||
$this->assertEquals('releaseYear', $releaseWithDate1['body']['attributes'][0]);
|
||||
$this->assertEquals('$createdAt', $releaseWithDate1['body']['attributes'][1]);
|
||||
$this->assertEquals('$updatedAt', $releaseWithDate1['body']['attributes'][2]);
|
||||
|
||||
// wait for database worker to create index
|
||||
sleep(2);
|
||||
|
||||
$movies = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), []);
|
||||
|
||||
$this->assertIsArray($movies['body']['indexes']);
|
||||
$this->assertCount(3, $movies['body']['indexes']);
|
||||
$this->assertEquals($titleIndex['body']['key'], $movies['body']['indexes'][0]['key']);
|
||||
$this->assertEquals($releaseYearIndex['body']['key'], $movies['body']['indexes'][1]['key']);
|
||||
$this->assertEquals($releaseWithDate['body']['key'], $movies['body']['indexes'][2]['key']);
|
||||
$this->assertEquals('available', $movies['body']['indexes'][0]['status']);
|
||||
$this->assertEquals('available', $movies['body']['indexes'][1]['status']);
|
||||
$this->assertEquals('available', $movies['body']['indexes'][2]['status']);
|
||||
|
||||
$releaseWithDate = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
$releaseWithDate2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
|
@ -1314,11 +1296,11 @@ trait DatabasesBase
|
|||
'attributes' => ['birthDay'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $releaseWithDate['headers']['status-code']);
|
||||
$this->assertEquals('birthDay', $releaseWithDate['body']['key']);
|
||||
$this->assertEquals('key', $releaseWithDate['body']['type']);
|
||||
$this->assertCount(1, $releaseWithDate['body']['attributes']);
|
||||
$this->assertEquals('birthDay', $releaseWithDate['body']['attributes'][0]);
|
||||
$this->assertEquals(202, $releaseWithDate2['headers']['status-code']);
|
||||
$this->assertEquals('birthDay', $releaseWithDate2['body']['key']);
|
||||
$this->assertEquals('key', $releaseWithDate2['body']['type']);
|
||||
$this->assertCount(1, $releaseWithDate2['body']['attributes']);
|
||||
$this->assertEquals('birthDay', $releaseWithDate2['body']['attributes'][0]);
|
||||
|
||||
// Test for failure
|
||||
$fulltextReleaseYear = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
|
@ -1406,9 +1388,12 @@ trait DatabasesBase
|
|||
'key' => 'index-ip-actors',
|
||||
'type' => 'key',
|
||||
'attributes' => ['releaseYear', 'actors'], // 2 levels
|
||||
'orders' => ['DESC', 'DESC'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $twoLevelsArray['headers']['status-code']);
|
||||
$this->assertEquals('DESC', $twoLevelsArray['body']['orders'][0]);
|
||||
$this->assertEquals(null, $twoLevelsArray['body']['orders'][1]); // Overwrite by API (array)
|
||||
|
||||
$unknown = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
@ -1423,6 +1408,50 @@ trait DatabasesBase
|
|||
$this->assertEquals(400, $unknown['headers']['status-code']);
|
||||
$this->assertEquals('Unknown attribute: Unknown', $unknown['body']['message']);
|
||||
|
||||
$index1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]), [
|
||||
'key' => 'integers-order',
|
||||
'type' => 'key',
|
||||
'attributes' => ['integers'], // array attribute
|
||||
'orders' => ['DESC'], // Check order is removed in API
|
||||
]);
|
||||
$this->assertEquals(202, $index1['headers']['status-code']);
|
||||
|
||||
$index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/indexes', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]), [
|
||||
'key' => 'integers-size',
|
||||
'type' => 'key',
|
||||
'attributes' => ['integers'], // array attribute
|
||||
]);
|
||||
$this->assertEquals(202, $index2['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Create Indexes by worker
|
||||
*/
|
||||
sleep(2);
|
||||
|
||||
$movies = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), []);
|
||||
|
||||
$this->assertIsArray($movies['body']['indexes']);
|
||||
$this->assertCount(8, $movies['body']['indexes']);
|
||||
$this->assertEquals($titleIndex['body']['key'], $movies['body']['indexes'][0]['key']);
|
||||
$this->assertEquals($releaseYearIndex['body']['key'], $movies['body']['indexes'][1]['key']);
|
||||
$this->assertEquals($releaseWithDate1['body']['key'], $movies['body']['indexes'][2]['key']);
|
||||
$this->assertEquals($releaseWithDate2['body']['key'], $movies['body']['indexes'][3]['key']);
|
||||
foreach ($movies['body']['indexes'] as $index) {
|
||||
$this->assertEquals('available', $index['status']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -2034,6 +2063,18 @@ trait DatabasesBase
|
|||
$this->assertEquals(2017, $documents['body']['documents'][1]['releaseYear']);
|
||||
$this->assertCount(2, $documents['body']['documents']);
|
||||
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
'{"method":"contains","attribute":"title","values":[bad]}'
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $documents['headers']['status-code']);
|
||||
$this->assertEquals('Invalid query: Syntax error', $documents['body']['message']);
|
||||
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
|
|
|
@ -89,9 +89,9 @@ class AccountTest extends Scope
|
|||
|
||||
$this->assertArrayNotHasKey('errors', $session['body']);
|
||||
$this->assertIsArray($session['body']['data']);
|
||||
$this->assertIsArray($session['body']['data']['accountCreateMagicURLSession']);
|
||||
$this->assertIsArray($session['body']['data']['accountCreateMagicURLToken']);
|
||||
|
||||
return $session['body']['data']['accountCreateMagicURLSession'];
|
||||
return $session['body']['data']['accountCreateMagicURLToken'];
|
||||
}
|
||||
|
||||
public function testCreateEmailVerification(): array
|
||||
|
|
|
@ -1208,7 +1208,7 @@ trait Base
|
|||
}';
|
||||
case self::$CREATE_MAGIC_URL:
|
||||
return 'mutation createMagicURL($userId: String!, $email: String!){
|
||||
accountCreateMagicURLSession(userId: $userId, email: $email) {
|
||||
accountCreateMagicURLToken(userId: $userId, email: $email) {
|
||||
userId
|
||||
expire
|
||||
}
|
||||
|
@ -1832,8 +1832,8 @@ trait Base
|
|||
}
|
||||
}';
|
||||
case self::$CREATE_TELESIGN_PROVIDER:
|
||||
return 'mutation createTelesignProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $password: String!) {
|
||||
messagingCreateTelesignProvider(providerId: $providerId, name: $name, from: $from, username: $username, password: $password) {
|
||||
return 'mutation createTelesignProvider($providerId: String!, $name: String!, $from: String!, $customerId: String!, $apiKey: String!) {
|
||||
messagingCreateTelesignProvider(providerId: $providerId, name: $name, from: $from, customerId: $customerId, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
|
@ -1956,8 +1956,8 @@ trait Base
|
|||
}
|
||||
}';
|
||||
case self::$UPDATE_TELESIGN_PROVIDER:
|
||||
return 'mutation updateTelesignProvider($providerId: String!, $name: String!, $username: String!, $password: String!) {
|
||||
messagingUpdateTelesignProvider(providerId: $providerId, name: $name, username: $username, password: $password) {
|
||||
return 'mutation updateTelesignProvider($providerId: String!, $name: String!, $customerId: String!, $apiKey: String!) {
|
||||
messagingUpdateTelesignProvider(providerId: $providerId, name: $name, customerId: $customerId, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
|
|
|
@ -45,8 +45,8 @@ class MessagingTest extends Scope
|
|||
'Telesign' => [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Telesign1',
|
||||
'username' => 'my-username',
|
||||
'password' => 'my-password',
|
||||
'customerId' => 'my-username',
|
||||
'apiKey' => 'my-password',
|
||||
'from' => '+123456789',
|
||||
],
|
||||
'Textmagic' => [
|
||||
|
@ -104,7 +104,7 @@ class MessagingTest extends Scope
|
|||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]), $graphQLPayload);
|
||||
|
||||
\array_push($providers, $response['body']['data']['messagingCreate' . $key . 'Provider']);
|
||||
$providers[] = $response['body']['data']['messagingCreate' . $key . 'Provider'];
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($providersParams[$key]['name'], $response['body']['data']['messagingCreate' . $key . 'Provider']['name']);
|
||||
}
|
||||
|
@ -138,8 +138,8 @@ class MessagingTest extends Scope
|
|||
'Telesign' => [
|
||||
'providerId' => $providers[3]['_id'],
|
||||
'name' => 'Telesign2',
|
||||
'username' => 'my-username',
|
||||
'password' => 'my-password',
|
||||
'customerId' => 'my-username',
|
||||
'apiKey' => 'my-password',
|
||||
],
|
||||
'Textmagic' => [
|
||||
'providerId' => $providers[4]['_id'],
|
||||
|
|
|
@ -424,4 +424,74 @@ class HealthCustomServerTest extends Scope
|
|||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function testCertificateValidity(): array
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/certificate?domain=www.google.com', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('/CN=www.google.com', $response['body']['name']);
|
||||
$this->assertEquals('www.google.com', $response['body']['subjectSN']);
|
||||
$this->assertEquals('Google Trust Services LLC', $response['body']['issuerOrganisation']);
|
||||
$this->assertIsInt($response['body']['validFrom']);
|
||||
$this->assertIsInt($response['body']['validTo']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/certificate?domain=appwrite.io', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('/CN=appwrite.io', $response['body']['name']);
|
||||
$this->assertEquals('appwrite.io', $response['body']['subjectSN']);
|
||||
$this->assertEquals("Let's Encrypt", $response['body']['issuerOrganisation']);
|
||||
$this->assertIsInt($response['body']['validFrom']);
|
||||
$this->assertIsInt($response['body']['validTo']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/certificate?domain=https://google.com', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/certificate?domain=localhost', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/certificate?domain=doesnotexist.com', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/certificate?domain=www.google.com/usr/src/local', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/health/certificate?domain=', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Tests\E2E\Services\Messaging;
|
|||
use Appwrite\Messaging\Status as MessageStatus;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\DSN\DSN;
|
||||
|
@ -50,8 +51,8 @@ trait MessagingBase
|
|||
'telesign' => [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Telesign1',
|
||||
'username' => 'my-username',
|
||||
'password' => 'my-password',
|
||||
'customerId' => 'my-username',
|
||||
'apiKey' => 'my-password',
|
||||
'from' => '+123456789',
|
||||
],
|
||||
'textmagic' => [
|
||||
|
@ -141,8 +142,8 @@ trait MessagingBase
|
|||
],
|
||||
'telesign' => [
|
||||
'name' => 'Telesign2',
|
||||
'username' => 'my-username',
|
||||
'password' => 'my-password',
|
||||
'customerId' => 'my-username',
|
||||
'apiKey' => 'my-password',
|
||||
],
|
||||
'textmagic' => [
|
||||
'name' => 'Textmagic2',
|
||||
|
@ -176,6 +177,7 @@ trait MessagingBase
|
|||
'bundleId' => 'my-bundleid',
|
||||
],
|
||||
];
|
||||
|
||||
foreach (\array_keys($providersParams) as $index => $key) {
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/messaging/providers/' . $key . '/' . $providers[$index]['$id'], [
|
||||
'content-type' => 'application/json',
|
||||
|
@ -201,7 +203,9 @@ trait MessagingBase
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('Mailgun2', $response['body']['name']);
|
||||
$this->assertEquals(false, $response['body']['enabled']);
|
||||
|
||||
$providers[1] = $response['body'];
|
||||
|
||||
return $providers;
|
||||
}
|
||||
|
||||
|
@ -682,12 +686,27 @@ trait MessagingBase
|
|||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$targetList = $response['body'];
|
||||
$this->assertEquals(1, $targetList['total']);
|
||||
$this->assertEquals(1, count($targetList['targets']));
|
||||
$this->assertEquals(2, $targetList['total']);
|
||||
$this->assertEquals(2, count($targetList['targets']));
|
||||
$this->assertEquals($message['targets'][0], $targetList['targets'][0]['$id']);
|
||||
$this->assertEquals($message['targets'][1], $targetList['targets'][1]['$id']);
|
||||
|
||||
/**
|
||||
* Cursor Test
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $message['$id'] . '/targets', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'queries' => [
|
||||
Query::cursorAfter(new Document(['$id' => $targetList['targets'][0]['$id']]))->toString(),
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(2, $response['body']['total']);
|
||||
$this->assertEquals(1, count($response['body']['targets']));
|
||||
$this->assertEquals($targetList['targets'][1]['$id'], $response['body']['targets'][0]['$id']);
|
||||
|
||||
// Test for empty targets
|
||||
$response = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', [
|
||||
|
@ -719,7 +738,7 @@ trait MessagingBase
|
|||
|
||||
public function testCreateDraftEmail()
|
||||
{
|
||||
// Create User
|
||||
// Create User 1
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
|
@ -728,15 +747,33 @@ trait MessagingBase
|
|||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . "@example.com",
|
||||
'password' => 'password',
|
||||
'name' => 'Messaging User',
|
||||
'name' => 'Messaging User 1',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code'], "Error creating user: " . var_export($response['body'], true));
|
||||
|
||||
$user = $response['body'];
|
||||
$user1 = $response['body'];
|
||||
|
||||
$this->assertEquals(1, \count($user['targets']));
|
||||
$targetId = $user['targets'][0]['$id'];
|
||||
$this->assertEquals(1, \count($user1['targets']));
|
||||
$targetId1 = $user1['targets'][0]['$id'];
|
||||
|
||||
// Create User 2
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . "@example.com",
|
||||
'password' => 'password',
|
||||
'name' => 'Messaging User 2',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code'], "Error creating user: " . var_export($response['body'], true));
|
||||
$user2 = $response['body'];
|
||||
|
||||
$this->assertEquals(1, \count($user2['targets']));
|
||||
$targetId2 = $user2['targets'][0]['$id'];
|
||||
|
||||
// Create Email
|
||||
$response = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', [
|
||||
|
@ -745,13 +782,12 @@ trait MessagingBase
|
|||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'messageId' => ID::unique(),
|
||||
'targets' => [$targetId],
|
||||
'targets' => [$targetId1, $targetId2],
|
||||
'subject' => 'New blog post',
|
||||
'content' => 'Check out the new blog post at http://localhost',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$message = $response['body'];
|
||||
$this->assertEquals(MessageStatus::DRAFT, $message['status']);
|
||||
|
||||
|
|
Loading…
Reference in a new issue