mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
misc changes
allow filtering by providerType, userId in subscribers. Adds cancelled status for message status. Validate targets when creating message. delete all targets when user is deleted. fix twilio bug. add db env vars in messaging worker compose.phtml
This commit is contained in:
@@ -1755,6 +1755,28 @@ $commonCollections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('topicId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1777,6 +1799,17 @@ $commonCollections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1793,6 +1826,20 @@ $commonCollections = [
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_userId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['_key_userId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_userInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_topicId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
|
||||
@@ -847,5 +847,20 @@ return [
|
||||
'description' => 'Message with the requested ID has already been scheduled for delivery.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_TARGET_NOT_EMAIL => [
|
||||
'name' => Exception::MESSAGE_TARGET_NOT_EMAIL,
|
||||
'description' => 'Message with the target ID is not an Email target:',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_TARGET_NOT_SMS => [
|
||||
'name' => Exception::MESSAGE_TARGET_NOT_SMS,
|
||||
'description' => 'Message with the target ID is not an SMS target:',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_TARGET_NOT_PUSH => [
|
||||
'name' => Exception::MESSAGE_TARGET_NOT_PUSH,
|
||||
'description' => 'Message with the target ID is not a Push target:',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -483,7 +483,7 @@ App::post('/v1/messaging/providers/twilio')
|
||||
'type' => MESSAGE_TYPE_SMS,
|
||||
'enabled' => $enabled,
|
||||
'credentials' => $credentials,
|
||||
'options' => $from,
|
||||
'options' => $options,
|
||||
]);
|
||||
|
||||
try {
|
||||
@@ -1952,6 +1952,9 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
'topicInternalId' => $topic->getInternalId(),
|
||||
'targetId' => $targetId,
|
||||
'targetInternalId' => $target->getInternalId(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerType' => $target->getAttribute('providerType'),
|
||||
]);
|
||||
|
||||
try {
|
||||
@@ -1987,11 +1990,16 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
->label('sdk.response.model', Response::MODEL_SUBSCRIBER_LIST)
|
||||
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
|
||||
->param('queries', [], new Subscribers(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Providers::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, array $queries, Database $dbForProject, Response $response) {
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
@@ -2223,7 +2231,7 @@ App::post('/v1/messaging/messages/email')
|
||||
->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true)
|
||||
->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true)
|
||||
->param('description', '', new Text(256), 'Description for message.', true)
|
||||
->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true)
|
||||
->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
|
||||
->param('html', false, new Boolean(), 'Is content of type HTML', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
@@ -2238,6 +2246,18 @@ App::post('/v1/messaging/messages/email')
|
||||
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
|
||||
}
|
||||
|
||||
foreach ($targets as $target) {
|
||||
$targetDocument = $dbForProject->getDocument('targets', $target);
|
||||
|
||||
if ($targetDocument->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_EMAIL) {
|
||||
throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL . ' ' . $targetDocument->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$message = $dbForProject->createDocument('messages', new Document([
|
||||
'$id' => $messageId,
|
||||
'providerType' => MESSAGE_TYPE_EMAIL,
|
||||
@@ -2288,7 +2308,7 @@ App::post('/v1/messaging/messages/sms')
|
||||
->param('users', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of User IDs.', true)
|
||||
->param('targets', [], new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true)
|
||||
->param('description', '', new Text(256), 'Description for Message.', true)
|
||||
->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true)
|
||||
->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', 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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
@@ -2302,6 +2322,18 @@ App::post('/v1/messaging/messages/sms')
|
||||
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
|
||||
}
|
||||
|
||||
foreach ($targets as $target) {
|
||||
$targetDocument = $dbForProject->getDocument('targets', $target);
|
||||
|
||||
if ($targetDocument->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_SMS) {
|
||||
throw new Exception(Exception::MESSAGE_TARGET_NOT_SMS . ' ' . $targetDocument->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$message = $dbForProject->createDocument('messages', new Document([
|
||||
'$id' => $messageId,
|
||||
'providerType' => MESSAGE_TYPE_SMS,
|
||||
@@ -2358,7 +2390,7 @@ App::post('/v1/messaging/messages/push')
|
||||
->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true)
|
||||
->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true)
|
||||
->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true)
|
||||
->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true)
|
||||
->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', 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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
@@ -2372,6 +2404,18 @@ App::post('/v1/messaging/messages/push')
|
||||
throw new Exception(Exception::MESSAGE_MISSING_TARGET);
|
||||
}
|
||||
|
||||
foreach ($targets as $target) {
|
||||
$targetDocument = $dbForProject->getDocument('targets', $target);
|
||||
|
||||
if ($targetDocument->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) {
|
||||
throw new Exception(Exception::MESSAGE_TARGET_NOT_PUSH . ' ' . $targetDocument->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$pushData = [];
|
||||
|
||||
$keys = ['title', 'body', 'data', 'action', 'icon', 'sound', 'color', 'tag', 'badge'];
|
||||
@@ -2581,7 +2625,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
->param('subject', '', new Text(998), 'Email Subject.', true)
|
||||
->param('description', '', new Text(256), 'Description for Message.', true)
|
||||
->param('content', '', new Text(64230), 'Email Content.', true)
|
||||
->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true)
|
||||
->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', true)
|
||||
->param('html', false, new Boolean(), 'Is content of type HTML', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
@@ -2613,6 +2657,18 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
}
|
||||
|
||||
if (!\is_null($targets)) {
|
||||
foreach ($targets as $target) {
|
||||
$targetDocument = $dbForProject->getDocument('targets', $target);
|
||||
|
||||
if ($targetDocument->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_EMAIL) {
|
||||
throw new Exception(Exception::MESSAGE_TARGET_NOT_EMAIL . ' ' . $targetDocument->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$message->setAttribute('targets', $targets);
|
||||
}
|
||||
|
||||
@@ -2680,7 +2736,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
->param('targets', null, new ArrayList(new Text(Database::LENGTH_KEY), 1), 'List of Targets IDs.', true)
|
||||
->param('description', '', new Text(256), 'Description for Message.', true)
|
||||
->param('content', '', new Text(64230), 'Email Content.', true)
|
||||
->param('status', '', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true)
|
||||
->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', 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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
@@ -2711,6 +2767,18 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
}
|
||||
|
||||
if (!\is_null($targets)) {
|
||||
foreach ($targets as $target) {
|
||||
$targetDocument = $dbForProject->getDocument('targets', $target);
|
||||
|
||||
if ($targetDocument->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_SMS) {
|
||||
throw new Exception(Exception::MESSAGE_TARGET_NOT_SMS . ' ' . $targetDocument->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$message->setAttribute('targets', $targets);
|
||||
}
|
||||
|
||||
@@ -2777,7 +2845,8 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
->param('sound', '', new Text(256), 'Sound for push notification. Available only for Android and IOS Platform.', true)
|
||||
->param('color', '', new Text(256), 'Color for push notification. Available only for Android Platform.', true)
|
||||
->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true)
|
||||
->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true) ->param('status', 'processing', new WhiteList(['draft', 'processing']), 'Message Status. Value must be either draft or processing.', true)
|
||||
->param('badge', '', new Text(256), 'Badge for push notification. Available only for IOS Platform.', true)
|
||||
->param('status', '', new WhiteList(['draft', 'cancelled', 'processing']), 'Message Status. Value must be either draft or cancelled or processing.', 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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
@@ -2808,6 +2877,18 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
}
|
||||
|
||||
if (!\is_null($targets)) {
|
||||
foreach ($targets as $target) {
|
||||
$targetDocument = $dbForProject->getDocument('targets', $target);
|
||||
|
||||
if ($targetDocument->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($targetDocument->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) {
|
||||
throw new Exception(Exception::MESSAGE_TARGET_NOT_PUSH . ' ' . $targetDocument->getId());
|
||||
}
|
||||
}
|
||||
|
||||
$message->setAttribute('targets', $targets);
|
||||
}
|
||||
|
||||
|
||||
@@ -1397,6 +1397,10 @@ App::patch('/v1/users/:userId/targets/:targetId')
|
||||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($provider->getAttribute('type') !== $target->getAttribute('providerType')) {
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
$target->setAttribute('providerId', $provider->getId());
|
||||
$target->setAttribute('providerInternalId', $provider->getInternalId());
|
||||
}
|
||||
|
||||
@@ -521,14 +521,20 @@ services:
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_SMS_PROVIDER
|
||||
- _APP_SMS_FROM
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_SMS_FROM
|
||||
- _APP_SMS_PROVIDER
|
||||
|
||||
appwrite-worker-migrations:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
|
||||
@@ -256,6 +256,10 @@ class Exception extends \Exception
|
||||
public const MESSAGE_MISSING_TARGET = 'message_missing_target';
|
||||
public const MESSAGE_ALREADY_SENT = 'message_already_sent';
|
||||
public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled';
|
||||
public const MESSAGE_TARGET_NOT_EMAIL = 'message_target_not_email';
|
||||
public const MESSAGE_TARGET_NOT_SMS = 'message_target_not_sms';
|
||||
public const MESSAGE_TARGET_NOT_PUSH = 'message_target_not_push';
|
||||
|
||||
|
||||
protected string $type = '';
|
||||
protected array $errors = [];
|
||||
|
||||
@@ -556,6 +556,11 @@ class Deletes extends Action
|
||||
$this->deleteByGroup('identities', [
|
||||
Query::equal('userInternalId', [$userInternalId])
|
||||
], $dbForProject);
|
||||
|
||||
// Delete targets
|
||||
$this->deleteByGroup('targets', [
|
||||
Query::equal('userInternalId', [$userInternalId])
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -91,14 +91,16 @@ class Messaging extends Action
|
||||
if (\count($topicsId) > 0) {
|
||||
$topics = $dbForProject->find('topics', [Query::equal('$id', $topicsId)]);
|
||||
foreach ($topics as $topic) {
|
||||
$recipients = \array_merge($recipients, $topic->getAttribute('targets'));
|
||||
$targets = \array_filter($topic->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
|
||||
$recipients = \array_merge($recipients, $targets);
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($usersId) > 0) {
|
||||
$users = $dbForProject->find('users', [Query::equal('$id', $usersId)]);
|
||||
foreach ($users as $user) {
|
||||
$recipients = \array_merge($recipients, $user->getAttribute('targets'));
|
||||
$targets = \array_filter($user->getAttribute('targets'), fn (Document $target) => $target->getAttribute('providerType') === $message->getAttribute('providerType'));
|
||||
$recipients = \array_merge($recipients, $targets);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ class Subscribers extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'targetId',
|
||||
'topicId'
|
||||
'topicId',
|
||||
'userId',
|
||||
'providerType'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -98,7 +98,7 @@ class Message extends Model
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Status of delivery.',
|
||||
'default' => 'processing',
|
||||
'example' => 'Message status can be one of the following: processing, sent, failed.',
|
||||
'example' => 'Message status can be one of the following: processing, sent, cancelled, failed.',
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
||||
@@ -49,6 +49,12 @@ class Subscriber extends Model
|
||||
'userId' => '5e5ea5c16897e',
|
||||
],
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Topic ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('userName', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User Name.',
|
||||
@@ -60,6 +66,12 @@ class Subscriber extends Model
|
||||
'description' => 'Topic ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('providerType', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.',
|
||||
'default' => '',
|
||||
'example' => MESSAGE_TYPE_EMAIL,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class Target extends Model
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.',
|
||||
'default' => '',
|
||||
'example' => 'email',
|
||||
'example' => MESSAGE_TYPE_EMAIL,
|
||||
])
|
||||
->addRule('identifier', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
||||
Reference in New Issue
Block a user