Merge pull request #7611 from appwrite/feat-email-attachments
Feat email attachments
This commit is contained in:
commit
9b8ad99030
15 changed files with 434 additions and 176 deletions
|
@ -845,8 +845,8 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->inject('request')
|
->inject('request')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('deviceFunctions')
|
->inject('deviceForFunctions')
|
||||||
->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceFunctions) {
|
->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForFunctions) {
|
||||||
|
|
||||||
$function = $dbForProject->getDocument('functions', $functionId);
|
$function = $dbForProject->getDocument('functions', $functionId);
|
||||||
if ($function->isEmpty()) {
|
if ($function->isEmpty()) {
|
||||||
|
@ -863,7 +863,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $deployment->getAttribute('path', '');
|
$path = $deployment->getAttribute('path', '');
|
||||||
if (!$deviceFunctions->exists($path)) {
|
if (!$deviceForFunctions->exists($path)) {
|
||||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -873,7 +873,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||||
|
|
||||||
$size = $deviceFunctions->getFileSize($path);
|
$size = $deviceForFunctions->getFileSize($path);
|
||||||
$rangeHeader = $request->getHeader('range');
|
$rangeHeader = $request->getHeader('range');
|
||||||
|
|
||||||
if (!empty($rangeHeader)) {
|
if (!empty($rangeHeader)) {
|
||||||
|
@ -895,13 +895,13 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||||
->addHeader('Content-Length', $end - $start + 1)
|
->addHeader('Content-Length', $end - $start + 1)
|
||||||
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
|
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
|
||||||
|
|
||||||
$response->send($deviceFunctions->read($path, $start, ($end - $start + 1)));
|
$response->send($deviceForFunctions->read($path, $start, ($end - $start + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||||
$response->chunk(
|
$response->chunk(
|
||||||
$deviceFunctions->read(
|
$deviceForFunctions->read(
|
||||||
$path,
|
$path,
|
||||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||||
|
@ -910,7 +910,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$response->send($deviceFunctions->read($path));
|
$response->send($deviceForFunctions->read($path));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1049,10 +1049,10 @@ App::post('/v1/functions/:functionId/deployments')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('queueForEvents')
|
->inject('queueForEvents')
|
||||||
->inject('project')
|
->inject('project')
|
||||||
->inject('deviceFunctions')
|
->inject('deviceForFunctions')
|
||||||
->inject('deviceLocal')
|
->inject('deviceForLocal')
|
||||||
->inject('queueForBuilds')
|
->inject('queueForBuilds')
|
||||||
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, bool $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceFunctions, Device $deviceLocal, Build $queueForBuilds) {
|
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, bool $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds) {
|
||||||
|
|
||||||
$activate = filter_var($activate, FILTER_VALIDATE_BOOLEAN);
|
$activate = filter_var($activate, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
|
||||||
|
@ -1133,11 +1133,11 @@ App::post('/v1/functions/:functionId/deployments')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
$fileSize ??= $deviceLocal->getFileSize($fileTmpName);
|
$fileSize ??= $deviceForLocal->getFileSize($fileTmpName);
|
||||||
$path = $deviceFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
|
$path = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
|
||||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||||
|
|
||||||
$metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)];
|
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
|
||||||
if (!$deployment->isEmpty()) {
|
if (!$deployment->isEmpty()) {
|
||||||
$chunks = $deployment->getAttribute('chunksTotal', 1);
|
$chunks = $deployment->getAttribute('chunksTotal', 1);
|
||||||
$metadata = $deployment->getAttribute('metadata', []);
|
$metadata = $deployment->getAttribute('metadata', []);
|
||||||
|
@ -1146,7 +1146,7 @@ App::post('/v1/functions/:functionId/deployments')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$chunksUploaded = $deviceFunctions->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
|
$chunksUploaded = $deviceForFunctions->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
|
||||||
|
|
||||||
if (empty($chunksUploaded)) {
|
if (empty($chunksUploaded)) {
|
||||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
|
||||||
|
@ -1169,7 +1169,7 @@ App::post('/v1/functions/:functionId/deployments')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileSize = $deviceFunctions->getFileSize($path);
|
$fileSize = $deviceForFunctions->getFileSize($path);
|
||||||
|
|
||||||
if ($deployment->isEmpty()) {
|
if ($deployment->isEmpty()) {
|
||||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||||
|
@ -1378,8 +1378,8 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('queueForDeletes')
|
->inject('queueForDeletes')
|
||||||
->inject('queueForEvents')
|
->inject('queueForEvents')
|
||||||
->inject('deviceFunctions')
|
->inject('deviceForFunctions')
|
||||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Device $deviceFunctions) {
|
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Device $deviceForFunctions) {
|
||||||
|
|
||||||
$function = $dbForProject->getDocument('functions', $functionId);
|
$function = $dbForProject->getDocument('functions', $functionId);
|
||||||
if ($function->isEmpty()) {
|
if ($function->isEmpty()) {
|
||||||
|
@ -1400,7 +1400,7 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($deployment->getAttribute('path', ''))) {
|
if (!empty($deployment->getAttribute('path', ''))) {
|
||||||
if (!($deviceFunctions->delete($deployment->getAttribute('path', '')))) {
|
if (!($deviceForFunctions->delete($deployment->getAttribute('path', '')))) {
|
||||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -792,8 +792,8 @@ App::get('/v1/health/stats') // Currently only used internally
|
||||||
->label('docs', false)
|
->label('docs', false)
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->inject('register')
|
->inject('register')
|
||||||
->inject('deviceFiles')
|
->inject('deviceForFiles')
|
||||||
->action(function (Response $response, Registry $register, Device $deviceFiles) {
|
->action(function (Response $response, Registry $register, Device $deviceForFiles) {
|
||||||
|
|
||||||
$cache = $register->get('cache');
|
$cache = $register->get('cache');
|
||||||
|
|
||||||
|
@ -802,9 +802,9 @@ App::get('/v1/health/stats') // Currently only used internally
|
||||||
$response
|
$response
|
||||||
->json([
|
->json([
|
||||||
'storage' => [
|
'storage' => [
|
||||||
'used' => Storage::human($deviceFiles->getDirectorySize($deviceFiles->getRoot() . '/')),
|
'used' => Storage::human($deviceForFiles->getDirectorySize($deviceForFiles->getRoot() . '/')),
|
||||||
'partitionTotal' => Storage::human($deviceFiles->getPartitionTotalSpace()),
|
'partitionTotal' => Storage::human($deviceForFiles->getPartitionTotalSpace()),
|
||||||
'partitionFree' => Storage::human($deviceFiles->getPartitionFreeSpace()),
|
'partitionFree' => Storage::human($deviceForFiles->getPartitionFreeSpace()),
|
||||||
],
|
],
|
||||||
'cache' => [
|
'cache' => [
|
||||||
'uptime' => $cacheStats['uptime_in_seconds'] ?? 0,
|
'uptime' => $cacheStats['uptime_in_seconds'] ?? 0,
|
||||||
|
|
|
@ -10,6 +10,7 @@ use Appwrite\Messaging\Status as MessageStatus;
|
||||||
use Appwrite\Network\Validator\Email;
|
use Appwrite\Network\Validator\Email;
|
||||||
use Appwrite\Permission;
|
use Appwrite\Permission;
|
||||||
use Appwrite\Role;
|
use Appwrite\Role;
|
||||||
|
use Appwrite\Utopia\Database\Validator\CompoundUID;
|
||||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||||
use Appwrite\Utopia\Database\Validator\Queries\Messages;
|
use Appwrite\Utopia\Database\Validator\Queries\Messages;
|
||||||
use Appwrite\Utopia\Database\Validator\Queries\Providers;
|
use Appwrite\Utopia\Database\Validator\Queries\Providers;
|
||||||
|
@ -2573,6 +2574,7 @@ App::post('/v1/messaging/messages/email')
|
||||||
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
|
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||||
->param('cc', [], new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true)
|
->param('cc', [], new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true)
|
||||||
->param('bcc', [], new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true)
|
->param('bcc', [], new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true)
|
||||||
|
->param('attachments', [], new ArrayList(new CompoundUID()), 'Array of compound bucket IDs to file IDs to be attached to the email.', true)
|
||||||
->param('status', MessageStatus::DRAFT, new WhiteList([MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]), 'Message Status. Value must be one of: ' . implode(', ', [MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]) . '.', true)
|
->param('status', MessageStatus::DRAFT, new WhiteList([MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]), 'Message Status. Value must be one of: ' . implode(', ', [MessageStatus::DRAFT, MessageStatus::SCHEDULED, MessageStatus::PROCESSING]) . '.', true)
|
||||||
->param('html', false, new Boolean(), 'Is content of type HTML', true)
|
->param('html', false, new Boolean(), 'Is content of type HTML', true)
|
||||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||||
|
@ -2582,7 +2584,7 @@ App::post('/v1/messaging/messages/email')
|
||||||
->inject('project')
|
->inject('project')
|
||||||
->inject('queueForMessaging')
|
->inject('queueForMessaging')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
|
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, array $attachments, string $status, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||||
$messageId = $messageId == 'unique()'
|
$messageId = $messageId == 'unique()'
|
||||||
? ID::unique()
|
? ID::unique()
|
||||||
: $messageId;
|
: $messageId;
|
||||||
|
@ -2615,6 +2617,29 @@ App::post('/v1/messaging/messages/email')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($attachments)) {
|
||||||
|
foreach ($attachments as &$attachment) {
|
||||||
|
[$bucketId, $fileId] = CompoundUID::parse($attachment);
|
||||||
|
|
||||||
|
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||||
|
|
||||||
|
if ($bucket->isEmpty()) {
|
||||||
|
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||||
|
|
||||||
|
if ($file->isEmpty()) {
|
||||||
|
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$attachment = [
|
||||||
|
'bucketId' => $bucketId,
|
||||||
|
'fileId' => $fileId,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$message = $dbForProject->createDocument('messages', new Document([
|
$message = $dbForProject->createDocument('messages', new Document([
|
||||||
'$id' => $messageId,
|
'$id' => $messageId,
|
||||||
'providerType' => MESSAGE_TYPE_EMAIL,
|
'providerType' => MESSAGE_TYPE_EMAIL,
|
||||||
|
@ -2628,6 +2653,7 @@ App::post('/v1/messaging/messages/email')
|
||||||
'html' => $html,
|
'html' => $html,
|
||||||
'cc' => $cc,
|
'cc' => $cc,
|
||||||
'bcc' => $bcc,
|
'bcc' => $bcc,
|
||||||
|
'attachments' => $attachments,
|
||||||
],
|
],
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
]));
|
]));
|
||||||
|
|
|
@ -360,9 +360,9 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||||
->inject('user')
|
->inject('user')
|
||||||
->inject('queueForEvents')
|
->inject('queueForEvents')
|
||||||
->inject('mode')
|
->inject('mode')
|
||||||
->inject('deviceFiles')
|
->inject('deviceForFiles')
|
||||||
->inject('deviceLocal')
|
->inject('deviceForLocal')
|
||||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceFiles, Device $deviceLocal) {
|
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
|
||||||
|
|
||||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||||
|
|
||||||
|
@ -493,13 +493,13 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to storage
|
// Save to storage
|
||||||
$fileSize ??= $deviceLocal->getFileSize($fileTmpName);
|
$fileSize ??= $deviceForLocal->getFileSize($fileTmpName);
|
||||||
$path = $deviceFiles->getPath($fileId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
|
$path = $deviceForFiles->getPath($fileId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
|
||||||
$path = str_ireplace($deviceFiles->getRoot(), $deviceFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
|
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
|
||||||
|
|
||||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||||
|
|
||||||
$metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)];
|
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
|
||||||
if (!$file->isEmpty()) {
|
if (!$file->isEmpty()) {
|
||||||
$chunks = $file->getAttribute('chunksTotal', 1);
|
$chunks = $file->getAttribute('chunksTotal', 1);
|
||||||
$uploaded = $file->getAttribute('chunksUploaded', 0);
|
$uploaded = $file->getAttribute('chunksUploaded', 0);
|
||||||
|
@ -514,32 +514,32 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$chunksUploaded = $deviceFiles->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
|
$chunksUploaded = $deviceForFiles->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
|
||||||
|
|
||||||
if (empty($chunksUploaded)) {
|
if (empty($chunksUploaded)) {
|
||||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed uploading file');
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed uploading file');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($chunksUploaded === $chunks) {
|
if ($chunksUploaded === $chunks) {
|
||||||
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antivirus', true) && $fileSize <= APP_LIMIT_ANTIVIRUS && $deviceFiles->getType() === Storage::DEVICE_LOCAL) {
|
if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled' && $bucket->getAttribute('antivirus', true) && $fileSize <= APP_LIMIT_ANTIVIRUS && $deviceForFiles->getType() === Storage::DEVICE_LOCAL) {
|
||||||
$antivirus = new Network(
|
$antivirus = new Network(
|
||||||
App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
|
App::getEnv('_APP_STORAGE_ANTIVIRUS_HOST', 'clamav'),
|
||||||
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310)
|
(int) App::getEnv('_APP_STORAGE_ANTIVIRUS_PORT', 3310)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!$antivirus->fileScan($path)) {
|
if (!$antivirus->fileScan($path)) {
|
||||||
$deviceFiles->delete($path);
|
$deviceForFiles->delete($path);
|
||||||
throw new Exception(Exception::STORAGE_INVALID_FILE);
|
throw new Exception(Exception::STORAGE_INVALID_FILE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$mimeType = $deviceFiles->getFileMimeType($path); // Get mime-type before compression and encryption
|
$mimeType = $deviceForFiles->getFileMimeType($path); // Get mime-type before compression and encryption
|
||||||
$fileHash = $deviceFiles->getFileHash($path); // Get file hash before compression and encryption
|
$fileHash = $deviceForFiles->getFileHash($path); // Get file hash before compression and encryption
|
||||||
$data = '';
|
$data = '';
|
||||||
// Compression
|
// Compression
|
||||||
$algorithm = $bucket->getAttribute('compression', Compression::NONE);
|
$algorithm = $bucket->getAttribute('compression', Compression::NONE);
|
||||||
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != Compression::NONE) {
|
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != Compression::NONE) {
|
||||||
$data = $deviceFiles->read($path);
|
$data = $deviceForFiles->read($path);
|
||||||
switch ($algorithm) {
|
switch ($algorithm) {
|
||||||
case Compression::ZSTD:
|
case Compression::ZSTD:
|
||||||
$compressor = new Zstd();
|
$compressor = new Zstd();
|
||||||
|
@ -559,7 +559,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||||
|
|
||||||
if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) {
|
if ($bucket->getAttribute('encryption', true) && $fileSize <= APP_STORAGE_READ_BUFFER) {
|
||||||
if (empty($data)) {
|
if (empty($data)) {
|
||||||
$data = $deviceFiles->read($path);
|
$data = $deviceForFiles->read($path);
|
||||||
}
|
}
|
||||||
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
|
$key = App::getEnv('_APP_OPENSSL_KEY_V1');
|
||||||
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
|
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
|
||||||
|
@ -567,12 +567,12 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($data)) {
|
if (!empty($data)) {
|
||||||
if (!$deviceFiles->write($path, $data, $mimeType)) {
|
if (!$deviceForFiles->write($path, $data, $mimeType)) {
|
||||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to save file');
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to save file');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$sizeActual = $deviceFiles->getFileSize($path);
|
$sizeActual = $deviceForFiles->getFileSize($path);
|
||||||
|
|
||||||
$openSSLVersion = null;
|
$openSSLVersion = null;
|
||||||
$openSSLCipher = null;
|
$openSSLCipher = null;
|
||||||
|
@ -872,9 +872,9 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||||
->inject('project')
|
->inject('project')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('mode')
|
->inject('mode')
|
||||||
->inject('deviceFiles')
|
->inject('deviceForFiles')
|
||||||
->inject('deviceLocal')
|
->inject('deviceForLocal')
|
||||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceFiles, Device $deviceLocal) {
|
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
|
||||||
|
|
||||||
if (!\extension_loaded('imagick')) {
|
if (!\extension_loaded('imagick')) {
|
||||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||||
|
@ -931,10 +931,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||||
$cipher = null;
|
$cipher = null;
|
||||||
$background = (empty($background)) ? 'eceff1' : $background;
|
$background = (empty($background)) ? 'eceff1' : $background;
|
||||||
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
|
$type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
|
||||||
$deviceFiles = $deviceLocal;
|
$deviceForFiles = $deviceForLocal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$deviceFiles->exists($path)) {
|
if (!$deviceForFiles->exists($path)) {
|
||||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -950,7 +950,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||||
$output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type;
|
$output = empty($type) ? (array_search($mime, $outputs) ?? 'jpg') : $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
$source = $deviceFiles->read($path);
|
$source = $deviceForFiles->read($path);
|
||||||
|
|
||||||
if (!empty($cipher)) { // Decrypt
|
if (!empty($cipher)) { // Decrypt
|
||||||
$source = OpenSSL::decrypt(
|
$source = OpenSSL::decrypt(
|
||||||
|
@ -1033,8 +1033,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('mode')
|
->inject('mode')
|
||||||
->inject('deviceFiles')
|
->inject('deviceForFiles')
|
||||||
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceFiles) {
|
->action(function (string $bucketId, string $fileId, Request $request, Response $response, Database $dbForProject, string $mode, Device $deviceForFiles) {
|
||||||
|
|
||||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||||
|
|
||||||
|
@ -1064,7 +1064,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||||
|
|
||||||
$path = $file->getAttribute('path', '');
|
$path = $file->getAttribute('path', '');
|
||||||
|
|
||||||
if (!$deviceFiles->exists($path)) {
|
if (!$deviceForFiles->exists($path)) {
|
||||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1100,7 +1100,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||||
|
|
||||||
$source = '';
|
$source = '';
|
||||||
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
|
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
|
||||||
$source = $deviceFiles->read($path);
|
$source = $deviceForFiles->read($path);
|
||||||
$source = OpenSSL::decrypt(
|
$source = OpenSSL::decrypt(
|
||||||
$source,
|
$source,
|
||||||
$file->getAttribute('openSSLCipher'),
|
$file->getAttribute('openSSLCipher'),
|
||||||
|
@ -1114,14 +1114,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||||
switch ($file->getAttribute('algorithm', Compression::NONE)) {
|
switch ($file->getAttribute('algorithm', Compression::NONE)) {
|
||||||
case Compression::ZSTD:
|
case Compression::ZSTD:
|
||||||
if (empty($source)) {
|
if (empty($source)) {
|
||||||
$source = $deviceFiles->read($path);
|
$source = $deviceForFiles->read($path);
|
||||||
}
|
}
|
||||||
$compressor = new Zstd();
|
$compressor = new Zstd();
|
||||||
$source = $compressor->decompress($source);
|
$source = $compressor->decompress($source);
|
||||||
break;
|
break;
|
||||||
case Compression::GZIP:
|
case Compression::GZIP:
|
||||||
if (empty($source)) {
|
if (empty($source)) {
|
||||||
$source = $deviceFiles->read($path);
|
$source = $deviceForFiles->read($path);
|
||||||
}
|
}
|
||||||
$compressor = new GZIP();
|
$compressor = new GZIP();
|
||||||
$source = $compressor->decompress($source);
|
$source = $compressor->decompress($source);
|
||||||
|
@ -1136,13 +1136,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($rangeHeader)) {
|
if (!empty($rangeHeader)) {
|
||||||
$response->send($deviceFiles->read($path, $start, ($end - $start + 1)));
|
$response->send($deviceForFiles->read($path, $start, ($end - $start + 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||||
$response->chunk(
|
$response->chunk(
|
||||||
$deviceFiles->read(
|
$deviceForFiles->read(
|
||||||
$path,
|
$path,
|
||||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||||
|
@ -1151,7 +1151,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$response->send($deviceFiles->read($path));
|
$response->send($deviceForFiles->read($path));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1173,8 +1173,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||||
->inject('request')
|
->inject('request')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('mode')
|
->inject('mode')
|
||||||
->inject('deviceFiles')
|
->inject('deviceForFiles')
|
||||||
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceFiles) {
|
->action(function (string $bucketId, string $fileId, Response $response, Request $request, Database $dbForProject, string $mode, Device $deviceForFiles) {
|
||||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||||
|
|
||||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||||
|
@ -1205,7 +1205,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||||
|
|
||||||
$path = $file->getAttribute('path', '');
|
$path = $file->getAttribute('path', '');
|
||||||
|
|
||||||
if (!$deviceFiles->exists($path)) {
|
if (!$deviceForFiles->exists($path)) {
|
||||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1249,7 +1249,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||||
|
|
||||||
$source = '';
|
$source = '';
|
||||||
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
|
if (!empty($file->getAttribute('openSSLCipher'))) { // Decrypt
|
||||||
$source = $deviceFiles->read($path);
|
$source = $deviceForFiles->read($path);
|
||||||
$source = OpenSSL::decrypt(
|
$source = OpenSSL::decrypt(
|
||||||
$source,
|
$source,
|
||||||
$file->getAttribute('openSSLCipher'),
|
$file->getAttribute('openSSLCipher'),
|
||||||
|
@ -1263,14 +1263,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||||
switch ($file->getAttribute('algorithm', Compression::NONE)) {
|
switch ($file->getAttribute('algorithm', Compression::NONE)) {
|
||||||
case Compression::ZSTD:
|
case Compression::ZSTD:
|
||||||
if (empty($source)) {
|
if (empty($source)) {
|
||||||
$source = $deviceFiles->read($path);
|
$source = $deviceForFiles->read($path);
|
||||||
}
|
}
|
||||||
$compressor = new Zstd();
|
$compressor = new Zstd();
|
||||||
$source = $compressor->decompress($source);
|
$source = $compressor->decompress($source);
|
||||||
break;
|
break;
|
||||||
case Compression::GZIP:
|
case Compression::GZIP:
|
||||||
if (empty($source)) {
|
if (empty($source)) {
|
||||||
$source = $deviceFiles->read($path);
|
$source = $deviceForFiles->read($path);
|
||||||
}
|
}
|
||||||
$compressor = new GZIP();
|
$compressor = new GZIP();
|
||||||
$source = $compressor->decompress($source);
|
$source = $compressor->decompress($source);
|
||||||
|
@ -1286,15 +1286,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($rangeHeader)) {
|
if (!empty($rangeHeader)) {
|
||||||
$response->send($deviceFiles->read($path, $start, ($end - $start + 1)));
|
$response->send($deviceForFiles->read($path, $start, ($end - $start + 1)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$size = $deviceFiles->getFileSize($path);
|
$size = $deviceForFiles->getFileSize($path);
|
||||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||||
$response->chunk(
|
$response->chunk(
|
||||||
$deviceFiles->read(
|
$deviceForFiles->read(
|
||||||
$path,
|
$path,
|
||||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||||
|
@ -1303,7 +1303,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$response->send($deviceFiles->read($path));
|
$response->send($deviceForFiles->read($path));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1438,9 +1438,9 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('queueForEvents')
|
->inject('queueForEvents')
|
||||||
->inject('mode')
|
->inject('mode')
|
||||||
->inject('deviceFiles')
|
->inject('deviceForFiles')
|
||||||
->inject('queueForDeletes')
|
->inject('queueForDeletes')
|
||||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceFiles, Delete $queueForDeletes) {
|
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes) {
|
||||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||||
|
|
||||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||||
|
@ -1471,12 +1471,12 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||||
|
|
||||||
$deviceDeleted = false;
|
$deviceDeleted = false;
|
||||||
if ($file->getAttribute('chunksTotal') !== $file->getAttribute('chunksUploaded')) {
|
if ($file->getAttribute('chunksTotal') !== $file->getAttribute('chunksUploaded')) {
|
||||||
$deviceDeleted = $deviceFiles->abort(
|
$deviceDeleted = $deviceForFiles->abort(
|
||||||
$file->getAttribute('path'),
|
$file->getAttribute('path'),
|
||||||
($file->getAttribute('metadata', [])['uploadId'] ?? '')
|
($file->getAttribute('metadata', [])['uploadId'] ?? '')
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$deviceDeleted = $deviceFiles->delete($file->getAttribute('path'));
|
$deviceDeleted = $deviceForFiles->delete($file->getAttribute('path'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($deviceDeleted) {
|
if ($deviceDeleted) {
|
||||||
|
|
|
@ -1383,19 +1383,19 @@ App::setResource('cache', function (Group $pools) {
|
||||||
return new Cache(new Sharding($adapters));
|
return new Cache(new Sharding($adapters));
|
||||||
}, ['pools']);
|
}, ['pools']);
|
||||||
|
|
||||||
App::setResource('deviceLocal', function () {
|
App::setResource('deviceForLocal', function () {
|
||||||
return new Local();
|
return new Local();
|
||||||
});
|
});
|
||||||
|
|
||||||
App::setResource('deviceFiles', function ($project) {
|
App::setResource('deviceForFiles', function ($project) {
|
||||||
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
||||||
}, ['project']);
|
}, ['project']);
|
||||||
|
|
||||||
App::setResource('deviceFunctions', function ($project) {
|
App::setResource('deviceForFunctions', function ($project) {
|
||||||
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
|
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
|
||||||
}, ['project']);
|
}, ['project']);
|
||||||
|
|
||||||
App::setResource('deviceBuilds', function ($project) {
|
App::setResource('deviceForBuilds', function ($project) {
|
||||||
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
||||||
}, ['project']);
|
}, ['project']);
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ use Utopia\Logger\Log;
|
||||||
use Utopia\Logger\Logger;
|
use Utopia\Logger\Logger;
|
||||||
use Utopia\Pools\Group;
|
use Utopia\Pools\Group;
|
||||||
use Utopia\Queue\Connection;
|
use Utopia\Queue\Connection;
|
||||||
|
use Utopia\Storage\Device\Local;
|
||||||
|
|
||||||
Authorization::disable();
|
Authorization::disable();
|
||||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||||
|
@ -201,22 +202,26 @@ Server::setResource('pools', function (Registry $register) {
|
||||||
return $register->get('pools');
|
return $register->get('pools');
|
||||||
}, ['register']);
|
}, ['register']);
|
||||||
|
|
||||||
Server::setResource('functionsDevice', function (Document $project) {
|
Server::setResource('deviceForFunctions', function (Document $project) {
|
||||||
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
|
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
|
||||||
}, ['project']);
|
}, ['project']);
|
||||||
|
|
||||||
Server::setResource('filesDevice', function (Document $project) {
|
Server::setResource('deviceForFiles', function (Document $project) {
|
||||||
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
||||||
}, ['project']);
|
}, ['project']);
|
||||||
|
|
||||||
Server::setResource('buildsDevice', function (Document $project) {
|
Server::setResource('deviceForBuilds', function (Document $project) {
|
||||||
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
|
||||||
}, ['project']);
|
}, ['project']);
|
||||||
|
|
||||||
Server::setResource('cacheDevice', function (Document $project) {
|
Server::setResource('deviceForCache', function (Document $project) {
|
||||||
return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
|
return getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId());
|
||||||
}, ['project']);
|
}, ['project']);
|
||||||
|
|
||||||
|
Server::setResource('deviceForLocalFiles', function (Document $project) {
|
||||||
|
return new Local(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
|
||||||
|
}, ['project']);
|
||||||
|
|
||||||
$pools = $register->get('pools');
|
$pools = $register->get('pools');
|
||||||
$platform = new Appwrite();
|
$platform = new Appwrite();
|
||||||
$args = $_SERVER['argv'];
|
$args = $_SERVER['argv'];
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
"utopia-php/image": "0.6.*",
|
"utopia-php/image": "0.6.*",
|
||||||
"utopia-php/locale": "0.4.*",
|
"utopia-php/locale": "0.4.*",
|
||||||
"utopia-php/logger": "0.3.*",
|
"utopia-php/logger": "0.3.*",
|
||||||
"utopia-php/messaging": "0.9.*",
|
"utopia-php/messaging": "0.10.*",
|
||||||
"utopia-php/migration": "0.3.*",
|
"utopia-php/migration": "0.3.*",
|
||||||
"utopia-php/orchestration": "0.9.*",
|
"utopia-php/orchestration": "0.9.*",
|
||||||
"utopia-php/platform": "0.5.*",
|
"utopia-php/platform": "0.5.*",
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
"utopia-php/websocket": "0.1.*",
|
"utopia-php/websocket": "0.1.*",
|
||||||
"matomo/device-detector": "6.1.*",
|
"matomo/device-detector": "6.1.*",
|
||||||
"dragonmantank/cron-expression": "3.3.2",
|
"dragonmantank/cron-expression": "3.3.2",
|
||||||
"phpmailer/phpmailer": "6.8.0",
|
"phpmailer/phpmailer": "6.9.1",
|
||||||
"chillerlan/php-qrcode": "4.3.4",
|
"chillerlan/php-qrcode": "4.3.4",
|
||||||
"adhocore/jwt": "1.1.2",
|
"adhocore/jwt": "1.1.2",
|
||||||
"spomky-labs/otphp": "^10.0",
|
"spomky-labs/otphp": "^10.0",
|
||||||
|
|
63
composer.lock
generated
63
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "609062319cc652e2760367f39604ac77",
|
"content-hash": "a65e4309e3fd851aec97be2cf5b83cb4",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "adhocore/jwt",
|
"name": "adhocore/jwt",
|
||||||
|
@ -885,16 +885,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpmailer/phpmailer",
|
"name": "phpmailer/phpmailer",
|
||||||
"version": "v6.8.0",
|
"version": "v6.9.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||||
"reference": "df16b615e371d81fb79e506277faea67a1be18f1"
|
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/df16b615e371d81fb79e506277faea67a1be18f1",
|
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18",
|
||||||
"reference": "df16b615e371d81fb79e506277faea67a1be18f1",
|
"reference": "039de174cd9c17a8389754d3b877a2ed22743e18",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -904,16 +904,17 @@
|
||||||
"php": ">=5.5.0"
|
"php": ">=5.5.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.2",
|
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
|
||||||
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
"doctrine/annotations": "^1.2.6 || ^1.13.3",
|
||||||
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
"php-parallel-lint/php-console-highlighter": "^1.0.0",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
"php-parallel-lint/php-parallel-lint": "^1.3.2",
|
||||||
"phpcompatibility/php-compatibility": "^9.3.5",
|
"phpcompatibility/php-compatibility": "^9.3.5",
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
"squizlabs/php_codesniffer": "^3.7.1",
|
"squizlabs/php_codesniffer": "^3.7.2",
|
||||||
"yoast/phpunit-polyfills": "^1.0.4"
|
"yoast/phpunit-polyfills": "^1.0.4"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
|
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
|
||||||
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
|
||||||
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
|
||||||
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
|
||||||
|
@ -953,7 +954,7 @@
|
||||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
|
||||||
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0"
|
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -961,7 +962,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-03-06T14:43:22+00:00"
|
"time": "2023-11-25T22:23:28+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "spomky-labs/otphp",
|
"name": "spomky-labs/otphp",
|
||||||
|
@ -1911,28 +1912,28 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/messaging",
|
"name": "utopia-php/messaging",
|
||||||
"version": "0.9.1",
|
"version": "0.10.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/utopia-php/messaging.git",
|
"url": "https://github.com/utopia-php/messaging.git",
|
||||||
"reference": "7beec07684e9e1dfcf4ab5b1ba731fa396dccbdf"
|
"reference": "71dce00ad43eb278a877cb2c329f7b8d677adfeb"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/7beec07684e9e1dfcf4ab5b1ba731fa396dccbdf",
|
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/71dce00ad43eb278a877cb2c329f7b8d677adfeb",
|
||||||
"reference": "7beec07684e9e1dfcf4ab5b1ba731fa396dccbdf",
|
"reference": "71dce00ad43eb278a877cb2c329f7b8d677adfeb",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"ext-openssl": "*",
|
"ext-openssl": "*",
|
||||||
"php": ">=8.0.0"
|
"php": ">=8.0.0",
|
||||||
|
"phpmailer/phpmailer": "6.9.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"laravel/pint": "1.13.*",
|
"laravel/pint": "1.13.11",
|
||||||
"phpmailer/phpmailer": "6.8.*",
|
"phpstan/phpstan": "1.10.58",
|
||||||
"phpstan/phpstan": "1.10.*",
|
"phpunit/phpunit": "10.5.10"
|
||||||
"phpunit/phpunit": "9.6.10"
|
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -1955,9 +1956,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/utopia-php/messaging/issues",
|
"issues": "https://github.com/utopia-php/messaging/issues",
|
||||||
"source": "https://github.com/utopia-php/messaging/tree/0.9.1"
|
"source": "https://github.com/utopia-php/messaging/tree/0.10.0"
|
||||||
},
|
},
|
||||||
"time": "2024-02-15T03:44:44+00:00"
|
"time": "2024-02-20T07:30:15+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/migration",
|
"name": "utopia-php/migration",
|
||||||
|
@ -3409,16 +3410,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpdocumentor/type-resolver",
|
"name": "phpdocumentor/type-resolver",
|
||||||
"version": "1.8.0",
|
"version": "1.8.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
||||||
"reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc"
|
"reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fad452781b3d774e3337b0c0b245dd8e5a4455fc",
|
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353",
|
||||||
"reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc",
|
"reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -3461,9 +3462,9 @@
|
||||||
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
|
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
|
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
|
||||||
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.0"
|
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.1"
|
||||||
},
|
},
|
||||||
"time": "2024-01-11T11:49:22+00:00"
|
"time": "2024-01-18T19:15:27+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpspec/prophecy",
|
"name": "phpspec/prophecy",
|
||||||
|
@ -5019,16 +5020,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "squizlabs/php_codesniffer",
|
"name": "squizlabs/php_codesniffer",
|
||||||
"version": "3.8.1",
|
"version": "3.9.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
|
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
|
||||||
"reference": "14f5fff1e64118595db5408e946f3a22c75807f7"
|
"reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7",
|
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b",
|
||||||
"reference": "14f5fff1e64118595db5408e946f3a22c75807f7",
|
"reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -5095,7 +5096,7 @@
|
||||||
"type": "open_collective"
|
"type": "open_collective"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-01-11T20:47:48+00:00"
|
"time": "2024-02-16T15:06:51+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "swoole/ide-helper",
|
"name": "swoole/ide-helper",
|
||||||
|
|
|
@ -50,9 +50,9 @@ class Builds extends Action
|
||||||
->inject('queueForUsage')
|
->inject('queueForUsage')
|
||||||
->inject('cache')
|
->inject('cache')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('functionsDevice')
|
->inject('deviceForFunctions')
|
||||||
->inject('log')
|
->inject('log')
|
||||||
->callback(fn($message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $usage, Cache $cache, Database $dbForProject, Device $functionsDevice, Log $log) => $this->action($message, $dbForConsole, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $functionsDevice, $log));
|
->callback(fn($message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $usage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log) => $this->action($message, $dbForConsole, $queueForEvents, $queueForFunctions, $usage, $cache, $dbForProject, $deviceForFunctions, $log));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,12 +63,12 @@ class Builds extends Action
|
||||||
* @param Usage $queueForUsage
|
* @param Usage $queueForUsage
|
||||||
* @param Cache $cache
|
* @param Cache $cache
|
||||||
* @param Database $dbForProject
|
* @param Database $dbForProject
|
||||||
* @param Device $functionsDevice
|
* @param Device $deviceForFunctions
|
||||||
* @param Log $log
|
* @param Log $log
|
||||||
* @return void
|
* @return void
|
||||||
* @throws \Utopia\Database\Exception
|
* @throws \Utopia\Database\Exception
|
||||||
*/
|
*/
|
||||||
public function action(Message $message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $functionsDevice, Log $log): void
|
public function action(Message $message, Database $dbForConsole, Event $queueForEvents, Func $queueForFunctions, Usage $queueForUsage, Cache $cache, Database $dbForProject, Device $deviceForFunctions, Log $log): void
|
||||||
{
|
{
|
||||||
$payload = $message->getPayload() ?? [];
|
$payload = $message->getPayload() ?? [];
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class Builds extends Action
|
||||||
case BUILD_TYPE_RETRY:
|
case BUILD_TYPE_RETRY:
|
||||||
Console::info('Creating build for deployment: ' . $deployment->getId());
|
Console::info('Creating build for deployment: ' . $deployment->getId());
|
||||||
$github = new GitHub($cache);
|
$github = new GitHub($cache);
|
||||||
$this->buildDeployment($functionsDevice, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForConsole, $dbForProject, $github, $project, $resource, $deployment, $template, $log);
|
$this->buildDeployment($deviceForFunctions, $queueForFunctions, $queueForEvents, $queueForUsage, $dbForConsole, $dbForProject, $github, $project, $resource, $deployment, $template, $log);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -99,7 +99,7 @@ class Builds extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Device $functionsDevice
|
* @param Device $deviceForFunctions
|
||||||
* @param Func $queueForFunctions
|
* @param Func $queueForFunctions
|
||||||
* @param Event $queueForEvents
|
* @param Event $queueForEvents
|
||||||
* @param Usage $queueForUsage
|
* @param Usage $queueForUsage
|
||||||
|
@ -115,7 +115,7 @@ class Builds extends Action
|
||||||
* @throws \Utopia\Database\Exception
|
* @throws \Utopia\Database\Exception
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
protected function buildDeployment(Device $functionsDevice, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Database $dbForConsole, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, Log $log): void
|
protected function buildDeployment(Device $deviceForFunctions, Func $queueForFunctions, Event $queueForEvents, Usage $queueForUsage, Database $dbForConsole, Database $dbForProject, GitHub $github, Document $project, Document $function, Document $deployment, Document $template, Log $log): void
|
||||||
{
|
{
|
||||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ class Builds extends Action
|
||||||
'path' => '',
|
'path' => '',
|
||||||
'runtime' => $function->getAttribute('runtime'),
|
'runtime' => $function->getAttribute('runtime'),
|
||||||
'source' => $deployment->getAttribute('path', ''),
|
'source' => $deployment->getAttribute('path', ''),
|
||||||
'sourceType' => strtolower($functionsDevice->getType()),
|
'sourceType' => strtolower($deviceForFunctions->getType()),
|
||||||
'logs' => '',
|
'logs' => '',
|
||||||
'endTime' => null,
|
'endTime' => null,
|
||||||
'duration' => 0,
|
'duration' => 0,
|
||||||
|
@ -311,8 +311,8 @@ class Builds extends Action
|
||||||
|
|
||||||
Console::execute('tar --exclude code.tar.gz -czf ' . $tmpPathFile . ' -C /tmp/builds/' . \escapeshellcmd($buildId) . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory) . ' .', '', $stdout, $stderr);
|
Console::execute('tar --exclude code.tar.gz -czf ' . $tmpPathFile . ' -C /tmp/builds/' . \escapeshellcmd($buildId) . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory) . ' .', '', $stdout, $stderr);
|
||||||
|
|
||||||
$path = $functionsDevice->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
$path = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
||||||
$result = $localDevice->transfer($tmpPathFile, $path, $functionsDevice);
|
$result = $localDevice->transfer($tmpPathFile, $path, $deviceForFunctions);
|
||||||
|
|
||||||
if (!$result) {
|
if (!$result) {
|
||||||
throw new \Exception("Unable to move file");
|
throw new \Exception("Unable to move file");
|
||||||
|
|
|
@ -44,22 +44,22 @@ class Deletes extends Action
|
||||||
->inject('message')
|
->inject('message')
|
||||||
->inject('dbForConsole')
|
->inject('dbForConsole')
|
||||||
->inject('getProjectDB')
|
->inject('getProjectDB')
|
||||||
->inject('filesDevice')
|
->inject('deviceForFiles')
|
||||||
->inject('functionsDevice')
|
->inject('deviceForFunctions')
|
||||||
->inject('buildsDevice')
|
->inject('deviceForBuilds')
|
||||||
->inject('cacheDevice')
|
->inject('deviceForCache')
|
||||||
->inject('abuseRetention')
|
->inject('abuseRetention')
|
||||||
->inject('executionRetention')
|
->inject('executionRetention')
|
||||||
->inject('auditRetention')
|
->inject('auditRetention')
|
||||||
->inject('log')
|
->inject('log')
|
||||||
->callback(fn ($message, $dbForConsole, callable $getProjectDB, Device $filesDevice, Device $functionsDevice, Device $buildsDevice, Device $cacheDevice, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log) => $this->action($message, $dbForConsole, $getProjectDB, $filesDevice, $functionsDevice, $buildsDevice, $cacheDevice, $abuseRetention, $executionRetention, $auditRetention, $log));
|
->callback(fn ($message, $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log) => $this->action($message, $dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $abuseRetention, $executionRetention, $auditRetention, $log));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function action(Message $message, Database $dbForConsole, callable $getProjectDB, Device $filesDevice, Device $functionsDevice, Device $buildsDevice, Device $cacheDevice, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log): void
|
public function action(Message $message, Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, string $abuseRetention, string $executionRetention, string $auditRetention, Log $log): void
|
||||||
{
|
{
|
||||||
$payload = $message->getPayload() ?? [];
|
$payload = $message->getPayload() ?? [];
|
||||||
|
|
||||||
|
@ -87,13 +87,13 @@ class Deletes extends Action
|
||||||
$this->deleteCollection($getProjectDB, $document, $project);
|
$this->deleteCollection($getProjectDB, $document, $project);
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_PROJECTS:
|
case DELETE_TYPE_PROJECTS:
|
||||||
$this->deleteProject($dbForConsole, $getProjectDB, $filesDevice, $functionsDevice, $buildsDevice, $cacheDevice, $document);
|
$this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $document);
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_FUNCTIONS:
|
case DELETE_TYPE_FUNCTIONS:
|
||||||
$this->deleteFunction($dbForConsole, $getProjectDB, $functionsDevice, $buildsDevice, $document, $project);
|
$this->deleteFunction($dbForConsole, $getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project);
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_DEPLOYMENTS:
|
case DELETE_TYPE_DEPLOYMENTS:
|
||||||
$this->deleteDeployment($getProjectDB, $functionsDevice, $buildsDevice, $document, $project);
|
$this->deleteDeployment($getProjectDB, $deviceForFunctions, $deviceForBuilds, $document, $project);
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_USERS:
|
case DELETE_TYPE_USERS:
|
||||||
$this->deleteUser($getProjectDB, $document, $project);
|
$this->deleteUser($getProjectDB, $document, $project);
|
||||||
|
@ -101,11 +101,11 @@ class Deletes extends Action
|
||||||
case DELETE_TYPE_TEAMS:
|
case DELETE_TYPE_TEAMS:
|
||||||
$this->deleteMemberships($getProjectDB, $document, $project);
|
$this->deleteMemberships($getProjectDB, $document, $project);
|
||||||
if ($project->getId() === 'console') {
|
if ($project->getId() === 'console') {
|
||||||
$this->deleteProjectsByTeam($dbForConsole, $getProjectDB, $filesDevice, $functionsDevice, $buildsDevice, $cacheDevice, $document);
|
$this->deleteProjectsByTeam($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $document);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_BUCKETS:
|
case DELETE_TYPE_BUCKETS:
|
||||||
$this->deleteBucket($getProjectDB, $filesDevice, $document, $project);
|
$this->deleteBucket($getProjectDB, $deviceForFiles, $document, $project);
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_INSTALLATIONS:
|
case DELETE_TYPE_INSTALLATIONS:
|
||||||
$this->deleteInstallation($dbForConsole, $getProjectDB, $document, $project);
|
$this->deleteInstallation($dbForConsole, $getProjectDB, $document, $project);
|
||||||
|
@ -511,14 +511,14 @@ class Deletes extends Action
|
||||||
* @throws Restricted
|
* @throws Restricted
|
||||||
* @throws Structure
|
* @throws Structure
|
||||||
*/
|
*/
|
||||||
private function deleteProjectsByTeam(Database $dbForConsole, callable $getProjectDB, Device $filesDevice, Device $functionsDevice, Device $buildsDevice, Device $cacheDevice, Document $document): void
|
private function deleteProjectsByTeam(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void
|
||||||
{
|
{
|
||||||
|
|
||||||
$projects = $dbForConsole->find('projects', [
|
$projects = $dbForConsole->find('projects', [
|
||||||
Query::equal('teamInternalId', [$document->getInternalId()])
|
Query::equal('teamInternalId', [$document->getInternalId()])
|
||||||
]);
|
]);
|
||||||
foreach ($projects as $project) {
|
foreach ($projects as $project) {
|
||||||
$this->deleteProject($dbForConsole, $getProjectDB, $filesDevice, $functionsDevice, $buildsDevice, $cacheDevice, $project);
|
$this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $project);
|
||||||
$dbForConsole->deleteDocument('projects', $project->getId());
|
$dbForConsole->deleteDocument('projects', $project->getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -526,17 +526,17 @@ class Deletes extends Action
|
||||||
/**
|
/**
|
||||||
* @param Database $dbForConsole
|
* @param Database $dbForConsole
|
||||||
* @param callable $getProjectDB
|
* @param callable $getProjectDB
|
||||||
* @param Device $filesDevice
|
* @param Device $deviceForFiles
|
||||||
* @param Device $functionsDevice
|
* @param Device $deviceForFunctions
|
||||||
* @param Device $buildsDevice
|
* @param Device $deviceForBuilds
|
||||||
* @param Device $cacheDevice
|
* @param Device $deviceForCache
|
||||||
* @param Document $document
|
* @param Document $document
|
||||||
* @return void
|
* @return void
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @throws Authorization
|
* @throws Authorization
|
||||||
* @throws \Utopia\Database\Exception
|
* @throws \Utopia\Database\Exception
|
||||||
*/
|
*/
|
||||||
private function deleteProject(Database $dbForConsole, callable $getProjectDB, Device $filesDevice, Device $functionsDevice, Device $buildsDevice, Device $cacheDevice, Document $document): void
|
private function deleteProject(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void
|
||||||
{
|
{
|
||||||
$projectId = $document->getId();
|
$projectId = $document->getId();
|
||||||
$projectInternalId = $document->getInternalId();
|
$projectInternalId = $document->getInternalId();
|
||||||
|
@ -602,10 +602,10 @@ class Deletes extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all storage directories
|
// Delete all storage directories
|
||||||
$filesDevice->delete($filesDevice->getRoot(), true);
|
$deviceForFiles->delete($deviceForFiles->getRoot(), true);
|
||||||
$functionsDevice->delete($functionsDevice->getRoot(), true);
|
$deviceForFunctions->delete($deviceForFunctions->getRoot(), true);
|
||||||
$buildsDevice->delete($buildsDevice->getRoot(), true);
|
$deviceForBuilds->delete($deviceForBuilds->getRoot(), true);
|
||||||
$cacheDevice->delete($cacheDevice->getRoot(), true);
|
$deviceForCache->delete($deviceForCache->getRoot(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -767,14 +767,14 @@ class Deletes extends Action
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callable $getProjectDB
|
* @param callable $getProjectDB
|
||||||
* @param Device $functionsDevice
|
* @param Device $deviceForFunctions
|
||||||
* @param Device $buildsDevice
|
* @param Device $deviceForBuilds
|
||||||
* @param Document $document function document
|
* @param Document $document function document
|
||||||
* @param Document $project
|
* @param Document $project
|
||||||
* @return void
|
* @return void
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
private function deleteFunction(Database $dbForConsole, callable $getProjectDB, Device $functionsDevice, Device $buildsDevice, Document $document, Document $project): void
|
private function deleteFunction(Database $dbForConsole, callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project): void
|
||||||
{
|
{
|
||||||
$projectId = $project->getId();
|
$projectId = $project->getId();
|
||||||
$dbForProject = $getProjectDB($project);
|
$dbForProject = $getProjectDB($project);
|
||||||
|
@ -810,9 +810,9 @@ class Deletes extends Action
|
||||||
$deploymentInternalIds = [];
|
$deploymentInternalIds = [];
|
||||||
$this->deleteByGroup('deployments', [
|
$this->deleteByGroup('deployments', [
|
||||||
Query::equal('resourceInternalId', [$functionInternalId])
|
Query::equal('resourceInternalId', [$functionInternalId])
|
||||||
], $dbForProject, function (Document $document) use ($functionsDevice, &$deploymentInternalIds) {
|
], $dbForProject, function (Document $document) use ($deviceForFunctions, &$deploymentInternalIds) {
|
||||||
$deploymentInternalIds[] = $document->getInternalId();
|
$deploymentInternalIds[] = $document->getInternalId();
|
||||||
$this->deleteDeploymentFiles($functionsDevice, $document);
|
$this->deleteDeploymentFiles($deviceForFunctions, $document);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -823,8 +823,8 @@ class Deletes extends Action
|
||||||
foreach ($deploymentInternalIds as $deploymentInternalId) {
|
foreach ($deploymentInternalIds as $deploymentInternalId) {
|
||||||
$this->deleteByGroup('builds', [
|
$this->deleteByGroup('builds', [
|
||||||
Query::equal('deploymentInternalId', [$deploymentInternalId])
|
Query::equal('deploymentInternalId', [$deploymentInternalId])
|
||||||
], $dbForProject, function (Document $document) use ($buildsDevice) {
|
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
|
||||||
$this->deleteBuildFiles($buildsDevice, $document);
|
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -924,14 +924,14 @@ class Deletes extends Action
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callable $getProjectDB
|
* @param callable $getProjectDB
|
||||||
* @param Device $functionsDevice
|
* @param Device $deviceForFunctions
|
||||||
* @param Device $buildsDevice
|
* @param Device $deviceForBuilds
|
||||||
* @param Document $document
|
* @param Document $document
|
||||||
* @param Document $project
|
* @param Document $project
|
||||||
* @return void
|
* @return void
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
private function deleteDeployment(callable $getProjectDB, Device $functionsDevice, Device $buildsDevice, Document $document, Document $project): void
|
private function deleteDeployment(callable $getProjectDB, Device $deviceForFunctions, Device $deviceForBuilds, Document $document, Document $project): void
|
||||||
{
|
{
|
||||||
$projectId = $project->getId();
|
$projectId = $project->getId();
|
||||||
$dbForProject = $getProjectDB($project);
|
$dbForProject = $getProjectDB($project);
|
||||||
|
@ -941,7 +941,7 @@ class Deletes extends Action
|
||||||
/**
|
/**
|
||||||
* Delete deployment files
|
* Delete deployment files
|
||||||
*/
|
*/
|
||||||
$this->deleteDeploymentFiles($functionsDevice, $document);
|
$this->deleteDeploymentFiles($deviceForFunctions, $document);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete builds
|
* Delete builds
|
||||||
|
@ -950,8 +950,8 @@ class Deletes extends Action
|
||||||
|
|
||||||
$this->deleteByGroup('builds', [
|
$this->deleteByGroup('builds', [
|
||||||
Query::equal('deploymentInternalId', [$deploymentInternalId])
|
Query::equal('deploymentInternalId', [$deploymentInternalId])
|
||||||
], $dbForProject, function (Document $document) use ($buildsDevice) {
|
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
|
||||||
$this->deleteBuildFiles($buildsDevice, $document);
|
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1095,18 +1095,18 @@ class Deletes extends Action
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callable $getProjectDB
|
* @param callable $getProjectDB
|
||||||
* @param Device $filesDevice
|
* @param Device $deviceForFiles
|
||||||
* @param Document $document
|
* @param Document $document
|
||||||
* @param Document $project
|
* @param Document $project
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function deleteBucket(callable $getProjectDB, Device $filesDevice, Document $document, Document $project): void
|
private function deleteBucket(callable $getProjectDB, Device $deviceForFiles, Document $document, Document $project): void
|
||||||
{
|
{
|
||||||
$dbForProject = $getProjectDB($project);
|
$dbForProject = $getProjectDB($project);
|
||||||
|
|
||||||
$dbForProject->deleteCollection('bucket_' . $document->getInternalId());
|
$dbForProject->deleteCollection('bucket_' . $document->getInternalId());
|
||||||
|
|
||||||
$filesDevice->deletePath($document->getId());
|
$deviceForFiles->deletePath($document->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
namespace Appwrite\Platform\Workers;
|
namespace Appwrite\Platform\Workers;
|
||||||
|
|
||||||
|
use Appwrite\Auth\Auth;
|
||||||
use Appwrite\Event\Usage;
|
use Appwrite\Event\Usage;
|
||||||
use Appwrite\Extend\Exception;
|
use Appwrite\Extend\Exception;
|
||||||
use Appwrite\Messaging\Status as MessageStatus;
|
use Appwrite\Messaging\Status as MessageStatus;
|
||||||
use Utopia\App;
|
use Utopia\App;
|
||||||
use Utopia\CLI\Console;
|
use Utopia\CLI\Console;
|
||||||
|
use Utopia\Config\Config;
|
||||||
|
use Utopia\Database\Validator\Authorization;
|
||||||
use Utopia\DSN\DSN;
|
use Utopia\DSN\DSN;
|
||||||
use Utopia\Database\Database;
|
use Utopia\Database\Database;
|
||||||
use Utopia\Database\DateTime;
|
use Utopia\Database\DateTime;
|
||||||
|
@ -29,10 +32,13 @@ use Utopia\Messaging\Adapter\SMS\Textmagic;
|
||||||
use Utopia\Messaging\Adapter\SMS\Twilio;
|
use Utopia\Messaging\Adapter\SMS\Twilio;
|
||||||
use Utopia\Messaging\Adapter\SMS\Vonage;
|
use Utopia\Messaging\Adapter\SMS\Vonage;
|
||||||
use Utopia\Messaging\Messages\Email;
|
use Utopia\Messaging\Messages\Email;
|
||||||
|
use Utopia\Messaging\Messages\Email\Attachment;
|
||||||
use Utopia\Messaging\Messages\Push;
|
use Utopia\Messaging\Messages\Push;
|
||||||
use Utopia\Messaging\Messages\SMS;
|
use Utopia\Messaging\Messages\SMS;
|
||||||
use Utopia\Platform\Action;
|
use Utopia\Platform\Action;
|
||||||
use Utopia\Queue\Message;
|
use Utopia\Queue\Message;
|
||||||
|
use Utopia\Storage\Device;
|
||||||
|
use Utopia\Storage\Storage;
|
||||||
|
|
||||||
use function Swoole\Coroutine\batch;
|
use function Swoole\Coroutine\batch;
|
||||||
|
|
||||||
|
@ -53,20 +59,29 @@ class Messaging extends Action
|
||||||
->inject('message')
|
->inject('message')
|
||||||
->inject('log')
|
->inject('log')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
|
->inject('deviceForFiles')
|
||||||
|
->inject('deviceForLocalFiles')
|
||||||
->inject('queueForUsage')
|
->inject('queueForUsage')
|
||||||
->callback(fn(Message $message, Log $log, Database $dbForProject, Usage $queueForUsage) => $this->action($message, $log, $dbForProject, $queueForUsage));
|
->callback(fn(Message $message, Log $log, Database $dbForProject, Device $deviceForFiles, Device $deviceForLocalFiles, Usage $queueForUsage) => $this->action($message, $log, $dbForProject, $deviceForFiles, $deviceForLocalFiles, $queueForUsage));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Message $message
|
* @param Message $message
|
||||||
* @param Log $log
|
* @param Log $log
|
||||||
* @param Database $dbForProject
|
* @param Database $dbForProject
|
||||||
|
* @param callable $getLocalCache
|
||||||
* @param Usage $queueForUsage
|
* @param Usage $queueForUsage
|
||||||
* @return void
|
* @return void
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function action(Message $message, Log $log, Database $dbForProject, Usage $queueForUsage): void
|
public function action(
|
||||||
{
|
Message $message,
|
||||||
|
Log $log,
|
||||||
|
Database $dbForProject,
|
||||||
|
Device $deviceForFiles,
|
||||||
|
Device $deviceForLocalFiles,
|
||||||
|
Usage $queueForUsage
|
||||||
|
): void {
|
||||||
$payload = $message->getPayload() ?? [];
|
$payload = $message->getPayload() ?? [];
|
||||||
|
|
||||||
if (empty($payload)) {
|
if (empty($payload)) {
|
||||||
|
@ -86,15 +101,19 @@ class Messaging extends Action
|
||||||
case MESSAGE_SEND_TYPE_EXTERNAL:
|
case MESSAGE_SEND_TYPE_EXTERNAL:
|
||||||
$message = $dbForProject->getDocument('messages', $payload['messageId']);
|
$message = $dbForProject->getDocument('messages', $payload['messageId']);
|
||||||
|
|
||||||
$this->sendExternalMessage($dbForProject, $message);
|
$this->sendExternalMessage($dbForProject, $message, $deviceForFiles, $deviceForLocalFiles,);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception('Unknown message type: ' . $type);
|
throw new Exception('Unknown message type: ' . $type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sendExternalMessage(Database $dbForProject, Document $message): void
|
private function sendExternalMessage(
|
||||||
{
|
Database $dbForProject,
|
||||||
|
Document $message,
|
||||||
|
Device $deviceForFiles,
|
||||||
|
Device $deviceForLocalFiles,
|
||||||
|
): void {
|
||||||
$topicIds = $message->getAttribute('topics', []);
|
$topicIds = $message->getAttribute('topics', []);
|
||||||
$targetIds = $message->getAttribute('targets', []);
|
$targetIds = $message->getAttribute('targets', []);
|
||||||
$userIds = $message->getAttribute('users', []);
|
$userIds = $message->getAttribute('users', []);
|
||||||
|
@ -198,8 +217,8 @@ class Messaging extends Action
|
||||||
/**
|
/**
|
||||||
* @var array<array> $results
|
* @var array<array> $results
|
||||||
*/
|
*/
|
||||||
$results = batch(\array_map(function ($providerId) use ($identifiers, $providers, $fallback, $message, $dbForProject) {
|
$results = batch(\array_map(function ($providerId) use ($identifiers, $providers, $fallback, $message, $dbForProject, $deviceForFiles, $deviceForLocalFiles) {
|
||||||
return function () use ($providerId, $identifiers, $providers, $fallback, $message, $dbForProject) {
|
return function () use ($providerId, $identifiers, $providers, $fallback, $message, $dbForProject, $deviceForFiles, $deviceForLocalFiles) {
|
||||||
if (\array_key_exists($providerId, $providers)) {
|
if (\array_key_exists($providerId, $providers)) {
|
||||||
$provider = $providers[$providerId];
|
$provider = $providers[$providerId];
|
||||||
} else {
|
} else {
|
||||||
|
@ -225,8 +244,8 @@ class Messaging extends Action
|
||||||
$batches = \array_chunk($identifiers, $maxBatchSize);
|
$batches = \array_chunk($identifiers, $maxBatchSize);
|
||||||
$batchIndex = 0;
|
$batchIndex = 0;
|
||||||
|
|
||||||
return batch(\array_map(function ($batch) use ($message, $provider, $adapter, &$batchIndex, $dbForProject) {
|
return batch(\array_map(function ($batch) use ($message, $provider, $adapter, &$batchIndex, $dbForProject, $deviceForFiles, $deviceForLocalFiles) {
|
||||||
return function () use ($batch, $message, $provider, $adapter, &$batchIndex, $dbForProject) {
|
return function () use ($batch, $message, $provider, $adapter, &$batchIndex, $dbForProject, $deviceForFiles, $deviceForLocalFiles) {
|
||||||
$deliveredTotal = 0;
|
$deliveredTotal = 0;
|
||||||
$deliveryErrors = [];
|
$deliveryErrors = [];
|
||||||
$messageData = clone $message;
|
$messageData = clone $message;
|
||||||
|
@ -235,7 +254,7 @@ class Messaging extends Action
|
||||||
$data = match ($provider->getAttribute('type')) {
|
$data = match ($provider->getAttribute('type')) {
|
||||||
MESSAGE_TYPE_SMS => $this->buildSmsMessage($messageData, $provider),
|
MESSAGE_TYPE_SMS => $this->buildSmsMessage($messageData, $provider),
|
||||||
MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData),
|
MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData),
|
||||||
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($dbForProject, $messageData, $provider),
|
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($dbForProject, $messageData, $provider, $deviceForFiles, $deviceForLocalFiles),
|
||||||
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
|
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -309,6 +328,37 @@ class Messaging extends Action
|
||||||
$message->setAttribute('deliveredAt', DateTime::now());
|
$message->setAttribute('deliveredAt', DateTime::now());
|
||||||
|
|
||||||
$dbForProject->updateDocument('messages', $message->getId(), $message);
|
$dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||||
|
|
||||||
|
// Delete any attachments that were downloaded to the local cache
|
||||||
|
if ($provider->getAttribute('type') === MESSAGE_TYPE_EMAIL) {
|
||||||
|
if ($deviceForFiles->getType() === Storage::DEVICE_LOCAL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $message->getAttribute('data');
|
||||||
|
$attachments = $data['attachments'] ?? [];
|
||||||
|
|
||||||
|
foreach ($attachments as $attachment) {
|
||||||
|
$bucketId = $attachment['bucketId'];
|
||||||
|
$fileId = $attachment['fileId'];
|
||||||
|
|
||||||
|
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||||
|
if ($bucket->isEmpty()) {
|
||||||
|
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||||
|
if ($file->isEmpty()) {
|
||||||
|
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = $file->getAttribute('path', '');
|
||||||
|
|
||||||
|
if ($deviceForLocalFiles->exists($path)) {
|
||||||
|
$deviceForLocalFiles->delete($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sendInternalSMSMessage(Document $message, Document $project, array $recipients, Usage $queueForUsage, Log $log): void
|
private function sendInternalSMSMessage(Document $message, Document $project, array $recipients, Usage $queueForUsage, Log $log): void
|
||||||
|
@ -458,8 +508,13 @@ class Messaging extends Action
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildEmailMessage(Database $dbForProject, Document $message, Document $provider): Email
|
private function buildEmailMessage(
|
||||||
{
|
Database $dbForProject,
|
||||||
|
Document $message,
|
||||||
|
Document $provider,
|
||||||
|
Device $deviceForFiles,
|
||||||
|
Device $deviceForLocalFiles,
|
||||||
|
): Email {
|
||||||
$fromName = $provider['options']['fromName'] ?? null;
|
$fromName = $provider['options']['fromName'] ?? null;
|
||||||
$fromEmail = $provider['options']['fromEmail'] ?? null;
|
$fromEmail = $provider['options']['fromEmail'] ?? null;
|
||||||
$replyToEmail = $provider['options']['replyToEmail'] ?? null;
|
$replyToEmail = $provider['options']['replyToEmail'] ?? null;
|
||||||
|
@ -469,8 +524,9 @@ class Messaging extends Action
|
||||||
$bccTargets = $data['bcc'] ?? [];
|
$bccTargets = $data['bcc'] ?? [];
|
||||||
$cc = [];
|
$cc = [];
|
||||||
$bcc = [];
|
$bcc = [];
|
||||||
|
$attachments = $data['attachments'] ?? [];
|
||||||
|
|
||||||
if (\count($ccTargets) > 0) {
|
if (!empty($ccTargets)) {
|
||||||
$ccTargets = $dbForProject->find('targets', [
|
$ccTargets = $dbForProject->find('targets', [
|
||||||
Query::equal('$id', $ccTargets),
|
Query::equal('$id', $ccTargets),
|
||||||
Query::limit(\count($ccTargets)),
|
Query::limit(\count($ccTargets)),
|
||||||
|
@ -480,7 +536,7 @@ class Messaging extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\count($bccTargets) > 0) {
|
if (!empty($bccTargets)) {
|
||||||
$bccTargets = $dbForProject->find('targets', [
|
$bccTargets = $dbForProject->find('targets', [
|
||||||
Query::equal('$id', $bccTargets),
|
Query::equal('$id', $bccTargets),
|
||||||
Query::limit(\count($bccTargets)),
|
Query::limit(\count($bccTargets)),
|
||||||
|
@ -490,12 +546,64 @@ class Messaging extends Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($attachments)) {
|
||||||
|
foreach ($attachments as &$attachment) {
|
||||||
|
$bucketId = $attachment['bucketId'];
|
||||||
|
$fileId = $attachment['fileId'];
|
||||||
|
|
||||||
|
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||||
|
if ($bucket->isEmpty()) {
|
||||||
|
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||||
|
if ($file->isEmpty()) {
|
||||||
|
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimes = Config::getParam('storage-mimes');
|
||||||
|
$path = $file->getAttribute('path', '');
|
||||||
|
|
||||||
|
if (!$deviceForFiles->exists($path)) {
|
||||||
|
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$contentType = 'text/plain';
|
||||||
|
|
||||||
|
if (\in_array($file->getAttribute('mimeType'), $mimes)) {
|
||||||
|
$contentType = $file->getAttribute('mimeType');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($deviceForFiles->getType() !== Storage::DEVICE_LOCAL) {
|
||||||
|
$deviceForFiles->transfer($path, $path, $deviceForLocalFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
$attachment = new Attachment(
|
||||||
|
$file->getAttribute('name'),
|
||||||
|
$path,
|
||||||
|
$contentType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$to = $message['to'];
|
$to = $message['to'];
|
||||||
$subject = $data['subject'];
|
$subject = $data['subject'];
|
||||||
$content = $data['content'];
|
$content = $data['content'];
|
||||||
$html = $data['html'] ?? false;
|
$html = $data['html'] ?? false;
|
||||||
|
|
||||||
return new Email($to, $subject, $content, $fromName, $fromEmail, $replyToName, $replyToEmail, $cc, $bcc, null, $html);
|
return new Email(
|
||||||
|
$to,
|
||||||
|
$subject,
|
||||||
|
$content,
|
||||||
|
$fromName,
|
||||||
|
$fromEmail,
|
||||||
|
$replyToName,
|
||||||
|
$replyToEmail,
|
||||||
|
$cc,
|
||||||
|
$bcc,
|
||||||
|
$attachments,
|
||||||
|
$html
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildSmsMessage(Document $message, Document $provider): SMS
|
private function buildSmsMessage(Document $message, Document $provider): SMS
|
||||||
|
@ -504,7 +612,11 @@ class Messaging extends Action
|
||||||
$content = $message['data']['content'];
|
$content = $message['data']['content'];
|
||||||
$from = $provider['options']['from'];
|
$from = $provider['options']['from'];
|
||||||
|
|
||||||
return new SMS($to, $content, $from);
|
return new SMS(
|
||||||
|
$to,
|
||||||
|
$content,
|
||||||
|
$from
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildPushMessage(Document $message): Push
|
private function buildPushMessage(Document $message): Push
|
||||||
|
@ -520,6 +632,17 @@ class Messaging extends Action
|
||||||
$tag = $message['data']['tag'] ?? null;
|
$tag = $message['data']['tag'] ?? null;
|
||||||
$badge = $message['data']['badge'] ?? null;
|
$badge = $message['data']['badge'] ?? null;
|
||||||
|
|
||||||
return new Push($to, $title, $body, $data, $action, $sound, $icon, $color, $tag, $badge);
|
return new Push(
|
||||||
|
$to,
|
||||||
|
$title,
|
||||||
|
$body,
|
||||||
|
$data,
|
||||||
|
$action,
|
||||||
|
$sound,
|
||||||
|
$icon,
|
||||||
|
$color,
|
||||||
|
$tag,
|
||||||
|
$badge
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -451,6 +451,10 @@ class OpenAPI3 extends Format
|
||||||
$node['format'] = 'int32';
|
$node['format'] = 'int32';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'Appwrite\Utopia\Database\Validator\CompoundUID':
|
||||||
|
$node['schema']['type'] = $validator->getType();
|
||||||
|
$node['schema']['x-example'] = '[ID1:ID2]';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$node['schema']['type'] = 'string';
|
$node['schema']['type'] = 'string';
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -446,6 +446,10 @@ class Swagger2 extends Format
|
||||||
$node['format'] = 'int32';
|
$node['format'] = 'int32';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'Appwrite\Utopia\Database\Validator\CompoundUID':
|
||||||
|
$node['type'] = $validator->getType();
|
||||||
|
$node['x-example'] = '[ID1:ID2]';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
$node['type'] = 'string';
|
$node['type'] = 'string';
|
||||||
break;
|
break;
|
||||||
|
|
58
src/Appwrite/Utopia/Database/Validator/CompoundUID.php
Normal file
58
src/Appwrite/Utopia/Database/Validator/CompoundUID.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Utopia\Database\Validator;
|
||||||
|
|
||||||
|
use Utopia\Database\Validator\UID;
|
||||||
|
use Utopia\Validator;
|
||||||
|
|
||||||
|
class CompoundUID extends Validator
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Must consist of multiple UIDs separated by a colon. Each UID must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, and underscore. Can\'t start with a special char.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isArray(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValid($value): bool
|
||||||
|
{
|
||||||
|
if (!\is_string($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = static::parse($value);
|
||||||
|
|
||||||
|
if (\count($ids) < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$validator = new UID();
|
||||||
|
if (!$validator->isValid($id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return self::TYPE_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function parse(string $key): array
|
||||||
|
{
|
||||||
|
$parts = \explode(':', $key);
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$result[] = $part;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
37
tests/unit/Utopia/Database/Validator/CompoundUIDTest.php
Normal file
37
tests/unit/Utopia/Database/Validator/CompoundUIDTest.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Utopia\Database\Validator;
|
||||||
|
|
||||||
|
use Appwrite\Utopia\Database\Validator\CompoundUID;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class CompoundUIDTest extends TestCase
|
||||||
|
{
|
||||||
|
protected ?CompoundUID $object = null;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$this->object = new CompoundUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValues(): void
|
||||||
|
{
|
||||||
|
$this->assertEquals($this->object->isValid('123:456'), true);
|
||||||
|
$this->assertEquals($this->object->isValid('123'), false);
|
||||||
|
$this->assertEquals($this->object->isValid('123:_456'), false);
|
||||||
|
$this->assertEquals($this->object->isValid('dasda asdasd'), false);
|
||||||
|
$this->assertEquals($this->object->isValid('dasda:asdasd'), true);
|
||||||
|
$this->assertEquals($this->object->isValid('_asdas:dasdas'), false);
|
||||||
|
$this->assertEquals($this->object->isValid('as$$5da:sdasdas'), false);
|
||||||
|
$this->assertEquals($this->object->isValid(false), false);
|
||||||
|
$this->assertEquals($this->object->isValid(null), false);
|
||||||
|
$this->assertEquals($this->object->isValid('socialAccountForYoutubeAndRestSubscribers:12345'), false);
|
||||||
|
$this->assertEquals($this->object->isValid('socialAccountForYoutubeAndRSubscriber:12345'), false);
|
||||||
|
$this->assertEquals($this->object->isValid('socialAccount:ForYoutubeSubscribe'), true);
|
||||||
|
$this->assertEquals($this->object->isValid('socialAccountForYoutubeSubscribe:socialAccountForYoutubeSubscribe'), true);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue