mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
244
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86a36582f1 | ||
|
|
a0f0413984 | ||
|
|
99b5ac8669 | ||
|
|
36d257c847 | ||
|
|
55187d4781 | ||
|
|
4bef86d88b | ||
|
|
b86f531b30 | ||
|
|
8602c44d14 | ||
|
|
ffeb3f8fcf | ||
|
|
4ccffb6649 | ||
|
|
feac25f401 | ||
|
|
0b1b7646d5 | ||
|
|
4a44fb3330 | ||
|
|
736b76cbfa | ||
|
|
cc1769ed41 | ||
|
|
17ba6355e0 | ||
|
|
bb52647366 | ||
|
|
8882318d0d | ||
|
|
1e5a2a78aa | ||
|
|
ecfdab1598 | ||
|
|
a77a212e2c | ||
|
|
a92390dda2 | ||
|
|
030f9629a6 | ||
|
|
d842191cc6 | ||
|
|
76152019d6 | ||
|
|
d5cc214546 | ||
|
|
8e9f270ef0 | ||
|
|
22d445587e | ||
|
|
c5408393c8 | ||
|
|
fb98d21ae5 | ||
|
|
0251703e3a | ||
|
|
80a19f9862 | ||
|
|
abab68f1a1 | ||
|
|
5891872fad | ||
|
|
8a4302ffe6 | ||
|
|
c9c49158a3 | ||
|
|
61785ab35a | ||
|
|
48b91e39a8 | ||
|
|
4a43c84b29 | ||
|
|
897e975b49 | ||
|
|
514a5c37fd | ||
|
|
1a50b2948f | ||
|
|
7708779ef1 | ||
|
|
90352c57ac | ||
|
|
234f7f003b | ||
|
|
63bd4d6daf | ||
|
|
a0858ccc4e | ||
|
|
b019273166 | ||
|
|
df6466a1d3 | ||
|
|
e00b273577 | ||
|
|
e2acb608fa | ||
|
|
d63eaf3ef0 | ||
|
|
06a05a3358 | ||
|
|
3ebcfe548d | ||
|
|
342f122365 | ||
|
|
b4de44c905 | ||
|
|
39854239fb | ||
|
|
0510de5eab | ||
|
|
35d842171c | ||
|
|
3548c4ac47 | ||
|
|
dd14bbf785 | ||
|
|
87011af914 | ||
|
|
2fd7467e2a | ||
|
|
975b444761 | ||
|
|
8d9e0ae7d0 | ||
|
|
3456ad4e8c | ||
|
|
afef225339 | ||
|
|
f9030132a5 | ||
|
|
a2e34b5b64 | ||
|
|
67397cd763 | ||
|
|
bd36a09473 | ||
|
|
c1b0ef206b | ||
|
|
c58f5a4522 | ||
|
|
afd421e196 | ||
|
|
25fc64c4ab | ||
|
|
d2ce8155e6 | ||
|
|
250758a1d3 | ||
|
|
7d51da2e69 | ||
|
|
2fe94be4c1 | ||
|
|
350549db7d | ||
|
|
68dc92939f | ||
|
|
dbd227ed50 | ||
|
|
4bb611efc4 | ||
|
|
0066216bef | ||
|
|
778056d8e0 | ||
|
|
f472ae578f | ||
|
|
9b6fc440d9 | ||
|
|
1f9872b398 | ||
|
|
d709682d10 | ||
|
|
fbd4712a72 | ||
|
|
d742b0c0a9 | ||
|
|
945d048b6e | ||
|
|
6d5cc588bf | ||
|
|
851afae29a | ||
|
|
d14fbde9e7 | ||
|
|
420eb8857a | ||
|
|
6affa6b5a0 | ||
|
|
a723744b06 | ||
|
|
11a458f1df | ||
|
|
e653e68869 | ||
|
|
445ab98267 | ||
|
|
6631406236 | ||
|
|
7dd25f60f3 | ||
|
|
118a50b894 | ||
|
|
720c7300dc | ||
|
|
68365babd1 | ||
|
|
497586378f | ||
|
|
9fbad72b08 | ||
|
|
5136724a29 | ||
|
|
b54556d10e | ||
|
|
a69368973b | ||
|
|
878e56ae7e | ||
|
|
d14a4c2506 | ||
|
|
2f2875e3c0 | ||
|
|
ba96f71a3f | ||
|
|
b7119a2718 | ||
|
|
cbea4264ea | ||
|
|
996de004fb | ||
|
|
ddbee0a55b | ||
|
|
8957640d27 | ||
|
|
78a2f6f246 | ||
|
|
899c962061 | ||
|
|
a59cfb6461 | ||
|
|
32970ba8a0 | ||
|
|
2ecf9ee98a | ||
|
|
27741644e8 | ||
|
|
f4f1aa4b5b | ||
|
|
cf7b63fd2f | ||
|
|
ee0f789e37 | ||
|
|
a81006e961 | ||
|
|
15773a3561 | ||
|
|
06361828a9 | ||
|
|
039e717d72 | ||
|
|
3d286a2171 | ||
|
|
38d81c058b | ||
|
|
6aa443454b | ||
|
|
00498f0ae2 | ||
|
|
597fd965f7 | ||
|
|
4d31cd44d5 | ||
|
|
45ab78caa7 | ||
|
|
78a53aee37 | ||
|
|
01d5984965 | ||
|
|
1a791f46e6 | ||
|
|
337839103f | ||
|
|
f4eac9f41d | ||
|
|
3f95d6abd5 | ||
|
|
ea324262e7 | ||
|
|
8b2561d55a | ||
|
|
0b7d290419 | ||
|
|
c263a7b130 | ||
|
|
180493d642 | ||
|
|
0ed2729b86 | ||
|
|
0c76cfcb00 | ||
|
|
0a289bce43 | ||
|
|
2e89837c13 | ||
|
|
82e165c4c3 | ||
|
|
1762c81b8d | ||
|
|
0534b11b32 | ||
|
|
32b48a3c7c | ||
|
|
8d14dcb839 | ||
|
|
e104e6d0f2 | ||
|
|
7fd124705c | ||
|
|
24eabf29d0 | ||
|
|
112c7a5133 | ||
|
|
75d7f716e8 | ||
|
|
e53af019da | ||
|
|
c2c70d68e9 | ||
|
|
1d6276eb9f | ||
|
|
491d58f146 | ||
|
|
379f7b9aa2 | ||
|
|
264f0bd5dd | ||
|
|
f743bf384b | ||
|
|
2c4e90d660 | ||
|
|
639fae3156 | ||
|
|
d7c8562260 | ||
|
|
9567bf3943 | ||
|
|
c59887e8fb | ||
|
|
de6bcc1861 | ||
|
|
24853004f9 | ||
|
|
9be0592dab | ||
|
|
a266682951 | ||
|
|
94b7f67e44 | ||
|
|
d0c50403c8 | ||
|
|
b8f8fb1708 | ||
|
|
5960666068 | ||
|
|
f835e1b52e | ||
|
|
048e8e81ba | ||
|
|
33028ebbf2 | ||
|
|
33f4bd6ac4 | ||
|
|
52f6b72f23 | ||
|
|
df77f92860 | ||
|
|
a87745f685 | ||
|
|
eafea1c41d | ||
|
|
5ca8dc0a73 | ||
|
|
e8cc60b0c2 | ||
|
|
82d220e158 | ||
|
|
a95c29d546 | ||
|
|
ec54000263 | ||
|
|
ac33d7828f | ||
|
|
145b0f586d | ||
|
|
bf5f0a8419 | ||
|
|
eba91ae55a | ||
|
|
3e7805afe1 | ||
|
|
55d00d9afa | ||
|
|
c225563d77 | ||
|
|
dbbf4522e3 | ||
|
|
ab89abfc89 | ||
|
|
78385a59c6 | ||
|
|
9f19bec7a7 | ||
|
|
4406c7bd55 | ||
|
|
a6d613542b | ||
|
|
fe8b8d3dc6 | ||
|
|
5e3065c30a | ||
|
|
c0e4149feb | ||
|
|
cc1311b723 | ||
|
|
a174aca2c8 | ||
|
|
36a5d1e245 | ||
|
|
f73b3bf272 | ||
|
|
c83e4db23d | ||
|
|
07b7a9db0f | ||
|
|
455176d077 | ||
|
|
40f419663c | ||
|
|
e53f189b3b | ||
|
|
c7dec3762b | ||
|
|
ab655e69e6 | ||
|
|
9fc75e5535 | ||
|
|
37e5cecfb1 | ||
|
|
caad1a07d9 | ||
|
|
e2e4287801 | ||
|
|
45e03d3973 | ||
|
|
92e587769a | ||
|
|
4b63a4abbb | ||
|
|
296a54d0ce | ||
|
|
d7f806e671 | ||
|
|
3e70a19e4f | ||
|
|
be1afc61b0 | ||
|
|
9057a50a10 | ||
|
|
5baa0d674a | ||
|
|
4ca798f4d7 | ||
|
|
867f3e8c08 | ||
|
|
19fd30d6d8 | ||
|
|
13e73d8558 | ||
|
|
6108be6ae0 | ||
|
|
2d33681a0b |
@@ -98,4 +98,15 @@ _APP_VCS_GITHUB_CLIENT_SECRET=
|
||||
_APP_VCS_GITHUB_WEBHOOK_SECRET=
|
||||
_APP_MIGRATIONS_FIREBASE_CLIENT_ID=
|
||||
_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=
|
||||
_APP_ASSISTANT_OPENAI_API_KEY=
|
||||
_APP_ASSISTANT_OPENAI_API_KEY=
|
||||
_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID=
|
||||
_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY=
|
||||
_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM=
|
||||
_APP_MESSAGE_SMS_PROVIDER_MSG91_TO=
|
||||
_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY=
|
||||
_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN=
|
||||
_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM=
|
||||
_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL=
|
||||
_APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION=
|
||||
_APP_MESSAGE_PUSH_PROVIDER_FCM_SERVERY_KEY=
|
||||
_APP_MESSAGE_PUSH_PROVIDER_FCM_RECEIVER_TOKEN=
|
||||
|
||||
@@ -13,3 +13,4 @@ debug/
|
||||
app/sdks
|
||||
dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
Makefile
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
We would ❤️ you to contribute to Appwrite and help make it better! We want contributing to Appwrite to be fun, enjoyable, and educational for anyone and everyone. All contributions are welcome, including issues, and new docs, as well as updates and tweaks, blog posts, workshops, and more.
|
||||
|
||||
## Here for Hacktoberfest?
|
||||
If you're here to contribute during Hacktoberfest, we're so happy to see you here. Appwrite has been a long-time participant of Hacktoberfest and we welcome you, whatever your experience level. This year, we're **only taking contributions for issues tagged** `hacktoberfest`, so we can focus our resources to support your contributions.
|
||||
|
||||
You can [find issues using this query](https://github.com/search?q=org%3Aappwrite+is%3Aopen+type%3Aissue+label%3Ahacktoberfest&type=issues).
|
||||
|
||||
## How to Start?
|
||||
|
||||
If you are worried or don’t know where to start, check out the next section that explains what kind of help we could use and where you can get involved. You can send your questions to [@appwrite](https://twitter.com/appwrite) on Twitter or to anyone from the [Appwrite team on Discord](https://appwrite.io/discord). You can also submit an issue, and a maintainer can guide you!
|
||||
|
||||
@@ -93,6 +93,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/worker-builds && \
|
||||
chmod +x /usr/local/bin/worker-mails && \
|
||||
chmod +x /usr/local/bin/worker-messaging && \
|
||||
chmod +x /usr/local/bin/resque-scheduler && \
|
||||
chmod +x /usr/local/bin/worker-webhooks && \
|
||||
chmod +x /usr/local/bin/worker-migrations
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://appwrite.io" target="_blank"><img width="260" height="39" src="https://appwrite.io/images/appwrite.svg" alt="Appwrite Logo"></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./public/images/banner.png" alt="Appwrite Logo"></a>
|
||||
<br />
|
||||
<br />
|
||||
<b>适用于[Flutter/Vue/Angular/React/iOS/Android/* 等等平台 *]的完整后端服务</b>
|
||||
|
||||
+598
-2
@@ -5,7 +5,7 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
$providers = Config::getParam('providers', []);
|
||||
$providers = Config::getParam('authProviders', []);
|
||||
$auth = Config::getParam('auth', []);
|
||||
|
||||
/**
|
||||
@@ -221,6 +221,17 @@ $commonCollections = [
|
||||
'array' => false,
|
||||
'filters' => ['subQueryMemberships'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('targets'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryTargets'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1366,7 +1377,592 @@ $commonCollections = [
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
'providers' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('providers'),
|
||||
'name' => 'Providers',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('provider'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('type'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('default'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'default' => false,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('enabled'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => true,
|
||||
'default' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('credentials'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['json', 'encrypt'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('options'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_provider'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['provider'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['type'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_default'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['default'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_default_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['default','type'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
'messages' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('messages'),
|
||||
'name' => 'Messages',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('providerId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('description'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'processing',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('data'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('to'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deliveryTime'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deliveredAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deliveryErrors'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deliveredTo'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_providerId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['providerId'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_providerInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['providerInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'topics' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('topics'),
|
||||
'name' => 'Topics',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('providerId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('description'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('targets'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['subQueryTopicTargets'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_providerId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['providerId'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_providerInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['providerInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
'subscribers' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('subscribers'),
|
||||
'name' => 'Subscribers',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('targetId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('targetInternalId'),
|
||||
'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,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('topicInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_targetId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['targetId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_targetInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['targetInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_topicId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['topicId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_topicInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['topicInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
'targets' => [
|
||||
'$collection' => ID::custom(DATABASE::METADATA),
|
||||
'$id' => ID::custom('targets'),
|
||||
'name' => 'Targets',
|
||||
'attributes' => [
|
||||
[
|
||||
'$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('providerId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('identifier'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_userId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userId'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_userInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_providerId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['providerId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_providerInternalId'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['providerInternalId'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_identifier'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['identifier'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_identifier_providerId'),
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['providerId', 'identifier'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$projectCollections = array_merge([
|
||||
'databases' => [
|
||||
|
||||
@@ -240,6 +240,16 @@ return [
|
||||
'description' => 'OAuth2 provider returned some error.',
|
||||
'code' => 424,
|
||||
],
|
||||
Exception::USER_TARGET_NOT_FOUND => [
|
||||
'name' => Exception::USER_TARGET_NOT_FOUND,
|
||||
'description' => 'The target could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::USER_TARGET_ALREADY_EXISTS => [
|
||||
'name' => Exception::USER_TARGET_ALREADY_EXISTS,
|
||||
'description' => 'A target with the same ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Teams */
|
||||
Exception::TEAM_NOT_FOUND => [
|
||||
@@ -728,4 +738,57 @@ return [
|
||||
'description' => 'Migration is already in progress. You can check the status of the migration in your Appwrite Console\'s "Settings" > "Migrations".',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Provider Errors */
|
||||
Exception::PROVIDER_NOT_FOUND => [
|
||||
'name' => Exception::PROVIDER_NOT_FOUND,
|
||||
'description' => 'Provider with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::PROVIDER_ALREADY_EXISTS => [
|
||||
'name' => Exception::PROVIDER_ALREADY_EXISTS,
|
||||
'description' => 'Provider with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::PROVIDER_INCORRECT_TYPE => [
|
||||
'name' => Exception::PROVIDER_INCORRECT_TYPE,
|
||||
'description' => 'Provider with the requested ID is of incorrect type: ',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Topic Errors */
|
||||
Exception::TOPIC_NOT_FOUND => [
|
||||
'name' => Exception::TOPIC_NOT_FOUND,
|
||||
'description' => 'Topic with the request ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::TOPIC_ALREADY_EXISTS => [
|
||||
'name' => Exception::TOPIC_ALREADY_EXISTS,
|
||||
'description' => 'Topic with the request ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Subscriber Errors */
|
||||
Exception::SUBSCRIBER_NOT_FOUND => [
|
||||
'name' => Exception::SUBSCRIBER_NOT_FOUND,
|
||||
'description' => 'Subscriber with the request ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::SUBSCRIBER_ALREADY_EXISTS => [
|
||||
'name' => Exception::SUBSCRIBER_ALREADY_EXISTS,
|
||||
'description' => 'Subscriber with the request ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Message Errors */
|
||||
Exception::MESSAGE_NOT_FOUND => [
|
||||
'name' => Exception::MESSAGE_NOT_FOUND,
|
||||
'description' => 'Message with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::MESSAGE_ALREADY_SENT => [
|
||||
'name' => Exception::MESSAGE_ALREADY_SENT,
|
||||
'description' => 'Message with the requested ID has already been sent.',
|
||||
'code' => 400,
|
||||
]
|
||||
];
|
||||
|
||||
@@ -44,6 +44,20 @@ return [
|
||||
'$description' => 'This event triggers when a verification token for a user is validated.'
|
||||
],
|
||||
],
|
||||
'targets' => [
|
||||
'$model' => Response::MODEL_TARGET,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any user\'s target event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a user\'s target is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a user\'s target is updated.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a user\'s target is deleted.',
|
||||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a user is created.'
|
||||
],
|
||||
@@ -237,6 +251,54 @@ return [
|
||||
'$description' => 'This event triggers when a function is updated.',
|
||||
]
|
||||
],
|
||||
'messages' => [
|
||||
'$model' => Response::MODEL_MESSAGE,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any messaging event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a message is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a message is updated.',
|
||||
],
|
||||
'topics' => [
|
||||
'$model' => Response::MODEL_TOPIC,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any topic event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a provider is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a provider is deleted.'
|
||||
],
|
||||
'subscribers' => [
|
||||
'$model' => Response::MODEL_SUBSCRIBER,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any subscriber event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a subscriber is created.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a subscriber is deleted.'
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
'providers' => [
|
||||
'$model' => Response::MODEL_PROVIDER,
|
||||
'$resource' => true,
|
||||
'$description' => 'This event triggers on any provider event.',
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a provider is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a provider is updated.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a provider is deleted.'
|
||||
],
|
||||
],
|
||||
'rules' => [
|
||||
'$model' => Response::MODEL_PROXY_RULE,
|
||||
'$resource' => true,
|
||||
|
||||
@@ -21,6 +21,10 @@ $member = [
|
||||
'avatars.read',
|
||||
'execution.read',
|
||||
'execution.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
'assistant.read',
|
||||
];
|
||||
|
||||
@@ -60,6 +64,16 @@ $admins = [
|
||||
'migrations.write',
|
||||
'vcs.read',
|
||||
'vcs.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'providers.write',
|
||||
'providers.read',
|
||||
'messages.write',
|
||||
'messages.read',
|
||||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read'
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
@@ -76,6 +76,36 @@ return [ // List of publicly visible scopes
|
||||
'health.read' => [
|
||||
'description' => 'Access to read your project\'s health status',
|
||||
],
|
||||
'providers.read' => [
|
||||
'description' => 'Access to read your project\'s providers',
|
||||
],
|
||||
'providers.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s providers',
|
||||
],
|
||||
'messages.read' => [
|
||||
'description' => 'Access to read your project\'s messages',
|
||||
],
|
||||
'messages.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s messages',
|
||||
],
|
||||
'topics.read' => [
|
||||
'description' => 'Access to read your project\'s topics',
|
||||
],
|
||||
'topics.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s topics',
|
||||
],
|
||||
'subscribers.read' => [
|
||||
'description' => 'Access to read your project\'s subscribers',
|
||||
],
|
||||
'subscribers.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s subscribers',
|
||||
],
|
||||
'targets.read' => [
|
||||
'description' => 'Access to read your project\'s targets',
|
||||
],
|
||||
'targets.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s targets',
|
||||
],
|
||||
'rules.read' => [
|
||||
'description' => 'Access to read your project\'s proxy rules',
|
||||
],
|
||||
|
||||
@@ -251,4 +251,17 @@ return [
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/migrations.png',
|
||||
],
|
||||
'messaging' => [
|
||||
'key' => 'messaging',
|
||||
'name' => 'Messaging',
|
||||
'subtitle' => 'The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.).',
|
||||
'description' => '/docs/services/messaging.md',
|
||||
'controller' => 'api/messaging.php',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/server/messaging',
|
||||
'tests' => true,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/messaging.png',
|
||||
]
|
||||
];
|
||||
|
||||
@@ -8,7 +8,6 @@ use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Validator\Host;
|
||||
@@ -45,6 +44,7 @@ use Utopia\Validator\WhiteList;
|
||||
use Appwrite\Auth\Validator\PasswordHistory;
|
||||
use Appwrite\Auth\Validator\PasswordDictionary;
|
||||
use Appwrite\Auth\Validator\PersonalData;
|
||||
use Appwrite\Event\Messaging;
|
||||
|
||||
$oauthDefaultSuccess = '/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/auth/oauth2/failure';
|
||||
@@ -298,7 +298,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
||||
->label('sdk.methodType', 'webAuth')
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('providers'), fn($node) => (!$node['mock'])))) . '.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('authProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('authProviders'), fn($node) => (!$node['mock'])))) . '.')
|
||||
->param('success', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('failure', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
@@ -357,7 +357,7 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId')
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new Text(1024), 'Project ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('authProviders')), true), 'OAuth2 provider.')
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
|
||||
->param('state', '', new Text(2048), 'Login state params.', true)
|
||||
->param('error', '', new Text(2048, 0), 'Error code returned from the OAuth2 provider.', true)
|
||||
@@ -390,7 +390,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
|
||||
->label('origin', '*')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new Text(1024), 'Project ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('authProviders')), true), 'OAuth2 provider.')
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
|
||||
->param('state', '', new Text(2048), 'Login state params.', true)
|
||||
->param('error', '', new Text(2048, 0), 'Error code returned from the OAuth2 provider.', true)
|
||||
@@ -429,7 +429,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->label('docs', false)
|
||||
->label('usage.metric', 'sessions.{scope}.requests.create')
|
||||
->label('usage.params', ['provider:{request.provider}'])
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('authProviders')), true), 'OAuth2 provider.')
|
||||
->param('code', '', new Text(2048, 0), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true)
|
||||
->param('state', '', new Text(2048), 'OAuth2 state params.', true)
|
||||
->param('error', '', new Text(2048, 0), 'Error code returned from the OAuth2 provider.', true)
|
||||
@@ -457,7 +457,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$providers = Config::getParam('providers');
|
||||
$providers = Config::getParam('authProviders');
|
||||
$providerName = $providers[$provider]['name'] ?? '';
|
||||
|
||||
/** @var Appwrite\Auth\OAuth2 $oauth2 */
|
||||
@@ -1238,9 +1238,12 @@ App::post('/v1/account/sessions/phone')
|
||||
->inject('events')
|
||||
->inject('messaging')
|
||||
->inject('locale')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $events, EventPhone $messaging, Locale $locale) {
|
||||
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $events, Messaging $messaging, Locale $locale) {
|
||||
$provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [
|
||||
Query::equal('default', [true]),
|
||||
Query::equal('type', ['sms'])
|
||||
]));
|
||||
if ($provider === false || $provider->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
|
||||
@@ -1326,9 +1329,34 @@ App::post('/v1/account/sessions/phone')
|
||||
$message = $message->setParam('{{token}}', $secret);
|
||||
$message = $message->render();
|
||||
|
||||
$target = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$phone]),
|
||||
Query::equal('providerInternalId', [$provider->getInternalId()])
|
||||
]);
|
||||
|
||||
if (!$target) {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerId' => $provider->getId(),
|
||||
'providerInternalId' => $provider->getInternalId(),
|
||||
'identifier' => $phone,
|
||||
]));
|
||||
}
|
||||
|
||||
$messageDoc = $dbForProject->createDocument('messages', new Document([
|
||||
'$id' => $token->getId(),
|
||||
'to' => [$target->getId()],
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
'providerId' => $provider->getId(),
|
||||
'providerInternalId' => $provider->getInternalId(),
|
||||
]));
|
||||
|
||||
$messaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($message)
|
||||
->setMessageId($messageDoc->getId())
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
|
||||
$events->setPayload(
|
||||
@@ -2885,10 +2913,13 @@ App::post('/v1/account/verification/phone')
|
||||
->inject('messaging')
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $events, EventPhone $messaging, Document $project, Locale $locale) {
|
||||
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED);
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $events, Messaging $messaging, Document $project, Locale $locale) {
|
||||
$provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [
|
||||
Query::equal('default', [true]),
|
||||
Query::equal('type', ['sms'])
|
||||
]));
|
||||
if ($provider === false || $provider->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
|
||||
if (empty($user->getAttribute('phone'))) {
|
||||
@@ -2933,11 +2964,35 @@ App::post('/v1/account/verification/phone')
|
||||
$message = $message->setParam('{{token}}', $secret);
|
||||
$message = $message->render();
|
||||
|
||||
$target = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$user->getAttribute('phone')]),
|
||||
Query::equal('providerInternalId', [$provider->getInternalId()])
|
||||
]);
|
||||
|
||||
if (!$target) {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerId' => $provider->getId(),
|
||||
'providerInternalId' => $provider->getInternalId(),
|
||||
'identifier' => $user->getAttribute('phone'),
|
||||
]));
|
||||
}
|
||||
|
||||
$messageDoc = $dbForProject->createDocument('messages', new Document([
|
||||
'$id' => $verification->getId(),
|
||||
'to' => [$target->getId()],
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
'providerId' => $provider->getId(),
|
||||
'providerInternalId' => $provider->getInternalId(),
|
||||
]));
|
||||
|
||||
$messaging
|
||||
->setRecipient($user->getAttribute('phone'))
|
||||
->setMessage($message)
|
||||
->trigger()
|
||||
;
|
||||
->setMessageId($messageDoc->getId())
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
|
||||
@@ -2489,8 +2489,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
'orders' => $orders,
|
||||
]);
|
||||
|
||||
$validator = new IndexValidator($dbForProject->getAdapter()->getMaxIndexLength());
|
||||
if (!$validator->isValid($collection->setAttribute('indexes', $index, Document::SET_TYPE_APPEND))) {
|
||||
$validator = new IndexValidator(
|
||||
$collection->getAttribute('attributes'),
|
||||
$dbForProject->getAdapter()->getMaxIndexLength()
|
||||
);
|
||||
if (!$validator->isValid($index)) {
|
||||
throw new Exception(Exception::INDEX_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -570,7 +570,7 @@ App::patch('/v1/projects/:projectId/oauth2')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'Provider Name')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('authProviders')), true), 'Provider Name')
|
||||
->param('appId', null, new Text(256), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
|
||||
@@ -6,7 +6,7 @@ use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Validator\Host;
|
||||
@@ -380,6 +380,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->param('from', '', new Text(128), 'Sender of the message. It can be alphanumeric (Ex: MyCompany20). Restrictions may apply depending of the destination.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
@@ -388,7 +389,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('mails')
|
||||
->inject('messaging')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $mails, EventPhone $messaging, Event $events) {
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, string $from, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $mails, Messaging $messaging, Event $events) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
@@ -632,6 +633,15 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->trigger()
|
||||
;
|
||||
} elseif (!empty($phone)) {
|
||||
$provider = Authorization::skip(fn () => $dbForProject->findOne('providers', [
|
||||
Query::equal('default', [true, false]),
|
||||
Query::equal('type', ['sms'])
|
||||
]));
|
||||
|
||||
if ($provider === false || $provider->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.invitation-' . $locale->default] ?? [];
|
||||
@@ -642,9 +652,29 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
$message = $message->setParam('{{token}}', $url);
|
||||
$message = $message->render();
|
||||
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $invitee->getId(),
|
||||
'userInternalId' => $invitee->getInternalId(),
|
||||
'providerId' => $provider->getId(),
|
||||
'providerInternalId' => $provider->getInternalId(),
|
||||
'identifier' => $phone,
|
||||
]));
|
||||
|
||||
$messageDoc = $dbForProject->createDocument('messages', new Document([
|
||||
// Here membership ID is used as message ID so that it can be used in test cases to verify the message
|
||||
'$id' => $membership->getId(),
|
||||
'to' => [$target->getId()],
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
'providerId' => $provider->getId(),
|
||||
'providerInternalId' => $provider->getInternalId(),
|
||||
'deliveryTime' => Datetime::now(),
|
||||
]));
|
||||
|
||||
$messaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($message)
|
||||
->setMessageId($messageDoc->getId())
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,6 +380,64 @@ App::post('/v1/users/scrypt-modified')
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
});
|
||||
|
||||
App::post('/v1/users/:userId/targets')
|
||||
->desc('Create User Target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'users.targets.create')
|
||||
->label('audits.resource', 'target/response.$id')
|
||||
->label('scope', 'targets.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'createTarget')
|
||||
->label('sdk.description', '/docs/references/users/create-target.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('targetId', '', new UID(), 'Target ID.')
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $targetId, string $providerId, string $identifier, Response $response, Database $dbForProject, Event $events) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
if (!$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
try {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'$id' => $targetId,
|
||||
'providerId' => $providerId,
|
||||
'providerInternalId' => $provider->getInternalId(),
|
||||
'userId' => $userId,
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'identifier' => $identifier,
|
||||
]));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::get('/v1/users')
|
||||
->desc('List users')
|
||||
->groups(['api', 'users'])
|
||||
@@ -483,6 +541,38 @@ App::get('/v1/users/:userId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Get User Target')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'getTarget')
|
||||
->label('sdk.description', '/docs/references/users/get-user-target.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('targetId', '', new UID(), 'Target ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, string $targetId, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $user->find('$id', $targetId, 'targets');
|
||||
|
||||
if (empty($target)) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/sessions')
|
||||
->desc('List user sessions')
|
||||
->groups(['api', 'users'])
|
||||
@@ -647,6 +737,35 @@ App::get('/v1/users/:userId/logs')
|
||||
]), Response::MODEL_LOG_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/:userId/targets')
|
||||
->desc('List User Targets')
|
||||
->groups(['api', 'users'])
|
||||
->label('scope', 'targets.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'listTargets')
|
||||
->label('sdk.description', '/docs/references/users/list-user-targets.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET_LIST)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$targets = $user->getAttribute('targets', []);
|
||||
$response->dynamic(new Document([
|
||||
'targets' => $targets,
|
||||
'total' => \count($targets),
|
||||
]), Response::MODEL_TARGET_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/identities')
|
||||
->desc('List Identities')
|
||||
->groups(['api', 'users'])
|
||||
@@ -1081,6 +1200,56 @@ App::patch('/v1/users/:userId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/targets/:targetId/identifier')
|
||||
->desc('Update user target\'s identifier')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'users.targets.update')
|
||||
->label('audits.resource', 'target/{response.$id}')
|
||||
->label('scope', 'targets.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'updateTargetIdentifier')
|
||||
->label('sdk.description', '/docs/references/users/update-target-identifier.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TARGET)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('targetId', '', new UID(), 'Target ID.')
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $targetId, string $identifier, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($user->getId() !== $target->getAttribute('userId')) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target->setAttribute('identifier', $identifier);
|
||||
|
||||
$target = $dbForProject->updateDocument('targets', $target->getId(), $target);
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$events
|
||||
->setParam('userId', $userId)
|
||||
->setParam('targetId', $targetId);
|
||||
|
||||
$response
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
->desc('Delete user session')
|
||||
->groups(['api', 'users'])
|
||||
@@ -1211,6 +1380,48 @@ App::delete('/v1/users/:userId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Delete user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'users.targets.delete')
|
||||
->label('audits.resource', 'target/{request.$targetId}')
|
||||
->label('scope', 'targets.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'deleteTarget')
|
||||
->label('sdk.description', '/docs/references/users/delete-target.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('targetId', '', new UID(), 'Target ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $targetId, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($user->getId() !== $target->getAttribute('userId')) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = $dbForProject->deleteDocument('targets', $target->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/identities/:identityId')
|
||||
->desc('Delete Identity')
|
||||
->groups(['api', 'users'])
|
||||
@@ -1254,7 +1465,7 @@ App::get('/v1/users/usage')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USAGE_USERS)
|
||||
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
|
||||
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('providers', [])))), true), 'Provider Name.', true)
|
||||
->param('provider', '', new WhiteList(\array_merge(['email', 'anonymous'], \array_map(fn ($value) => "oauth-" . $value, \array_keys(Config::getParam('authProviders', [])))), true), 'Provider Name.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('register')
|
||||
|
||||
+36
-3
@@ -25,7 +25,7 @@ use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
@@ -169,6 +169,7 @@ const DELETE_TYPE_SESSIONS = 'sessions';
|
||||
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
|
||||
const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
|
||||
const DELETE_TYPE_SCHEDULES = 'schedules';
|
||||
const DELETE_TYPE_SUBSCRIBERS = 'subscribers';
|
||||
// Compression type
|
||||
const COMPRESSION_TYPE_NONE = 'none';
|
||||
const COMPRESSION_TYPE_GZIP = 'gzip';
|
||||
@@ -233,7 +234,7 @@ App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
|
||||
Config::load('events', __DIR__ . '/config/events.php');
|
||||
Config::load('auth', __DIR__ . '/config/auth.php');
|
||||
Config::load('errors', __DIR__ . '/config/errors.php');
|
||||
Config::load('providers', __DIR__ . '/config/providers.php');
|
||||
Config::load('authProviders', __DIR__ . '/config/authProviders.php');
|
||||
Config::load('platforms', __DIR__ . '/config/platforms.php');
|
||||
Config::load('collections', __DIR__ . '/config/collections.php');
|
||||
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
|
||||
@@ -530,6 +531,38 @@ Database::addFilter(
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTargets',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn() => $database
|
||||
->find('targets', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'subQueryTopicTargets',
|
||||
function (mixed $value) {
|
||||
return null;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$targetIds = Authorization::skip(fn () => \array_map(
|
||||
fn ($document) => $document->getAttribute('targetId'),
|
||||
$database
|
||||
->find('subscribers', [
|
||||
Query::equal('topicInternalId', [$document->getInternalId()]),
|
||||
])
|
||||
));
|
||||
if (\count($targetIds) > 0) {
|
||||
return $database->find('targets', [Query::equal('$id', $targetIds)]);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
);
|
||||
/**
|
||||
* DB Formats
|
||||
*/
|
||||
@@ -892,7 +925,7 @@ App::setResource('audits', fn() => new Audit());
|
||||
App::setResource('mails', fn() => new Mail());
|
||||
App::setResource('deletes', fn() => new Delete());
|
||||
App::setResource('database', fn() => new EventDatabase());
|
||||
App::setResource('messaging', fn() => new Phone());
|
||||
App::setResource('messaging', fn() => new Messaging());
|
||||
App::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
|
||||
@@ -127,6 +127,10 @@ class DeletesV1 extends Worker
|
||||
case DELETE_TYPE_SCHEDULES:
|
||||
$this->deleteSchedules($this->args['datetime']);
|
||||
break;
|
||||
case DELETE_TYPE_SUBSCRIBERS:
|
||||
$topic = new Document($this->args['document'] ?? []);
|
||||
$this->deleteSubscribers($project, $topic);
|
||||
break;
|
||||
default:
|
||||
Console::error('No delete operation for type: ' . $type);
|
||||
break;
|
||||
@@ -170,6 +174,24 @@ class DeletesV1 extends Worker
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param Document $topic
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function deleteSubscribers(Document $project, Document $topic)
|
||||
{
|
||||
if ($topic->isEmpty()) {
|
||||
Console::error('Failed to delete subscribers. Topic not found');
|
||||
return;
|
||||
}
|
||||
$dbForProject = $this->getProjectDB($project);
|
||||
|
||||
$this->deleteByGroup('subscribers', [
|
||||
Query::equal('topicInternalId', [$topic->getInternalId()])
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param string $resource
|
||||
|
||||
+189
-36
@@ -1,17 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Resque\Worker;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Messaging\Adapter;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Messaging\Adapters\SMS as SMSAdapter;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
use Utopia\Messaging\Adapters\SMS\Msg91;
|
||||
use Utopia\Messaging\Adapters\SMS\Telesign;
|
||||
use Utopia\Messaging\Adapters\SMS\TextMagic;
|
||||
use Utopia\Messaging\Adapters\SMS\Twilio;
|
||||
use Utopia\Messaging\Adapters\SMS\Vonage;
|
||||
use Utopia\Messaging\Adapters\Push as PushAdapter;
|
||||
use Utopia\Messaging\Adapters\Push\APNS;
|
||||
use Utopia\Messaging\Adapters\Push\FCM;
|
||||
use Utopia\Messaging\Adapters\Email as EmailAdapter;
|
||||
use Utopia\Messaging\Adapters\Email\Mailgun;
|
||||
use Utopia\Messaging\Adapters\Email\SendGrid;
|
||||
use Utopia\Messaging\Messages\Email;
|
||||
use Utopia\Messaging\Messages\Push;
|
||||
use Utopia\Messaging\Messages\SMS;
|
||||
use Appwrite\Extend\Exception;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
@@ -20,59 +33,199 @@ Console::success(APP_NAME . ' messaging worker v1 has started' . "\n");
|
||||
|
||||
class MessagingV1 extends Worker
|
||||
{
|
||||
protected ?Adapter $sms = null;
|
||||
protected ?string $from = null;
|
||||
protected ?SMSAdapter $sms = null;
|
||||
protected ?PushAdapter $push = null;
|
||||
protected ?EmailAdapter $email = null;
|
||||
|
||||
protected ?Database $dbForProject = null;
|
||||
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return "mails";
|
||||
return "messaging";
|
||||
}
|
||||
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$user = $dsn->getUser();
|
||||
$secret = $dsn->getPassword();
|
||||
|
||||
$this->sms = match ($dsn->getHost()) {
|
||||
'mock' => new Mock($user, $secret), // used for tests
|
||||
'twilio' => new Twilio($user, $secret),
|
||||
'text-magic' => new TextMagic($user, $secret),
|
||||
'telesign' => new Telesign($user, $secret),
|
||||
'msg91' => new Msg91($user, $secret),
|
||||
'vonage' => new Vonage($user, $secret),
|
||||
default => null
|
||||
};
|
||||
|
||||
$this->from = App::getEnv('_APP_SMS_FROM');
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
Console::info('Skipped sms processing. No Phone provider has been set.');
|
||||
return;
|
||||
$project = new Document($this->args['project']);
|
||||
$this->dbForProject = $this->getProjectDB($project);
|
||||
|
||||
$message = $this->dbForProject->getDocument('messages', $this->args['messageId']);
|
||||
|
||||
$provider = $this->dbForProject->getDocument('providers', $message->getAttribute('providerId'));
|
||||
|
||||
$this->processMessage($message, $provider);
|
||||
}
|
||||
|
||||
private function processMessage(Document $message, Document $provider): void
|
||||
{
|
||||
$adapter = match ($provider->getAttribute('type')) {
|
||||
'sms' => $this->sms($provider),
|
||||
'push' => $this->push($provider),
|
||||
'email' => $this->email($provider),
|
||||
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
|
||||
};
|
||||
|
||||
$recipientsId = $message->getAttribute('to');
|
||||
|
||||
/**
|
||||
* @var Document[] $recipients
|
||||
*/
|
||||
$recipients = [];
|
||||
|
||||
$topics = $this->dbForProject->find('topics', [Query::equal('$id', $recipientsId)]);
|
||||
foreach ($topics as $topic) {
|
||||
$recipients = \array_merge($recipients, $topic->getAttribute('targets'));
|
||||
}
|
||||
|
||||
if (empty($this->from)) {
|
||||
Console::info('Skipped sms processing. No phone number has been set.');
|
||||
return;
|
||||
$users = $this->dbForProject->find('users', [Query::equal('$id', $recipientsId)]);
|
||||
foreach ($users as $user) {
|
||||
$recipients = \array_merge($recipients, $user->getAttribute('targets'));
|
||||
}
|
||||
|
||||
$message = new SMS(
|
||||
to: [$this->args['recipient']],
|
||||
content: $this->args['message'],
|
||||
from: $this->from,
|
||||
);
|
||||
$targets = $this->dbForProject->find('targets', [Query::equal('$id', $recipientsId)]);
|
||||
$recipients = \array_merge($recipients, $targets);
|
||||
$recipients = \array_filter($recipients, function (Document $recipient) use ($provider) {
|
||||
return $recipient->getAttribute('providerId') === $provider->getId();
|
||||
});
|
||||
|
||||
try {
|
||||
$this->sms->send($message);
|
||||
} catch (\Exception $error) {
|
||||
throw new Exception('Error sending message: ' . $error->getMessage(), 500);
|
||||
$identifiers = \array_map(function (Document $recipient) {
|
||||
return $recipient->getAttribute('identifier');
|
||||
}, $recipients);
|
||||
|
||||
$maxBatchSize = $adapter->getMaxMessagesPerRequest();
|
||||
$batches = \array_chunk($identifiers, $maxBatchSize);
|
||||
$batchIndex = 0;
|
||||
|
||||
$results = batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) {
|
||||
return function () use ($batch, $message, $provider, $adapter, $batchIndex) {
|
||||
$deliveredTo = 0;
|
||||
$deliveryErrors = [];
|
||||
$messageData = clone $message;
|
||||
$messageData->setAttribute('to', $batch);
|
||||
$data = match ($provider->getAttribute('type')) {
|
||||
'sms' => $this->buildSMSMessage($messageData, $provider),
|
||||
'push' => $this->buildPushMessage($messageData),
|
||||
'email' => $this->buildEmailMessage($messageData, $provider),
|
||||
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
|
||||
};
|
||||
try {
|
||||
$adapter->send($data);
|
||||
$deliveredTo += \count($batch);
|
||||
} catch (\Exception $e) {
|
||||
$deliveryErrors[] = 'Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage();
|
||||
} finally {
|
||||
$batchIndex++;
|
||||
return [
|
||||
'deliveredTo' => $deliveredTo,
|
||||
'deliveryErrors' => $deliveryErrors,
|
||||
];
|
||||
}
|
||||
};
|
||||
}, $batches));
|
||||
|
||||
$deliveredTo = 0;
|
||||
$deliveryErrors = [];
|
||||
foreach ($results as $result) {
|
||||
$deliveredTo += $result['deliveredTo'];
|
||||
$deliveryErrors = \array_merge($deliveryErrors, $result['deliveryErrors']);
|
||||
}
|
||||
$message->setAttribute('deliveryErrors', $deliveryErrors);
|
||||
|
||||
if (\count($message->getAttribute('deliveryErrors')) > 0) {
|
||||
$message->setAttribute('status', 'failed');
|
||||
} else {
|
||||
$message->setAttribute('status', 'sent');
|
||||
}
|
||||
$message->setAttribute('to', $recipientsId);
|
||||
$message->setAttribute('deliveredTo', $deliveredTo);
|
||||
$message->setAttribute('deliveredAt', DateTime::now());
|
||||
|
||||
$this->dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
}
|
||||
|
||||
public function shutdown(): void
|
||||
{
|
||||
}
|
||||
|
||||
private function sms(Document $provider): ?SMSAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'mock' => new Mock('username', 'password'),
|
||||
'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']),
|
||||
'text-magic' => new TextMagic($credentials['username'], $credentials['apiKey']),
|
||||
'telesign' => new Telesign($credentials['username'], $credentials['password']),
|
||||
'msg91' => new Msg91($credentials['senderId'], $credentials['authKey']),
|
||||
'vonage' => new Vonage($credentials['apiKey'], $credentials['apiSecret']),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function push(Document $provider): ?PushAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'apns' => new APNS(
|
||||
$credentials['authKey'],
|
||||
$credentials['authKeyId'],
|
||||
$credentials['teamId'],
|
||||
$credentials['bundleId'],
|
||||
$credentials['endpoint']
|
||||
),
|
||||
'fcm' => new FCM($credentials['serverKey']),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function email(Document $provider): ?EmailAdapter
|
||||
{
|
||||
$credentials = $provider->getAttribute('credentials');
|
||||
return match ($provider->getAttribute('provider')) {
|
||||
'mailgun' => new Mailgun($credentials['apiKey'], $credentials['domain'], $credentials['isEuRegion']),
|
||||
'sendgrid' => new SendGrid($credentials['apiKey']),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
private function buildEmailMessage(Document $message, Document $provider): Email
|
||||
{
|
||||
$from = $provider['options']['from'];
|
||||
$to = $message['to'];
|
||||
$subject = $message['data']['subject'];
|
||||
$content = $message['data']['content'];
|
||||
$html = $message['data']['html'];
|
||||
|
||||
return new Email($to, $subject, $content, $from, null, $html);
|
||||
}
|
||||
|
||||
private function buildSMSMessage(Document $message, Document $provider): SMS
|
||||
{
|
||||
$to = $message['to'];
|
||||
$content = $message['data']['content'];
|
||||
$from = $provider['options']['from'];
|
||||
|
||||
return new SMS($to, $content, $from);
|
||||
}
|
||||
|
||||
private function buildPushMessage(Document $message): Push
|
||||
{
|
||||
$to = $message['to'];
|
||||
$title = $message['data']['title'];
|
||||
$body = $message['data']['body'];
|
||||
$data = $message['data']['data'];
|
||||
$action = $message['data']['action'];
|
||||
$sound = $message['data']['sound'];
|
||||
$icon = $message['data']['icon'];
|
||||
$color = $message['data']['color'];
|
||||
$tag = $message['data']['tag'];
|
||||
$badge = $message['data']['badge'];
|
||||
|
||||
return new Push($to, $title, $body, $data, $action, $sound, $icon, $color, $tag, $badge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
|
||||
then
|
||||
export REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
else
|
||||
export REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||
fi
|
||||
|
||||
INTERVAL=1 php /usr/src/code/vendor/bin/resque-scheduler -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
||||
+4
-4
@@ -43,20 +43,20 @@
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.13.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "0.31.*",
|
||||
"utopia-php/abuse": "0.32.*",
|
||||
"utopia-php/analytics": "0.10.*",
|
||||
"utopia-php/audit": "0.33.*",
|
||||
"utopia-php/audit": "0.34.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.43.*",
|
||||
"utopia-php/database": "0.44.*",
|
||||
"utopia-php/domains": "0.3.*",
|
||||
"utopia-php/dsn": "0.1.*",
|
||||
"utopia-php/framework": "0.31.0",
|
||||
"utopia-php/image": "0.5.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/messaging": "0.1.*",
|
||||
"utopia-php/messaging": "0.2.*",
|
||||
"utopia-php/migration": "0.3.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.4.*",
|
||||
|
||||
Generated
+37
-37
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "13a3bdc7c1dec5756bf58ec73a49753d",
|
||||
"content-hash": "ee4518740e581a9a4889936fb584a5a4",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -156,11 +156,11 @@
|
||||
},
|
||||
{
|
||||
"name": "appwrite/php-runtimes",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/runtimes.git",
|
||||
"reference": "5ab496b3908992b39275994a23783701c4b3de84"
|
||||
"reference": "b584d19cdcd82737d0ee5c34d23de791f5ed3610"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
@@ -195,7 +195,7 @@
|
||||
"php",
|
||||
"runtimes"
|
||||
],
|
||||
"time": "2023-09-12T19:38:43+00:00"
|
||||
"time": "2023-10-16T15:39:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-qrcode",
|
||||
@@ -1050,16 +1050,16 @@
|
||||
},
|
||||
{
|
||||
"name": "matomo/device-detector",
|
||||
"version": "6.1.5",
|
||||
"version": "6.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matomo-org/device-detector.git",
|
||||
"reference": "40ca2990dba2c1719e5c62168e822e0b86c167d4"
|
||||
"reference": "5cbea85106e561c7138d03603eb6e05128480409"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/40ca2990dba2c1719e5c62168e822e0b86c167d4",
|
||||
"reference": "40ca2990dba2c1719e5c62168e822e0b86c167d4",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/5cbea85106e561c7138d03603eb6e05128480409",
|
||||
"reference": "5cbea85106e561c7138d03603eb6e05128480409",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1115,7 +1115,7 @@
|
||||
"source": "https://github.com/matomo-org/matomo",
|
||||
"wiki": "https://dev.matomo.org/"
|
||||
},
|
||||
"time": "2023-08-17T16:17:41+00:00"
|
||||
"time": "2023-10-02T10:01:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mongodb/mongodb",
|
||||
@@ -1861,23 +1861,23 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/abuse",
|
||||
"version": "0.31.1",
|
||||
"version": "0.32.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/abuse.git",
|
||||
"reference": "b2ad372d1070f55f9545cb811b6ed2d40094e6dd"
|
||||
"reference": "9717ffb2d7711f3fd621bb6df3edf5724c08ea78"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/b2ad372d1070f55f9545cb811b6ed2d40094e6dd",
|
||||
"reference": "b2ad372d1070f55f9545cb811b6ed2d40094e6dd",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/9717ffb2d7711f3fd621bb6df3edf5724c08ea78",
|
||||
"reference": "9717ffb2d7711f3fd621bb6df3edf5724c08ea78",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.43.*"
|
||||
"utopia-php/database": "0.44.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.5.*",
|
||||
@@ -1904,9 +1904,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/abuse/issues",
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.31.1"
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.32.0"
|
||||
},
|
||||
"time": "2023-08-29T11:07:46+00:00"
|
||||
"time": "2023-10-18T07:28:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
@@ -1956,21 +1956,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "0.33.1",
|
||||
"version": "0.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "c117e8e9ce4e3e1b369e8b5b55b2d6ab3138eadd"
|
||||
"reference": "cf34cc3f9f20da4e574a9be4517e1a11025a858f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/c117e8e9ce4e3e1b369e8b5b55b2d6ab3138eadd",
|
||||
"reference": "c117e8e9ce4e3e1b369e8b5b55b2d6ab3138eadd",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/cf34cc3f9f20da4e574a9be4517e1a11025a858f",
|
||||
"reference": "cf34cc3f9f20da4e574a9be4517e1a11025a858f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.43.*"
|
||||
"utopia-php/database": "0.44.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.5.*",
|
||||
@@ -1997,9 +1997,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.33.1"
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.34.0"
|
||||
},
|
||||
"time": "2023-08-29T11:07:40+00:00"
|
||||
"time": "2023-10-18T07:43:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
@@ -2152,16 +2152,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "0.43.4",
|
||||
"version": "0.44.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "cabdd02e8dc1732eb0b22007c511e7bb3caa5c8c"
|
||||
"reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/cabdd02e8dc1732eb0b22007c511e7bb3caa5c8c",
|
||||
"reference": "cabdd02e8dc1732eb0b22007c511e7bb3caa5c8c",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/591cadbc2c622a3304aae9a16ebfdbe75ef33a06",
|
||||
"reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2202,9 +2202,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.43.4"
|
||||
"source": "https://github.com/utopia-php/database/tree/0.44.2"
|
||||
},
|
||||
"time": "2023-09-28T09:00:05+00:00"
|
||||
"time": "2023-10-19T07:39:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
@@ -2516,16 +2516,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/messaging",
|
||||
"version": "0.1.1",
|
||||
"version": "0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/messaging.git",
|
||||
"reference": "a75d66ddd59b834ab500a4878a2c084e6572604a"
|
||||
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/a75d66ddd59b834ab500a4878a2c084e6572604a",
|
||||
"reference": "a75d66ddd59b834ab500a4878a2c084e6572604a",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/2d0f474a106bb1da285f85e105c29b46085d3a43",
|
||||
"reference": "2d0f474a106bb1da285f85e105c29b46085d3a43",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2534,8 +2534,8 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.2",
|
||||
"phpmailer/phpmailer": "6.6.*",
|
||||
"phpunit/phpunit": "9.5.*"
|
||||
"phpmailer/phpmailer": "6.8.*",
|
||||
"phpunit/phpunit": "9.6.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -2558,9 +2558,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/messaging/issues",
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.1.1"
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.2.0"
|
||||
},
|
||||
"time": "2023-02-07T05:42:46+00:00"
|
||||
"time": "2023-09-14T20:48:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
|
||||
+39
-3
@@ -188,7 +188,17 @@ services:
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
|
||||
- _APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID
|
||||
- _APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY
|
||||
- _APP_MESSAGE_SMS_PROVIDER_MSG91_FROM
|
||||
- _APP_MESSAGE_SMS_PROVIDER_MSG91_TO
|
||||
- _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_API_KEY
|
||||
- _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_DOMAIN
|
||||
- _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_FROM
|
||||
- _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_RECEIVER_EMAIL
|
||||
- _APP_MESSAGE_EMAIL_PROVIDER_MAILGUN_IS_EU_REGION
|
||||
- _APP_MESSAGE_PUSH_PROVIDER_FCM_SERVERY_KEY
|
||||
- _APP_MESSAGE_PUSH_PROVIDER_FCM_RECEIVER_TOKEN
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
<<: *x-logging
|
||||
@@ -571,8 +581,34 @@ services:
|
||||
- _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
|
||||
|
||||
appwrite-resque-scheduler:
|
||||
entrypoint: resque-scheduler
|
||||
<<: *x-logging
|
||||
container_name: appwrite-resque-scheduler
|
||||
restart: unless-stopped
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ Finally, you will need to create a `feat-XXX-YYY-oauth` branch based on the `mas
|
||||
The first step in adding a new OAuth2 provider is to add it to the list of providers located at:
|
||||
|
||||
```
|
||||
app/config/providers.php
|
||||
app/config/authProviders.php
|
||||
```
|
||||
|
||||
Make sure to fill in all data needed and that your provider array key name:
|
||||
@@ -45,7 +45,7 @@ Make sure to fill in all data needed and that your provider array key name:
|
||||
- is in [`camelCase`](https://en.wikipedia.org/wiki/Camel_case) format for sentence, but lowercase for names. `github` must be all lowercased, but `paypalSandbox` should have uppercase S
|
||||
- has no spaces or special characters
|
||||
|
||||
> Please make sure to keep the list of providers in `providers.php` in the alphabetical order A-Z.
|
||||
> Please make sure to keep the list of providers in `authProviders.php` in the alphabetical order A-Z.
|
||||
|
||||
### 2.2 Add Provider Logo
|
||||
|
||||
@@ -199,7 +199,7 @@ If you need any help with the contribution, feel free to head over to [our Disco
|
||||
|
||||
If your OAuth provider requires special configuration apart from `clientId` and `clientSecret` you can create a custom form. Currently this is being realized through putting all custom fields as JSON into the `clientSecret` field to keep the project API stable. You can implement your custom form following these steps:
|
||||
|
||||
1. Add your custom form in `app/views/console/users/oauth/[PROVIDER].phtml`. Below is a template you can use. Add the filename to `app/config/providers.php`.
|
||||
1. Add your custom form in `app/views/console/users/oauth/[PROVIDER].phtml`. Below is a template you can use. Add the filename to `app/config/authProviders.php`.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@appwrite.io/repo",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/appwrite/appwrite.git"
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
<directory>./tests/e2e/Services/Teams</directory>
|
||||
<directory>./tests/e2e/Services/Users</directory>
|
||||
<directory>./tests/e2e/Services/Webhooks</directory>
|
||||
<directory>./tests/e2e/Services/Messaging</directory>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsCustomServerTest.php</file>
|
||||
<file>./tests/e2e/Services/Functions/FunctionsCustomClientTest.php</file>
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Resque;
|
||||
use ResqueScheduler;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
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.
|
||||
*
|
||||
* @param string $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessageId(string $messageId): self
|
||||
{
|
||||
$this->messageId = $messageId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set message ID for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageId(): string
|
||||
{
|
||||
return $this->messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Delivery time for the messaging event.
|
||||
*
|
||||
* @param string $deliveryTime
|
||||
* @return self
|
||||
*/
|
||||
public function setDeliveryTime(string $deliveryTime): self
|
||||
{
|
||||
$this->deliveryTime = $deliveryTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set Delivery Time for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDeliveryTime(): string
|
||||
{
|
||||
return $this->deliveryTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set project for this event.
|
||||
*
|
||||
* @param Document $project
|
||||
* @return self
|
||||
*/
|
||||
public function setProject(Document $project): self
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the event and sends it to the messaging worker.
|
||||
* @return string|bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function trigger(): string | bool
|
||||
{
|
||||
return Resque::enqueue($this->queue, $this->class, [
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'messageId' => $this->messageId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the messaging event and schedules it in the messaging worker queue.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Resque_Exception
|
||||
* @throws \ResqueScheduler_InvalidTimestampException
|
||||
*/
|
||||
public function schedule(): void
|
||||
{
|
||||
ResqueScheduler::enqueueAt(new \DateTime($this->deliveryTime, new \DateTimeZone('UTC')), $this->queue, $this->class, [
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'messageId' => $this->messageId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Resque;
|
||||
|
||||
class Phone extends Event
|
||||
{
|
||||
protected string $recipient = '';
|
||||
protected string $message = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(Event::MESSAGING_QUEUE_NAME, Event::MESSAGING_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the messaging event.
|
||||
*
|
||||
* @param string $recipient
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipient(string $recipient): self
|
||||
{
|
||||
$this->recipient = $recipient;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for this messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRecipient(): string
|
||||
{
|
||||
return $this->recipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets url for the messaging event.
|
||||
*
|
||||
* @param string $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessage(string $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set url for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the event and sends it to the messaging worker.
|
||||
*
|
||||
* @return string|bool
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
return Resque::enqueue($this->queue, $this->class, [
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'payload' => $this->payload,
|
||||
'recipient' => $this->recipient,
|
||||
'message' => $this->message,
|
||||
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,8 @@ class Exception extends \Exception
|
||||
public const USER_OAUTH2_BAD_REQUEST = 'user_oauth2_bad_request';
|
||||
public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized';
|
||||
public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error';
|
||||
|
||||
public const USER_TARGET_NOT_FOUND = 'user_target_not_found';
|
||||
public const USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists';
|
||||
/** Teams */
|
||||
public const TEAM_NOT_FOUND = 'team_not_found';
|
||||
public const TEAM_INVITE_ALREADY_EXISTS = 'team_invite_already_exists';
|
||||
@@ -224,6 +225,23 @@ class Exception extends \Exception
|
||||
public const MIGRATION_ALREADY_EXISTS = 'migration_already_exists';
|
||||
public const MIGRATION_IN_PROGRESS = 'migration_in_progress';
|
||||
|
||||
/** Provider */
|
||||
public const PROVIDER_NOT_FOUND = 'provider_not_found';
|
||||
public const PROVIDER_ALREADY_EXISTS = 'provider_already_exists';
|
||||
public const PROVIDER_INCORRECT_TYPE = 'provider_incorrect_type';
|
||||
|
||||
/** Topic */
|
||||
public const TOPIC_NOT_FOUND = 'topic_not_found';
|
||||
public const TOPIC_ALREADY_EXISTS = 'topic_already_exists';
|
||||
|
||||
/** Subscriber */
|
||||
public const SUBSCRIBER_NOT_FOUND = 'subscriber_not_found';
|
||||
public const SUBSCRIBER_ALREADY_EXISTS = 'subscriber_already_exists';
|
||||
|
||||
/** Message */
|
||||
public const MESSAGE_NOT_FOUND = 'message_not_found';
|
||||
public const MESSAGE_ALREADY_SENT = 'message_already_sent';
|
||||
|
||||
protected $type = '';
|
||||
protected $errors = [];
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ class V15 extends Migration
|
||||
['email', 'anonymous'],
|
||||
\array_map(
|
||||
fn ($value) => "oauth-" . $value,
|
||||
\array_keys(Config::getParam('providers', []))
|
||||
\array_keys(Config::getParam('authProviders', []))
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ class V16 extends Migration
|
||||
*/
|
||||
$authProviders = $document->getAttribute('authProviders', []);
|
||||
|
||||
foreach (Config::getParam('providers') as $provider => $value) {
|
||||
foreach (Config::getParam('authProviders') as $provider => $value) {
|
||||
if (!$value['enabled']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Messages extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'to',
|
||||
'providerId',
|
||||
'deliveredAt',
|
||||
'deliveredTo',
|
||||
'deliveryErrors',
|
||||
'status',
|
||||
'description',
|
||||
'data',
|
||||
'search'
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('messages', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Providers extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'name',
|
||||
'provider',
|
||||
'type',
|
||||
'default',
|
||||
'enabled',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('providers', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Subscribers extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'targetId',
|
||||
'topicId'
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('subscribers', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Topics extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('topics', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ use Appwrite\Utopia\Response\Model\AttributeIP;
|
||||
use Appwrite\Utopia\Response\Model\AttributeURL;
|
||||
use Appwrite\Utopia\Response\Model\AttributeDatetime;
|
||||
use Appwrite\Utopia\Response\Model\AttributeRelationship;
|
||||
use Appwrite\Utopia\Response\Model\AuthProvider;
|
||||
use Appwrite\Utopia\Response\Model\BaseList;
|
||||
use Appwrite\Utopia\Response\Model\Branch;
|
||||
use Appwrite\Utopia\Response\Model\Collection;
|
||||
@@ -80,8 +81,12 @@ use Appwrite\Utopia\Response\Model\HealthVersion;
|
||||
use Appwrite\Utopia\Response\Model\Installation;
|
||||
use Appwrite\Utopia\Response\Model\LocaleCode;
|
||||
use Appwrite\Utopia\Response\Model\Provider;
|
||||
use Appwrite\Utopia\Response\Model\Message;
|
||||
use Appwrite\Utopia\Response\Model\Subscriber;
|
||||
use Appwrite\Utopia\Response\Model\Topic;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepository;
|
||||
use Appwrite\Utopia\Response\Model\Runtime;
|
||||
use Appwrite\Utopia\Response\Model\Target;
|
||||
use Appwrite\Utopia\Response\Model\TemplateSMS;
|
||||
use Appwrite\Utopia\Response\Model\UsageBuckets;
|
||||
use Appwrite\Utopia\Response\Model\UsageCollection;
|
||||
@@ -191,6 +196,18 @@ class Response extends SwooleResponse
|
||||
public const MODEL_PHONE = 'phone';
|
||||
public const MODEL_PHONE_LIST = 'phoneList';
|
||||
|
||||
// Messaging
|
||||
public const MODEL_PROVIDER = 'provider';
|
||||
public const MODEL_PROVIDER_LIST = 'providerList';
|
||||
public const MODEL_MESSAGE = 'message';
|
||||
public const MODEL_MESSAGE_LIST = 'messageList';
|
||||
public const MODEL_TOPIC = 'topic';
|
||||
public const MODEL_TOPIC_LIST = 'topicList';
|
||||
public const MODEL_SUBSCRIBER = 'subscriber';
|
||||
public const MODEL_SUBSCRIBER_LIST = 'subscriberList';
|
||||
public const MODEL_TARGET = 'target';
|
||||
public const MODEL_TARGET_LIST = 'targetList';
|
||||
|
||||
// Teams
|
||||
public const MODEL_TEAM = 'team';
|
||||
public const MODEL_TEAM_LIST = 'teamList';
|
||||
@@ -238,8 +255,8 @@ class Response extends SwooleResponse
|
||||
public const MODEL_WEBHOOK_LIST = 'webhookList';
|
||||
public const MODEL_KEY = 'key';
|
||||
public const MODEL_KEY_LIST = 'keyList';
|
||||
public const MODEL_PROVIDER = 'provider';
|
||||
public const MODEL_PROVIDER_LIST = 'providerList';
|
||||
public const MODEL_AUTH_PROVIDER = 'authProvider';
|
||||
public const MODEL_AUTH_PROVIDER_LIST = 'authProviderList';
|
||||
public const MODEL_PLATFORM = 'platform';
|
||||
public const MODEL_PLATFORM_LIST = 'platformList';
|
||||
public const MODEL_VARIABLE = 'variable';
|
||||
@@ -316,7 +333,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false))
|
||||
->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false))
|
||||
->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false))
|
||||
->setModel(new BaseList('Providers List', self::MODEL_PROVIDER_LIST, 'platforms', self::MODEL_PROVIDER, true, false))
|
||||
->setModel(new BaseList('Auth Providers List', self::MODEL_AUTH_PROVIDER_LIST, 'platforms', self::MODEL_AUTH_PROVIDER, true, false))
|
||||
->setModel(new BaseList('Platforms List', self::MODEL_PLATFORM_LIST, 'platforms', self::MODEL_PLATFORM, true, false))
|
||||
->setModel(new BaseList('Countries List', self::MODEL_COUNTRY_LIST, 'countries', self::MODEL_COUNTRY))
|
||||
->setModel(new BaseList('Continents List', self::MODEL_CONTINENT_LIST, 'continents', self::MODEL_CONTINENT))
|
||||
@@ -328,6 +345,11 @@ class Response extends SwooleResponse
|
||||
->setModel(new BaseList('Status List', self::MODEL_HEALTH_STATUS_LIST, 'statuses', self::MODEL_HEALTH_STATUS))
|
||||
->setModel(new BaseList('Rule List', self::MODEL_PROXY_RULE_LIST, 'rules', self::MODEL_PROXY_RULE))
|
||||
->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE))
|
||||
->setModel(new BaseList('Provider list', self::MODEL_PROVIDER_LIST, 'providers', self::MODEL_PROVIDER))
|
||||
->setModel(new BaseList('Message list', self::MODEL_MESSAGE_LIST, 'messages', self::MODEL_MESSAGE))
|
||||
->setModel(new BaseList('Topic list', self::MODEL_TOPIC_LIST, 'topics', self::MODEL_TOPIC))
|
||||
->setModel(new BaseList('Subscriber list', self::MODEL_SUBSCRIBER_LIST, 'subscribers', self::MODEL_SUBSCRIBER))
|
||||
->setModel(new BaseList('Target list', self::MODEL_TARGET_LIST, 'targets', self::MODEL_TARGET))
|
||||
->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION))
|
||||
->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT))
|
||||
// Entities
|
||||
@@ -380,7 +402,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new Project())
|
||||
->setModel(new Webhook())
|
||||
->setModel(new Key())
|
||||
->setModel(new Provider())
|
||||
->setModel(new AuthProvider())
|
||||
->setModel(new Platform())
|
||||
->setModel(new Variable())
|
||||
->setModel(new Country())
|
||||
@@ -408,6 +430,11 @@ class Response extends SwooleResponse
|
||||
->setModel(new TemplateSMS())
|
||||
->setModel(new TemplateEmail())
|
||||
->setModel(new ConsoleVariables())
|
||||
->setModel(new Provider())
|
||||
->setModel(new Message())
|
||||
->setModel(new Topic())
|
||||
->setModel(new Subscriber())
|
||||
->setModel(new Target())
|
||||
->setModel(new Migration())
|
||||
->setModel(new MigrationReport())
|
||||
->setModel(new MigrationFirebaseProject())
|
||||
|
||||
@@ -88,9 +88,9 @@ class V16 extends Filter
|
||||
|
||||
protected function parseProject(array $content)
|
||||
{
|
||||
foreach ($content['providers'] ?? [] as $i => $provider) {
|
||||
$content['providers'][$i]['name'] = \ucfirst($provider['key']);
|
||||
unset($content['providers'][$i]['key']);
|
||||
foreach ($content['authProviders'] ?? [] as $i => $provider) {
|
||||
$content['authProviders'][$i]['name'] = \ucfirst($provider['key']);
|
||||
unset($content['authProviders'][$i]['key']);
|
||||
}
|
||||
|
||||
$content['domains'] = [];
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class AuthProvider extends Model
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('key', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Auth Provider.',
|
||||
'default' => '',
|
||||
'example' => 'github',
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Auth Provider name.',
|
||||
'default' => '',
|
||||
'example' => 'GitHub',
|
||||
])
|
||||
->addRule('appId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'OAuth 2.0 application ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('secret', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration.',
|
||||
'default' => '',
|
||||
'example' => 'Bpw_g9c2TGXxfgLshDbSaL8tsCcqgczQ',
|
||||
])
|
||||
->addRule('enabled', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Auth Provider is active and can be used to create session.',
|
||||
'example' => '',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'AuthProvider';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_AUTH_PROVIDER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\DateTime;
|
||||
|
||||
class Message extends Any
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Message ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('providerId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Provider ID for the message.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('to', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Message recipients.',
|
||||
'default' => '',
|
||||
'array' => true,
|
||||
'example' => ['user-1'],
|
||||
])
|
||||
->addRule('deliveryTime', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'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.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => true,
|
||||
'example' => ['Failed to send message to target 5e5ea5c16897e: Credentials not valid.'],
|
||||
])
|
||||
->addRule('deliveredTo', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Number of recipients the message was delivered to.',
|
||||
'default' => 0,
|
||||
'example' => 1,
|
||||
])
|
||||
->addRule('data', [
|
||||
'type' => self::TYPE_JSON,
|
||||
'description' => 'Data of the message.',
|
||||
'default' => [],
|
||||
'example' => [
|
||||
'subject' => 'Welcome to Appwrite',
|
||||
'content' => 'Hi there, welcome to Appwrite family.',
|
||||
],
|
||||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Status of delivery.',
|
||||
'default' => 'processing',
|
||||
'example' => 'Message status can be one of the following: processing, sent, failed.',
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Message description.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'example' => 'Welcome Email.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Message';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_MESSAGE;
|
||||
}
|
||||
}
|
||||
@@ -138,9 +138,9 @@ class Project extends Model
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('providers', [
|
||||
'type' => Response::MODEL_PROVIDER,
|
||||
'description' => 'List of Providers.',
|
||||
->addRule('authProviders', [
|
||||
'type' => Response::MODEL_AUTH_PROVIDER,
|
||||
'description' => 'List of Auth Providers.',
|
||||
'default' => [],
|
||||
'example' => [new \stdClass()],
|
||||
'array' => true,
|
||||
@@ -329,7 +329,7 @@ class Project extends Model
|
||||
}
|
||||
|
||||
// Providers
|
||||
$providers = Config::getParam('providers', []);
|
||||
$providers = Config::getParam('authProviders', []);
|
||||
$providerValues = $document->getAttribute('authProviders', []);
|
||||
$projectProviders = [];
|
||||
|
||||
@@ -348,7 +348,7 @@ class Project extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
$document->setAttribute("providers", $projectProviders);
|
||||
$document->setAttribute('authProviders', $projectProviders);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
@@ -7,44 +7,62 @@ use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Provider extends Model
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('key', [
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Provider.',
|
||||
'description' => 'Provider ID.',
|
||||
'default' => '',
|
||||
'example' => 'github',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Provider name.',
|
||||
'description' => 'The name for the provider instance.',
|
||||
'default' => '',
|
||||
'example' => 'GitHub',
|
||||
'example' => 'Mailgun',
|
||||
])
|
||||
->addRule('appId', [
|
||||
->addRule('provider', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'OAuth 2.0 application ID.',
|
||||
'description' => 'The name of the provider service.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
'example' => 'mailgun',
|
||||
])
|
||||
->addRule('secret', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'OAuth 2.0 application secret. Might be JSON string if provider requires extra configuration.',
|
||||
'default' => '',
|
||||
'example' => 'Bpw_g9c2TGXxfgLshDbSaL8tsCcqgczQ',
|
||||
->addRule('default', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Is this a pre-configured provider instance?',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('enabled', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Provider is active and can be used to create session.',
|
||||
'example' => '',
|
||||
'description' => 'Is provider enabled?',
|
||||
'default' => true,
|
||||
'example' => true,
|
||||
])
|
||||
;
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Type of provider.',
|
||||
'default' => '',
|
||||
'example' => 'sms',
|
||||
])
|
||||
->addRule('credentials', [
|
||||
'type' => self::TYPE_JSON,
|
||||
'description' => 'Provider credentials.',
|
||||
'default' => [],
|
||||
'example' => [
|
||||
'key' => '123456789'
|
||||
],
|
||||
])
|
||||
->addRule('options', [
|
||||
'type' => self::TYPE_JSON,
|
||||
'description' => 'Provider options.',
|
||||
'default' => [],
|
||||
'required' => false,
|
||||
'example' => [
|
||||
'from' => 'sender-email@mydomain'
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Subscriber extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Subscriber ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('targetId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Target ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('topicId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Topic ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Subscriber';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_SUBSCRIBER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Target extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Target ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('userId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('providerId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Provider ID.',
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('identifier', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The target identifier.',
|
||||
'default' => '',
|
||||
'example' => 'token',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Target';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_TARGET;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Topic extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Topic ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('providerId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Provider ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The name of the topic.',
|
||||
'default' => '',
|
||||
'example' => 'events',
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Description of the topic.',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'example' => 'All events related messages will be sent to this topic.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Topic';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_TOPIC;
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,13 @@ class User extends Model
|
||||
'default' => new \stdClass(),
|
||||
'example' => ['theme' => 'pink', 'timezone' => 'UTC'],
|
||||
])
|
||||
->addRule('targets', [
|
||||
'type' => Response::MODEL_TARGET,
|
||||
'description' => 'A user-owned message receiver. A single user may have multiple e.g. emails, phones, and a browser. Each target is registered with a single provider.',
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'example' => [],
|
||||
])
|
||||
->addRule('accessedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_USER_ACCCESS / 60 / 60 . ' hours.',
|
||||
|
||||
@@ -81,6 +81,16 @@ trait ProjectCustom
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'health.read',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'providers.read',
|
||||
'providers.write',
|
||||
'messages.read',
|
||||
'messages.write',
|
||||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read'
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
namespace Tests\E2E\Services\Account;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SMS\Adapter\Mock;
|
||||
use Appwrite\Tests\Retry;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
@@ -744,8 +743,29 @@ class AccountCustomClientTest extends Scope
|
||||
|
||||
public function testCreatePhone(): array
|
||||
{
|
||||
$number = '+123456789';
|
||||
$to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_TO');
|
||||
$from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM');
|
||||
$authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY');
|
||||
$senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID');
|
||||
|
||||
if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) {
|
||||
$this->markTestSkipped('SMS provider not configured');
|
||||
}
|
||||
|
||||
$number = $to;
|
||||
$response = $this->client->call(Client::METHOD_POST, '/messaging/providers/msg91', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]), [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Sms provider',
|
||||
'senderId' => $senderId,
|
||||
'authKey' => $authKey,
|
||||
'default' => true,
|
||||
'from' => $from,
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
@@ -756,6 +776,7 @@ class AccountCustomClientTest extends Scope
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'phone' => $number,
|
||||
'from' => $from,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
@@ -764,6 +785,7 @@ class AccountCustomClientTest extends Scope
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
$messageId = $response['body']['$id'];
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
@@ -780,17 +802,19 @@ class AccountCustomClientTest extends Scope
|
||||
|
||||
\sleep(5);
|
||||
|
||||
$smsRequest = $this->getLastRequest();
|
||||
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('http://request-catcher:5000/mock-sms', $smsRequest['url']);
|
||||
$this->assertEquals('Appwrite Mock Message Sender', $smsRequest['headers']['User-Agent']);
|
||||
$this->assertEquals('username', $smsRequest['headers']['X-Username']);
|
||||
$this->assertEquals('password', $smsRequest['headers']['X-Key']);
|
||||
$this->assertEquals('POST', $smsRequest['method']);
|
||||
$this->assertEquals('+123456789', $smsRequest['data']['from']);
|
||||
$this->assertEquals($number, $smsRequest['data']['to']);
|
||||
$this->assertEquals(200, $message['headers']['status-code']);
|
||||
$this->assertEquals(1, $message['body']['deliveredTo']);
|
||||
$this->assertEquals(0, \count($message['body']['deliveryErrors']));
|
||||
|
||||
$data['token'] = $smsRequest['data']['message'];
|
||||
|
||||
$data['token'] = $message['body']['data']['content'];
|
||||
$data['id'] = $userId;
|
||||
$data['number'] = $number;
|
||||
|
||||
@@ -999,19 +1023,28 @@ class AccountCustomClientTest extends Scope
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
|
||||
]));
|
||||
]), ['from' => App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM')]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
\sleep(2);
|
||||
\sleep(3);
|
||||
|
||||
$smsRequest = $this->getLastRequest();
|
||||
$message = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $response['body']['$id'], [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $message['headers']['status-code']);
|
||||
$this->assertEquals(1, $message['body']['deliveredTo']);
|
||||
$this->assertEquals(0, \count($message['body']['deliveryErrors']));
|
||||
|
||||
return \array_merge($data, [
|
||||
'token' => $smsRequest['data']['message']
|
||||
'token' => $message['body']['data']['content']
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
class AccountTest extends Scope
|
||||
@@ -122,7 +123,35 @@ class AccountTest extends Scope
|
||||
*/
|
||||
public function testCreatePhoneVerification(): array
|
||||
{
|
||||
$to = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_TO');
|
||||
$from = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_FROM');
|
||||
$authKey = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_AUTH_KEY');
|
||||
$senderId = App::getEnv('_APP_MESSAGE_SMS_PROVIDER_MSG91_SENDER_ID');
|
||||
|
||||
if (empty($to) || empty($from) || empty($authKey) || empty($senderId)) {
|
||||
$this->markTestSkipped('SMS provider not configured');
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_MSG91_PROVIDER);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Sms Provider',
|
||||
'from' => $from,
|
||||
'senderId' => $senderId,
|
||||
'authKey' => $authKey,
|
||||
'default' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $graphQLPayload);
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_PHONE_VERIFICATION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
|
||||
@@ -107,6 +107,11 @@ trait Base
|
||||
public static string $DELETE_USER_SESSIONS = 'delete_user_sessions';
|
||||
public static string $DELETE_USER_SESSION = 'delete_user_session';
|
||||
public static string $DELETE_USER = 'delete_user';
|
||||
public static string $CREATE_USER_TARGET = 'create_user_target';
|
||||
public static string $LIST_USER_TARGETS = 'list_user_targets';
|
||||
public static string $GET_USER_TARGET = 'get_user_target';
|
||||
public static string $UPDATE_USER_TARGET = 'update_user_target';
|
||||
public static string $DELETE_USER_TARGET = 'delete_user_target';
|
||||
|
||||
// Teams
|
||||
public static string $GET_TEAM = 'get_team';
|
||||
@@ -197,6 +202,53 @@ trait Base
|
||||
public static string $GET_QRCODE = 'get_qrcode';
|
||||
public static string $GET_USER_INITIALS = 'get_user_initials';
|
||||
|
||||
// Providers
|
||||
public static string $CREATE_MAILGUN_PROVIDER = 'create_mailgun_provider';
|
||||
public static string $CREATE_SENDGRID_PROVIDER = 'create_sendgrid_provider';
|
||||
public static string $CREATE_TWILIO_PROVIDER = 'create_twilio_provider';
|
||||
public static string $CREATE_TELESIGN_PROVIDER = 'create_telesign_provider';
|
||||
public static string $CREATE_TEXTMAGIC_PROVIDER = 'create_textmagic_provider';
|
||||
public static string $CREATE_MSG91_PROVIDER = 'create_msg91_provider';
|
||||
public static string $CREATE_VONAGE_PROVIDER = 'create_vonage_provider';
|
||||
public static string $CREATE_FCM_PROVIDER = 'create_fcm_provider';
|
||||
public static string $CREATE_APNS_PROVIDER = 'create_apns_provider';
|
||||
public static string $LIST_PROVIDERS = 'list_providers';
|
||||
public static string $GET_PROVIDER = 'get_provider';
|
||||
public static string $UPDATE_MAILGUN_PROVIDER = 'update_mailgun_provider';
|
||||
public static string $UPDATE_SENDGRID_PROVIDER = 'update_sendgrid_provider';
|
||||
public static string $UPDATE_TWILIO_PROVIDER = 'update_twilio_provider';
|
||||
public static string $UPDATE_TELESIGN_PROVIDER = 'update_telesign_provider';
|
||||
public static string $UPDATE_TEXTMAGIC_PROVIDER = 'update_textmagic_provider';
|
||||
public static string $UPDATE_MSG91_PROVIDER = 'update_msg91_provider';
|
||||
public static string $UPDATE_VONAGE_PROVIDER = 'update_vonage_provider';
|
||||
public static string $UPDATE_FCM_PROVIDER = 'update_fcm_provider';
|
||||
public static string $UPDATE_APNS_PROVIDER = 'update_apns_provider';
|
||||
public static string $DELETE_PROVIDER = 'delete_provider';
|
||||
|
||||
// Topics
|
||||
public static string $CREATE_TOPIC = 'create_topic';
|
||||
public static string $LIST_TOPICS = 'list_topics';
|
||||
public static string $GET_TOPIC = 'get_topic';
|
||||
public static string $UPDATE_TOPIC = 'update_topic';
|
||||
public static string $DELETE_TOPIC = 'delete_topic';
|
||||
|
||||
// Subscriptions
|
||||
public static string $CREATE_SUBSCRIBER = 'create_subscriber';
|
||||
public static string $LIST_SUBSCRIBERS = 'list_subscribers';
|
||||
public static string $GET_SUBSCRIBER = 'get_subscriber';
|
||||
public static string $DELETE_SUBSCRIBER = 'delete_subscriber';
|
||||
|
||||
// Messages
|
||||
public static string $CREATE_EMAIL = 'create_email';
|
||||
public static string $CREATE_SMS = 'create_sms';
|
||||
public static string $CREATE_PUSH_NOTIFICATION = 'create_push_notification';
|
||||
public static string $LIST_MESSAGES = 'list_messages';
|
||||
public static string $GET_MESSAGE = 'get_message';
|
||||
|
||||
public static string $UPDATE_EMAIL = 'update_email';
|
||||
public static string $UPDATE_SMS = 'update_sms';
|
||||
public static string $UPDATE_PUSH_NOTIFICATION = 'update_push_notification';
|
||||
|
||||
// Complex queries
|
||||
public static string $COMPLEX_QUERY = 'complex_query';
|
||||
|
||||
@@ -879,6 +931,51 @@ trait Base
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_USER_TARGET:
|
||||
return 'mutation createUserTarget($userId: String!, $targetId: String!, $providerId: String!, $identifier: String!){
|
||||
usersCreateTarget(userId: $userId, targetId: $targetId, providerId: $providerId, identifier: $identifier) {
|
||||
_id
|
||||
userId
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}';
|
||||
case self::$LIST_USER_TARGETS:
|
||||
return 'query listUserTargets($userId: String!) {
|
||||
usersListTargets(userId: $userId) {
|
||||
total
|
||||
targets {
|
||||
_id
|
||||
userId
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_USER_TARGET:
|
||||
return 'query getUserTarget($userId: String!, $targetId: String!) {
|
||||
usersGetTarget(userId: $userId, targetId: $targetId) {
|
||||
_id
|
||||
userId
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_USER_TARGET:
|
||||
return 'mutation updateUserTarget($userId: String!, $targetId: String!, $identifier: String!){
|
||||
usersUpdateTargetIdentifier(userId: $userId, targetId: $targetId, identifier: $identifier) {
|
||||
_id
|
||||
userId
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_USER_TARGET:
|
||||
return 'mutation deleteUserTarget($userId: String!, $targetId: String!){
|
||||
usersDeleteTarget(userId: $userId, targetId: $targetId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$GET_LOCALE:
|
||||
return 'query getLocale {
|
||||
localeGet {
|
||||
@@ -1686,6 +1783,428 @@ trait Base
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_MAILGUN_PROVIDER:
|
||||
return 'mutation createMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $from: String!) {
|
||||
messagingCreateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, from: $from) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_SENDGRID_PROVIDER:
|
||||
return 'mutation createSendgridProvider($providerId: String!, $name: String!, $apiKey: String!) {
|
||||
messagingCreateSendgridProvider(providerId: $providerId, name: $name, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TWILIO_PROVIDER:
|
||||
return 'mutation createTwilioProvider($providerId: String!, $name: String!, $accountSid: String!, $authToken: String!) {
|
||||
messagingCreateTwilioProvider(providerId: $providerId, name: $name, accountSid: $accountSid, authToken: $authToken) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TELESIGN_PROVIDER:
|
||||
return 'mutation createTelesignProvider($providerId: String!, $name: String!, $username: String!, $password: String!) {
|
||||
messagingCreateTelesignProvider(providerId: $providerId, name: $name, username: $username, password: $password) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TEXTMAGIC_PROVIDER:
|
||||
return 'mutation createTextmagicProvider($providerId: String!, $name: String!, $username: String!, $apiKey: String!) {
|
||||
messagingCreateTextmagicProvider(providerId: $providerId, name: $name, username: $username, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_MSG91_PROVIDER:
|
||||
return 'mutation createMsg91Provider($providerId: String!, $name: String!, $from: String!, $senderId: String!, $authKey: String!, $default: Boolean, $enabled: Boolean) {
|
||||
messagingCreateMsg91Provider(providerId: $providerId, name: $name, from: $from, senderId: $senderId, authKey: $authKey, default: $default, enabled: $enabled) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_VONAGE_PROVIDER:
|
||||
return 'mutation createVonageProvider($providerId: String!, $name: String!, $apiKey: String!, $apiSecret: String!) {
|
||||
messagingCreateVonageProvider(providerId: $providerId, name: $name, apiKey: $apiKey, apiSecret: $apiSecret) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_FCM_PROVIDER:
|
||||
return 'mutation createFcmProvider($providerId: String!, $name: String!, $serverKey: String!) {
|
||||
messagingCreateFcmProvider(providerId: $providerId, name: $name, serverKey: $serverKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_APNS_PROVIDER:
|
||||
return 'mutation createApnsProvider($providerId: String!, $name: String!, $authKey: String!, $authKeyId: String!, $teamId: String!, $bundleId: String!, $endpoint: String!) {
|
||||
messagingCreateApnsProvider(providerId: $providerId, name: $name, authKey: $authKey, authKeyId: $authKeyId, teamId: $teamId, bundleId: $bundleId, endpoint: $endpoint) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$LIST_PROVIDERS:
|
||||
return 'query listProviders {
|
||||
messagingListProviders {
|
||||
total
|
||||
providers {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_PROVIDER:
|
||||
return 'query getProvider($providerId: String!) {
|
||||
messagingGetProvider(providerId: $providerId) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_MAILGUN_PROVIDER:
|
||||
return 'mutation updateMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $isEuRegion: Boolean, $enabled: Boolean) {
|
||||
messagingUpdateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, isEuRegion: $isEuRegion, enabled: $enabled) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_SENDGRID_PROVIDER:
|
||||
return 'mutation messagingUpdateSendgridProvider($providerId: String!, $name: String!, $apiKey: String!) {
|
||||
messagingUpdateSendgridProvider(providerId: $providerId, name: $name, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TWILIO_PROVIDER:
|
||||
return 'mutation updateTwilioProvider($providerId: String!, $name: String!, $accountSid: String!, $authToken: String!) {
|
||||
messagingUpdateTwilioProvider(providerId: $providerId, name: $name, accountSid: $accountSid, authToken: $authToken) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TELESIGN_PROVIDER:
|
||||
return 'mutation updateTelesignProvider($providerId: String!, $name: String!, $username: String!, $password: String!) {
|
||||
messagingUpdateTelesignProvider(providerId: $providerId, name: $name, username: $username, password: $password) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TEXTMAGIC_PROVIDER:
|
||||
return 'mutation updateTextmagicProvider($providerId: String!, $name: String!, $username: String!, $apiKey: String!) {
|
||||
messagingUpdateTextmagicProvider(providerId: $providerId, name: $name, username: $username, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_MSG91_PROVIDER:
|
||||
return 'mutation updateMsg91Provider($providerId: String!, $name: String!, $senderId: String!, $authKey: String!) {
|
||||
messagingUpdateMsg91Provider(providerId: $providerId, name: $name, senderId: $senderId, authKey: $authKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_VONAGE_PROVIDER:
|
||||
return 'mutation updateVonageProvider($providerId: String!, $name: String!, $apiKey: String!, $apiSecret: String!) {
|
||||
messagingUpdateVonageProvider(providerId: $providerId, name: $name, apiKey: $apiKey, apiSecret: $apiSecret) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_FCM_PROVIDER:
|
||||
return 'mutation updateFcmProvider($providerId: String!, $name: String!, $serverKey: String!) {
|
||||
messagingUpdateFcmProvider(providerId: $providerId, name: $name, serverKey: $serverKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_APNS_PROVIDER:
|
||||
return 'mutation updateApnsProvider($providerId: String!, $name: String!, $authKey: String!, $authKeyId: String!, $teamId: String!, $bundleId: String!, $endpoint: String!) {
|
||||
messagingUpdateApnsProvider(providerId: $providerId, name: $name, authKey: $authKey, authKeyId: $authKeyId, teamId: $teamId, bundleId: $bundleId, endpoint: $endpoint) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
default
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_PROVIDER:
|
||||
return 'mutation deleteProvider($providerId: String!) {
|
||||
messagingDeleteProvider(providerId: $providerId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TOPIC:
|
||||
return 'mutation createTopic($providerId: String!, $topicId: String!, $name: String!, $description: String!) {
|
||||
messagingCreateTopic(providerId: $providerId, topicId: $topicId, name: $name, description: $description) {
|
||||
_id
|
||||
name
|
||||
providerId
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$LIST_TOPICS:
|
||||
return 'query listTopics {
|
||||
messagingListTopics {
|
||||
total
|
||||
topics {
|
||||
_id
|
||||
name
|
||||
providerId
|
||||
description
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_TOPIC:
|
||||
return 'query getTopic($topicId: String!) {
|
||||
messagingGetTopic(topicId: $topicId) {
|
||||
_id
|
||||
name
|
||||
providerId
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TOPIC:
|
||||
return 'mutation updateTopic($topicId: String!, $name: String!, $description: String!) {
|
||||
messagingUpdateTopic(topicId: $topicId, name: $name, description: $description) {
|
||||
_id
|
||||
name
|
||||
providerId
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_TOPIC:
|
||||
return 'mutation deleteTopic($topicId: String!) {
|
||||
messagingDeleteTopic(topicId: $topicId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_SUBSCRIBER:
|
||||
return 'mutation createSubscriber($subscriberId: String!, $targetId: String!, $topicId: String!) {
|
||||
messagingCreateSubscriber(subscriberId: $subscriberId, targetId: $targetId, topicId: $topicId) {
|
||||
_id
|
||||
targetId
|
||||
topicId
|
||||
}
|
||||
}';
|
||||
case self::$LIST_SUBSCRIBERS:
|
||||
return 'query listSubscribers($topicId: String!) {
|
||||
messagingListSubscribers(topicId: $topicId) {
|
||||
total
|
||||
subscribers {
|
||||
_id
|
||||
targetId
|
||||
topicId
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_SUBSCRIBER:
|
||||
return 'query getSubscriber($topicId: String!, $subscriberId: String!) {
|
||||
messagingGetSubscriber(topicId: $topicId, subscriberId: $subscriberId) {
|
||||
_id
|
||||
targetId
|
||||
topicId
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_SUBSCRIBER:
|
||||
return 'mutation deleteSubscriber($topicId: String!, $subscriberId: String!) {
|
||||
messagingDeleteSubscriber(topicId: $topicId, subscriberId: $subscriberId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_EMAIL:
|
||||
return 'mutation createEmail($messageId: String!, $providerId: String!, $to: [String!]!, $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $deliveryTime: String) {
|
||||
messagingCreateEmail(messageId: $messageId, providerId: $providerId, to: $to, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) {
|
||||
_id
|
||||
providerId
|
||||
to
|
||||
deliveryTime
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTo
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_SMS:
|
||||
return 'mutation createSMS($messageId: String!, $providerId: String!, $to: [String!]!, $content: String!, $status: String, $description: String, $deliveryTime: String) {
|
||||
messagingCreateSMS(messageId: $messageId, providerId: $providerId, to: $to, content: $content, status: $status, description: $description, deliveryTime: $deliveryTime) {
|
||||
_id
|
||||
providerId
|
||||
to
|
||||
deliveryTime
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTo
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_PUSH_NOTIFICATION:
|
||||
return 'mutation createPushNotification($messageId: String!, $providerId: String!, $to: [String!]!, $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $deliveryTime: String) {
|
||||
messagingCreatePushNotification(messageId: $messageId, providerId: $providerId, to: $to, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, deliveryTime: $deliveryTime) {
|
||||
_id
|
||||
providerId
|
||||
to
|
||||
deliveryTime
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTo
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$LIST_MESSAGES:
|
||||
return 'query listMessages {
|
||||
messagingListMessages {
|
||||
total
|
||||
messages {
|
||||
_id
|
||||
providerId
|
||||
to
|
||||
deliveryTime
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTo
|
||||
status
|
||||
description
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_MESSAGE:
|
||||
return 'query getMessage($messageId: String!) {
|
||||
messagingGetMessage(messageId: $messageId) {
|
||||
_id
|
||||
providerId
|
||||
to
|
||||
deliveryTime
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTo
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_EMAIL:
|
||||
return 'mutation updateEmail($messageId: String!, $to: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $deliveryTime: String) {
|
||||
messagingUpdateEmail(messageId: $messageId, to: $to, subject: $subject, content: $content, status: $status, description: $description, html: $html, deliveryTime: $deliveryTime) {
|
||||
_id
|
||||
providerId
|
||||
to
|
||||
deliveryTime
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTo
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_SMS:
|
||||
return 'mutation updateSMS($messageId: String!, $to: [String!], $content: String, $status: String, $description: String, $deliveryTime: String) {
|
||||
messagingUpdateSMS(messageId: $messageId, to: $to, content: $content, status: $status, description: $description, deliveryTime: $deliveryTime) {
|
||||
_id
|
||||
providerId
|
||||
to
|
||||
deliveryTime
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTo
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_PUSH_NOTIFICATION:
|
||||
return 'mutation updatePushNotification($messageId: String!, $to: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $deliveryTime: String) {
|
||||
messagingUpdatePushNotification(messageId: $messageId, to: $to, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, deliveryTime: $deliveryTime) {
|
||||
_id
|
||||
providerId
|
||||
to
|
||||
deliveryTime
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTo
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$COMPLEX_QUERY:
|
||||
return 'mutation complex($databaseId: String!, $databaseName: String!, $collectionId: String!, $collectionName: String!, $documentSecurity: Boolean!, $collectionPermissions: [String!]!) {
|
||||
databasesCreate(databaseId: $databaseId, name: $databaseName) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,55 @@ class UsersTest extends Scope
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUser
|
||||
*/
|
||||
public function testCreateUserTarget(array $user)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_MAILGUN_PROVIDER);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'providerId' => ID::unique(),
|
||||
'name' => 'Mailgun1',
|
||||
'apiKey' => 'api-key',
|
||||
'domain' => 'domain',
|
||||
'from' => 'from@domain',
|
||||
'isEuRegion' => false,
|
||||
],
|
||||
];
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
$providerId = $provider['body']['data']['messagingCreateMailgunProvider']['_id'];
|
||||
|
||||
$this->assertEquals(200, $provider['headers']['status-code']);
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_USER_TARGET);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'targetId' => ID::unique(),
|
||||
'userId' => $user['_id'],
|
||||
'providerId' => $providerId,
|
||||
'identifier' => 'identifier',
|
||||
]
|
||||
];
|
||||
|
||||
$target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(200, $target['headers']['status-code']);
|
||||
$this->assertEquals('identifier', $target['body']['data']['usersCreateTarget']['identifier']);
|
||||
|
||||
return $target['body']['data']['usersCreateTarget'];
|
||||
}
|
||||
|
||||
public function testGetUsers()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -176,6 +225,54 @@ class UsersTest extends Scope
|
||||
$this->assertIsArray($user['body']['data']['usersListLogs']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testListUserTargets(array $target)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$LIST_USER_TARGETS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $target['userId'],
|
||||
]
|
||||
];
|
||||
|
||||
$targets = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(200, $targets['headers']['status-code']);
|
||||
$this->assertIsArray($targets['body']['data']['usersListTargets']);
|
||||
$this->assertCount(1, $targets['body']['data']['usersListTargets']['targets']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testGetUserTarget(array $target)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USER_TARGET);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $target['userId'],
|
||||
'targetId' => $target['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(200, $target['headers']['status-code']);
|
||||
$this->assertEquals('identifier', $target['body']['data']['usersGetTarget']['identifier']);
|
||||
}
|
||||
|
||||
public function testUpdateUserStatus()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -360,6 +457,31 @@ class UsersTest extends Scope
|
||||
$this->assertEquals('{"key":"value"}', $user['body']['data']['usersUpdatePrefs']['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testUpdateUserTarget(array $target)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_TARGET);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $target['userId'],
|
||||
'targetId' => $target['_id'],
|
||||
'identifier' => 'newidentifier',
|
||||
],
|
||||
];
|
||||
|
||||
$target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(200, $target['headers']['status-code']);
|
||||
$this->assertEquals('newidentifier', $target['body']['data']['usersUpdateTargetIdentifier']['identifier']);
|
||||
}
|
||||
|
||||
public function testDeleteUserSessions()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -407,6 +529,29 @@ class UsersTest extends Scope
|
||||
$this->getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testDeleteUserTarget(array $target)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_USER_TARGET);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $target['userId'],
|
||||
'targetId' => $target['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$target = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(204, $target['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testDeleteUser()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Messaging;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
|
||||
class MessagingConsoleClientTest extends Scope
|
||||
{
|
||||
use MessagingBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Messaging;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
|
||||
class MessagingCustomClientTest extends Scope
|
||||
{
|
||||
use MessagingBase;
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Messaging;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class MessagingCustomServerTest extends Scope
|
||||
{
|
||||
use MessagingBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
}
|
||||
@@ -794,7 +794,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
public function testUpdateProjectOAuth($data): array
|
||||
{
|
||||
$id = $data['projectId'] ?? '';
|
||||
$providers = require('app/config/providers.php');
|
||||
$providers = require('app/config/authProviders.php');
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
@@ -825,7 +825,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
foreach ($providers as $key => $provider) {
|
||||
$asserted = false;
|
||||
foreach ($response['body']['providers'] as $responseProvider) {
|
||||
foreach ($response['body']['authProviders'] as $responseProvider) {
|
||||
if ($responseProvider['key'] === $key) {
|
||||
$this->assertEquals('AppId-' . ucfirst($key), $responseProvider['appId']);
|
||||
$this->assertEquals('Secret-' . ucfirst($key), $responseProvider['secret']);
|
||||
@@ -867,7 +867,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$i = 0;
|
||||
foreach ($providers as $key => $provider) {
|
||||
$asserted = false;
|
||||
foreach ($response['body']['providers'] as $responseProvider) {
|
||||
foreach ($response['body']['authProviders'] as $responseProvider) {
|
||||
if ($responseProvider['key'] === $key) {
|
||||
// On first provider, test enabled=false
|
||||
$this->assertEquals($i !== 0, $responseProvider['enabled']);
|
||||
|
||||
@@ -1223,6 +1223,94 @@ trait UsersBase
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
public function testCreateUserTarget(array $data): array
|
||||
{
|
||||
$provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'providerId' => 'unique()',
|
||||
'name' => 'Sengrid1',
|
||||
'apiKey' => 'my-apikey'
|
||||
]);
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users/' . $data['userId'] . '/targets', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'targetId' => ID::unique(),
|
||||
'providerId' => $provider['body']['$id'],
|
||||
'identifier' => 'my-token',
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals($provider['body']['$id'], $response['body']['providerId']);
|
||||
$this->assertEquals('my-token', $response['body']['identifier']);
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUserTarget
|
||||
*/
|
||||
public function testUpdateUserTarget(array $data): array
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/targets/' . $data['$id'] . '/identifier', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'identifier' => 'my-updated-token',
|
||||
]);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('my-updated-token', $response['body']['identifier']);
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateUserTarget
|
||||
*/
|
||||
public function testListUserTarget(array $data)
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/targets', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, \count($response['body']['targets']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateUserTarget
|
||||
*/
|
||||
public function testGetUserTarget(array $data)
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/targets/' . $data['$id'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($data['$id'], $response['body']['$id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateUserTarget
|
||||
*/
|
||||
public function testDeleteUserTarget(array $data)
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $data['userId'] . '/targets/' . $data['$id'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/targets', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(0, $response['body']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
|
||||
@@ -154,9 +154,9 @@ class V16Test extends TestCase
|
||||
public function projectProvider(): array
|
||||
{
|
||||
return [
|
||||
'providers' => [
|
||||
'authProviders' => [
|
||||
[
|
||||
'providers' => [
|
||||
'authProviders' => [
|
||||
[
|
||||
'key' => 'github',
|
||||
'name' => 'GitHub',
|
||||
@@ -167,7 +167,7 @@ class V16Test extends TestCase
|
||||
],
|
||||
],
|
||||
[
|
||||
'providers' => [
|
||||
'authProviders' => [
|
||||
[
|
||||
'name' => 'Github',
|
||||
'appId' => 'client_id',
|
||||
|
||||
Reference in New Issue
Block a user