mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'feat-mailgun-provider' of https://github.com/appwrite/appwrite into feat-topics-controller
This commit is contained in:
@@ -20,8 +20,9 @@ class Linkedin extends OAuth2
|
||||
* @var array
|
||||
*/
|
||||
protected array $scopes = [
|
||||
'r_liteprofile',
|
||||
'r_emailaddress',
|
||||
'openid',
|
||||
'profile',
|
||||
'email'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -117,8 +118,7 @@ class Linkedin extends OAuth2
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['id'] ?? '';
|
||||
return $user['sub'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,9 +128,8 @@ class Linkedin extends OAuth2
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$email = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))', ['Authorization: Bearer ' . \urlencode($accessToken)]), true);
|
||||
|
||||
return $email['elements'][0]['handle~']['emailAddress'] ?? '';
|
||||
$user = $this->getUser($accessToken);
|
||||
return $user['email'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,9 +143,8 @@ class Linkedin extends OAuth2
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$email = $this->getUserEmail($accessToken);
|
||||
|
||||
return !empty($email);
|
||||
$user = $this->getUser($accessToken);
|
||||
return $user['email_verified'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,12 +157,16 @@ class Linkedin extends OAuth2
|
||||
$user = $this->getUser($accessToken);
|
||||
$name = '';
|
||||
|
||||
if (isset($user['localizedFirstName'])) {
|
||||
$name = $user['localizedFirstName'];
|
||||
if (isset($user['name'])) {
|
||||
return $user['name'];
|
||||
}
|
||||
|
||||
if (isset($user['localizedLastName'])) {
|
||||
$name = (empty($name)) ? $user['localizedLastName'] : $name . ' ' . $user['localizedLastName'];
|
||||
if (isset($user['given_name'])) {
|
||||
$name = $user['given_name'];
|
||||
}
|
||||
|
||||
if (isset($user['family_name'])) {
|
||||
$name = (empty($name)) ? $user['family_name'] : $name . ' ' . $user['family_name'];
|
||||
}
|
||||
|
||||
return $name;
|
||||
@@ -178,7 +180,7 @@ class Linkedin extends OAuth2
|
||||
protected function getUser(string $accessToken)
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$this->user = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/me', ['Authorization: Bearer ' . \urlencode($accessToken)]), true);
|
||||
$this->user = \json_decode($this->request('GET', 'https://api.linkedin.com/v2/userinfo', ['Authorization: Bearer ' . \urlencode($accessToken)]), true);
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Resque;
|
||||
use Utopia\Database\Document;
|
||||
use ResqueScheduler;
|
||||
use Utopia\Database\DateTime;
|
||||
|
||||
class Messaging extends Event
|
||||
{
|
||||
protected ?string $messageId = null;
|
||||
private ?string $deliveryTime = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(Event::MESSAGING_QUEUE_NAME, Event::MESSAGING_CLASS_NAME);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets message ID for the messaging event.
|
||||
*
|
||||
@@ -39,17 +39,38 @@ class Messaging extends Event
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the event and sends it to the messaging worker.
|
||||
* Sets Delivery time for the messaging event.
|
||||
*
|
||||
* @return string|bool
|
||||
* @throws \InvalidArgumentException
|
||||
* @param string $deliveryTime
|
||||
* @return self
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
public function setDeliveryTime(string $deliveryTime): self
|
||||
{
|
||||
return Resque::enqueue($this->queue, $this->class, [
|
||||
$this->deliveryTime = $deliveryTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set Delivery Time for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDeliveryTime(): string
|
||||
{
|
||||
return $this->deliveryTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the event and sends it to the messaging worker.
|
||||
*/
|
||||
public function trigger(): string | bool
|
||||
{
|
||||
ResqueScheduler::enqueueAt(!empty($this->deliveryTime) ? $this->deliveryTime : DateTime::now(), $this->queue, $this->class, [
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'messageId' => $this->messageId,
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,12 +45,6 @@ class Event extends Validator
|
||||
* Identify all sections of the pattern.
|
||||
*/
|
||||
$type = $parts[0] ?? false;
|
||||
|
||||
if ($type == 'functions') {
|
||||
$this->message = 'Triggering a function on a function event is not allowed.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$resource = $parts[1] ?? false;
|
||||
$hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false);
|
||||
$hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Validator;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
|
||||
class FunctionEvent extends Event
|
||||
{
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (str_starts_with($value, 'functions.')) {
|
||||
$this->message = 'Triggering a function on a function event is not allowed.';
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::isValid($value);
|
||||
}
|
||||
}
|
||||
@@ -235,6 +235,7 @@ class Mapper
|
||||
case 'Utopia\Validator\Domain':
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case 'Appwrite\Event\Validator\Event':
|
||||
case 'Appwrite\Event\Validator\FunctionEvent':
|
||||
case 'Utopia\Validator\HexColor':
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\IP':
|
||||
|
||||
@@ -67,6 +67,9 @@ abstract class Migration
|
||||
'1.4.0' => 'V19',
|
||||
'1.4.1' => 'V19',
|
||||
'1.4.2' => 'V19',
|
||||
'1.4.3' => 'V19',
|
||||
'1.4.4' => 'V19',
|
||||
'1.4.5' => 'V19',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -93,6 +93,12 @@ class Doctor extends Action
|
||||
Console::log('🟢 HTTPS force option is enabled');
|
||||
}
|
||||
|
||||
if ('enabled' !== App::getEnv('_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS', 'disabled')) {
|
||||
Console::log('🔴 HTTPS force option is disabled for function domains');
|
||||
} else {
|
||||
Console::log('🟢 HTTPS force option is enabled for function domains');
|
||||
}
|
||||
|
||||
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
|
||||
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
|
||||
|
||||
@@ -245,7 +251,7 @@ class Doctor extends Action
|
||||
try {
|
||||
if (App::isProduction()) {
|
||||
Console::log('');
|
||||
$version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost') . '/v1/health/version'), true);
|
||||
$version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost') . '/version'), true);
|
||||
|
||||
if ($version && isset($version['version'])) {
|
||||
if (\version_compare($version['version'], App::getEnv('_APP_VERSION', 'UNKNOWN')) === 0) {
|
||||
|
||||
@@ -97,6 +97,32 @@ class Hamster extends Action
|
||||
/** Get Project Name */
|
||||
$statsPerProject['project_name'] = $project->getAttribute('name');
|
||||
|
||||
/** Total Project Variables */
|
||||
$statsPerProject['custom_variables'] = $dbForProject->count('variables', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Total Migrations */
|
||||
$statsPerProject['custom_migrations'] = $dbForProject->count('migrations', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Custom SMTP */
|
||||
$smtp = $project->getAttribute('smtp', null);
|
||||
if ($smtp) {
|
||||
$statsPerProject['custom_smtp_status'] = $smtp['enabled'] === true ? 'enabled' : 'disabled';
|
||||
|
||||
/** Get Custom Templates Count */
|
||||
$templates = array_keys($project->getAttribute('templates', []));
|
||||
$statsPerProject['custom_email_templates'] = array_filter($templates, function ($template) {
|
||||
return str_contains($template, 'email');
|
||||
});
|
||||
$statsPerProject['custom_sms_templates'] = array_filter($templates, function ($template) {
|
||||
return str_contains($template, 'sms');
|
||||
});
|
||||
}
|
||||
|
||||
/** Get total relationship attributes */
|
||||
$statsPerProject['custom_relationship_attributes'] = $dbForProject->count('attributes', [
|
||||
Query::equal('type', ['relationship'])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Functions */
|
||||
$statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
|
||||
|
||||
@@ -108,6 +134,17 @@ class Hamster extends Action
|
||||
|
||||
/** Get Total Deployments */
|
||||
$statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
|
||||
$statsPerProject['custom_deployments_manual'] = $dbForProject->count('deployments', [
|
||||
Query::equal('type', ['manual'])
|
||||
], APP_LIMIT_COUNT);
|
||||
$statsPerProject['custom_deployments_git'] = $dbForProject->count('deployments', [
|
||||
Query::equal('type', ['vcs'])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get VCS repos connected */
|
||||
$statsPerProject['custom_vcs_repositories'] = $dbForConsole->count('repositories', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Teams */
|
||||
$statsPerProject['custom_teams'] = $dbForProject->count('teams', [], APP_LIMIT_COUNT);
|
||||
@@ -132,19 +169,16 @@ class Hamster extends Action
|
||||
throw new Exception('Membership not found. Skipping project : ' . $project->getId());
|
||||
}
|
||||
|
||||
$userInternalId = $membership->getAttribute('userInternalId', null);
|
||||
if ($userInternalId) {
|
||||
$user = $dbForConsole->findOne('users', [
|
||||
Query::equal('_id', [$userInternalId]),
|
||||
]);
|
||||
|
||||
$userId = $membership->getAttribute('userId', null);
|
||||
if ($userId) {
|
||||
$user = $dbForConsole->getDocument('users', $userId);
|
||||
$statsPerProject['email'] = $user->getAttribute('email', null);
|
||||
$statsPerProject['name'] = $user->getAttribute('name', null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Domains */
|
||||
$statsPerProject['custom_domains'] = $dbForConsole->count('domains', [
|
||||
$statsPerProject['custom_domains'] = $dbForConsole->count('rules', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
@@ -234,15 +268,16 @@ class Hamster extends Action
|
||||
if (!$res) {
|
||||
Console::error('Failed to create user profile for project: ' . $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$event = new Event();
|
||||
$event
|
||||
->setName('Project Daily Usage')
|
||||
->setProps($statsPerProject);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
if (!$res) {
|
||||
Console::error('Failed to create event for project: ' . $project->getId());
|
||||
}
|
||||
$event = new Event();
|
||||
$event
|
||||
->setName('Project Daily Usage')
|
||||
->setProps($statsPerProject);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
|
||||
if (!$res) {
|
||||
Console::error('Failed to create event for project: ' . $project->getId());
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::error('Failed to send stats for project: ' . $project->getId());
|
||||
@@ -362,12 +397,9 @@ class Hamster extends Action
|
||||
throw new Exception('Membership not found. Skipping organization : ' . $document->getId());
|
||||
}
|
||||
|
||||
$userInternalId = $membership->getAttribute('userInternalId', null);
|
||||
if ($userInternalId) {
|
||||
$user = $dbForConsole->findOne('users', [
|
||||
Query::equal('_id', [$userInternalId]),
|
||||
]);
|
||||
|
||||
$userId = $membership->getAttribute('userId', null);
|
||||
if ($userId) {
|
||||
$user = $dbForConsole->getDocument('users', $userId);
|
||||
$statsPerOrganization['email'] = $user->getAttribute('email', null);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,12 @@ class Schedule extends Action
|
||||
$sum = count($results);
|
||||
$total = $total + $sum;
|
||||
foreach ($results as $document) {
|
||||
$schedules[$document['resourceId']] = $getSchedule($document);
|
||||
try {
|
||||
$schedules[$document['resourceId']] = $getSchedule($document);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error("Failed to load schedule for project {$document['projectId']} and function {$document['resourceId']}");
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
|
||||
|
||||
@@ -253,7 +253,7 @@ class Specs extends Action
|
||||
->setParam('docs.url', $endpoint . '/docs');
|
||||
|
||||
if ($mocks) {
|
||||
$path = __DIR__ . '/../config/specs/' . $format . '-mocks-' . $platform . '.json';
|
||||
$path = __DIR__ . '/../../../../app/config/specs/' . $format . '-mocks-' . $platform . '.json';
|
||||
|
||||
if (!file_put_contents($path, json_encode($specs->parse()))) {
|
||||
throw new Exception('Failed to save mocks spec file: ' . $path);
|
||||
|
||||
@@ -32,11 +32,18 @@ class Message extends Any
|
||||
])
|
||||
->addRule('deliveryTime', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Time the message is delivered at.',
|
||||
'description' => 'The scheduled time for message.',
|
||||
'required' => false,
|
||||
'default' => DateTime::now(),
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('deliveredAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The time when the message was delivered.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('deliveryErrors', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Delivery errors if any.',
|
||||
|
||||
@@ -68,6 +68,7 @@ class Migration extends Model
|
||||
->addRule('errors', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'All errors that occurred during the migration process.',
|
||||
'array' => true,
|
||||
'default' => [],
|
||||
'example' => [],
|
||||
])
|
||||
|
||||
@@ -70,7 +70,7 @@ class Executor
|
||||
array $variables = [],
|
||||
string $command = null,
|
||||
) {
|
||||
$runtimeId = "$projectId-$deploymentId";
|
||||
$runtimeId = "$projectId-$deploymentId-build";
|
||||
$route = "/runtimes";
|
||||
$params = [
|
||||
'runtimeId' => $runtimeId,
|
||||
@@ -113,7 +113,7 @@ class Executor
|
||||
) {
|
||||
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
|
||||
|
||||
$runtimeId = "$projectId-$deploymentId";
|
||||
$runtimeId = "$projectId-$deploymentId-build";
|
||||
$route = "/runtimes/{$runtimeId}/logs";
|
||||
$params = [
|
||||
'timeout' => $timeout
|
||||
@@ -177,6 +177,7 @@ class Executor
|
||||
string $method,
|
||||
array $headers,
|
||||
string $runtimeEntrypoint = null,
|
||||
int $requestTimeout = null
|
||||
) {
|
||||
if (empty($headers['host'])) {
|
||||
$headers['host'] = App::getEnv('_APP_DOMAIN', '');
|
||||
@@ -202,9 +203,13 @@ class Executor
|
||||
'runtimeEntrypoint' => $runtimeEntrypoint,
|
||||
];
|
||||
|
||||
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
|
||||
// Safety timeout. Executor has timeout, and open runtime has soft timeout.
|
||||
// This one shouldn't really happen, but prevents from unexpected networking behaviours.
|
||||
if ($requestTimeout == null) {
|
||||
$requestTimeout = $timeout + 15;
|
||||
}
|
||||
|
||||
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
|
||||
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $requestTimeout);
|
||||
|
||||
$status = $response['headers']['status-code'];
|
||||
if ($status >= 400) {
|
||||
|
||||
Reference in New Issue
Block a user