mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
370
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2889f00192 | ||
|
|
3436924c09 | ||
|
|
43d6515f04 | ||
|
|
c2c1163501 | ||
|
|
5709d0e457 | ||
|
|
f60e844ce3 | ||
|
|
f7b3d4dfa8 | ||
|
|
57035f35f8 | ||
|
|
31cc6a4ca3 | ||
|
|
9c4d329ceb | ||
|
|
998642a875 | ||
|
|
d7041c0efd | ||
|
|
ea5de9bde3 | ||
|
|
7abafbb09c | ||
|
|
ea2302238d | ||
|
|
a2df1932cd | ||
|
|
5dab541d76 | ||
|
|
22b1002305 | ||
|
|
bb1357f178 | ||
|
|
14afed5eb2 | ||
|
|
50d6dcc745 | ||
|
|
6459abeea2 | ||
|
|
d822878fe6 | ||
|
|
34d3e4fbfd | ||
|
|
a989b181fd | ||
|
|
c64798d1c3 | ||
|
|
d85cb572b0 | ||
|
|
396ff87fbc | ||
|
|
739cc36fdc | ||
|
|
fa8dc133b4 | ||
|
|
82874a1100 | ||
|
|
43b8fc2c31 | ||
|
|
042c1a725f | ||
|
|
4b58d08fd8 | ||
|
|
82c86c0ae2 | ||
|
|
4bc23afc65 | ||
|
|
ee4c2d0e0d | ||
|
|
eb4083641f | ||
|
|
e3f60f4c78 | ||
|
|
858175fe00 | ||
|
|
d9826cdce8 | ||
|
|
90c47ea241 | ||
|
|
9c6cd8512f | ||
|
|
88f228c106 | ||
|
|
3394218cb0 | ||
|
|
520d19e335 | ||
|
|
0b01f7140f | ||
|
|
8caf61bcd1 | ||
|
|
b1eaf97806 | ||
|
|
5c1cf3a526 | ||
|
|
7eb6b05e05 | ||
|
|
f0247930da | ||
|
|
7536d8eb0f | ||
|
|
bf95736da0 | ||
|
|
14e8234889 | ||
|
|
f854622705 | ||
|
|
893b65ce8f | ||
|
|
23078c43e2 | ||
|
|
c1869bb0ca | ||
|
|
6a52dd6a20 | ||
|
|
6a3fa7d5c5 | ||
|
|
a282ab38a6 | ||
|
|
c728d9bc8e | ||
|
|
549dcc493d | ||
|
|
c24664f5d9 | ||
|
|
71b5e1194f | ||
|
|
2aa8391c5e | ||
|
|
db9a946b0c | ||
|
|
a3c40fcf95 | ||
|
|
fdca55b0de | ||
|
|
adc76c5797 | ||
|
|
1870524be5 | ||
|
|
7ae614fe13 | ||
|
|
c5aaa670a9 | ||
|
|
f84a00dded | ||
|
|
8f53301209 | ||
|
|
e2aa06ed84 | ||
|
|
458c2d1446 | ||
|
|
162fac5724 | ||
|
|
4f681cfe10 | ||
|
|
4c084afa1e | ||
|
|
1b99f47f21 | ||
|
|
3ade88897c | ||
|
|
3b5379a0cb | ||
|
|
76ed15f2a1 | ||
|
|
4106dd5bbd | ||
|
|
3654799cc7 | ||
|
|
4c970cbc46 | ||
|
|
2365626270 | ||
|
|
bd39c5ed2e | ||
|
|
8d5bfdabc6 | ||
|
|
1487897583 | ||
|
|
a9c7b640ec | ||
|
|
b9de4a0169 | ||
|
|
f97d1fa9e3 | ||
|
|
61d46bee5c | ||
|
|
270e6e7c15 | ||
|
|
48744f66c8 | ||
|
|
76d7369314 | ||
|
|
fda4e6a8bd | ||
|
|
2aa5c5742b | ||
|
|
f7529140bd | ||
|
|
a719c3f46b | ||
|
|
a58f473b29 | ||
|
|
a4c8d852a5 | ||
|
|
0dafc59cc2 | ||
|
|
a01d361886 | ||
|
|
06daa544e8 | ||
|
|
26ecad1a6f | ||
|
|
bc6df4bab7 | ||
|
|
5a9b1f0c24 | ||
|
|
92062a5463 | ||
|
|
c2cd544948 | ||
|
|
34c96e4672 | ||
|
|
34b3ed68ee | ||
|
|
ea76bc6680 | ||
|
|
9545e4ac72 | ||
|
|
456654c165 | ||
|
|
f5aa226b57 | ||
|
|
f6f4397aab | ||
|
|
887eeef585 | ||
|
|
fd4a81c4fc | ||
|
|
2435f722ca | ||
|
|
cbae797f38 | ||
|
|
d5b6421285 | ||
|
|
b60cbcada5 | ||
|
|
af40c63357 | ||
|
|
e040ecba36 | ||
|
|
6c260223ba | ||
|
|
161bbfb822 | ||
|
|
8ed37fb445 | ||
|
|
d714760eb8 | ||
|
|
24b2d70441 | ||
|
|
e4d2d15f70 | ||
|
|
db9e7b0199 | ||
|
|
a0f0413984 | ||
|
|
99b5ac8669 | ||
|
|
36d257c847 | ||
|
|
55187d4781 | ||
|
|
4bef86d88b | ||
|
|
b86f531b30 | ||
|
|
8602c44d14 | ||
|
|
ffeb3f8fcf | ||
|
|
4ccffb6649 | ||
|
|
feac25f401 | ||
|
|
0b1b7646d5 | ||
|
|
4a44fb3330 | ||
|
|
736b76cbfa | ||
|
|
cc1769ed41 | ||
|
|
17ba6355e0 | ||
|
|
bb52647366 | ||
|
|
8882318d0d | ||
|
|
1e5a2a78aa | ||
|
|
ecfdab1598 | ||
|
|
a77a212e2c | ||
|
|
a92390dda2 | ||
|
|
d842191cc6 | ||
|
|
76152019d6 | ||
|
|
d5cc214546 | ||
|
|
8e9f270ef0 | ||
|
|
22d445587e | ||
|
|
fb98d21ae5 | ||
|
|
0251703e3a | ||
|
|
80a19f9862 | ||
|
|
abab68f1a1 | ||
|
|
5891872fad | ||
|
|
c9c49158a3 | ||
|
|
61785ab35a | ||
|
|
48b91e39a8 | ||
|
|
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 | ||
|
|
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 |
@@ -87,7 +87,7 @@ _APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
_APP_GRAPHQL_MAX_DEPTH=3
|
||||
_APP_GRAPHQL_MAX_DEPTH=4
|
||||
_APP_DOCKER_HUB_USERNAME=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
_APP_VCS_GITHUB_APP_NAME=
|
||||
@@ -98,4 +98,9 @@ _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=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
|
||||
_APP_MESSAGE_SMS_TEST_DSN=
|
||||
_APP_MESSAGE_EMAIL_TEST_DSN=
|
||||
_APP_MESSAGE_PUSH_TEST_DSN=
|
||||
_APP_CONSOLE_SUPABASE_APP_ID=d64cd75f-2786-4b94-aeda-eae01e165941
|
||||
_APP_CONSOLE_SUPABASE_SECRET=sba_d5a802fbdcdf86d27f7d071a2778bff0419040de
|
||||
|
||||
@@ -99,6 +99,7 @@ jobs:
|
||||
Users,
|
||||
Webhooks,
|
||||
VCS,
|
||||
Messaging,
|
||||
]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -13,3 +13,4 @@ debug/
|
||||
app/sdks
|
||||
dev/yasd_init.php
|
||||
.phpunit.result.cache
|
||||
Makefile
|
||||
|
||||
+1
-1
@@ -466,7 +466,7 @@
|
||||
## Features
|
||||
- Added Phone Authentication by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3357
|
||||
- Added Twilio Support
|
||||
- Added TextMagic Support
|
||||
- Added Textmagic Support
|
||||
- Added Telesign Support
|
||||
- Added Endpoint to create Phone Session (`POST:/v1/account/sessions/phone`)
|
||||
- Added Endpoint to confirm Phone Session (`PUT:/v1/account/sessions/phone`)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+579
-3
@@ -5,7 +5,7 @@ use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
$providers = Config::getParam('providers', []);
|
||||
$providers = Config::getParam('oAuthProviders', []);
|
||||
$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,
|
||||
@@ -670,6 +681,17 @@ $commonCollections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('expire'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1366,7 +1388,561 @@ $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('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' => '',
|
||||
'array' => false,
|
||||
'filters' => ['providerSearch'],
|
||||
],
|
||||
],
|
||||
'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_enabled_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['enabled','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('providerType'),
|
||||
'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('topics'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 21845,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('users'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 21845,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('targets'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 21845,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('scheduledAt'),
|
||||
'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('deliveredTotal'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => ['messageSearch'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$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('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('total'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'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' => '',
|
||||
'array' => false,
|
||||
'filters' => ['topicSearch'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_name'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['name'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_search'),
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['search'],
|
||||
'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('providerType'),
|
||||
'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' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'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' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('name'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'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_UNIQUE,
|
||||
'attributes' => ['identifier'],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$projectCollections = array_merge([
|
||||
'databases' => [
|
||||
@@ -3412,7 +3988,7 @@ $consoleCollections = array_merge([
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('authProviders'),
|
||||
'$id' => ID::custom('oAuthProviders'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
|
||||
@@ -103,6 +103,16 @@ return [
|
||||
'description' => 'This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version.',
|
||||
'code' => 405,
|
||||
],
|
||||
Exception::GENERAL_INVALID_EMAIL => [
|
||||
'name' => Exception::GENERAL_INVALID_EMAIL,
|
||||
'description' => 'Value must be a valid email address.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GENERAL_INVALID_PHONE => [
|
||||
'name' => Exception::GENERAL_INVALID_PHONE,
|
||||
'description' => 'Value must be a valid phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** User Errors */
|
||||
Exception::USER_COUNT_EXCEEDED => [
|
||||
@@ -190,6 +200,16 @@ return [
|
||||
'description' => 'The password you are trying to use contains references to your name, email, phone or userID. For your security, please choose a different password and try again.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_PASSWORD_DICTIONARY => [
|
||||
'name' => Exception::USER_PASSWORD_DICTIONARY,
|
||||
'description' => 'Password must be at least 8 characters and should not be one of the commonly used password.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_PASSWORD_AI => [
|
||||
'name' => Exception::USER_PASSWORD_AI,
|
||||
'description' => 'As per AI, your password is not strong enough. Please choose a different password and try again.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::USER_SESSION_NOT_FOUND => [
|
||||
'name' => Exception::USER_SESSION_NOT_FOUND,
|
||||
'description' => 'The current user session could not be found.',
|
||||
@@ -250,6 +270,16 @@ return [
|
||||
'description' => 'User phone is already verified',
|
||||
'code' => 409
|
||||
],
|
||||
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 => [
|
||||
@@ -764,4 +794,68 @@ return [
|
||||
'description' => 'An error occurred on the provider\'s side. Please try again later.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** 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_MISSING_TARGET => [
|
||||
'name' => Exception::MESSAGE_MISSING_TARGET,
|
||||
'description' => 'Message with the requested ID is missing a target (Topics or Users or Targets).',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_ALREADY_SENT => [
|
||||
'name' => Exception::MESSAGE_ALREADY_SENT,
|
||||
'description' => 'Message with the requested ID has already been sent.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MESSAGE_ALREADY_SCHEDULED => [
|
||||
'name' => Exception::MESSAGE_ALREADY_SCHEDULED,
|
||||
'description' => 'Message with the requested ID has already been scheduled for delivery.',
|
||||
'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,56 @@ 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 topic is created.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a topic is updated.',
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a topic 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,
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
<a href="{{redirect}}" target="_blank">{{redirect}}</a>
|
||||
|
||||
<b>{{code}}</b>
|
||||
|
||||
<i>{{securityPhrase}}</i>
|
||||
|
||||
<p>{{footer}}</p>
|
||||
|
||||
<br>
|
||||
|
||||
@@ -12,9 +12,16 @@
|
||||
"emails.magicSession.subject": "Login",
|
||||
"emails.magicSession.hello": "Hey,",
|
||||
"emails.magicSession.body": "Follow this link to login.",
|
||||
"emails.magicSession.codeBody": "Enter this code to login.",
|
||||
"emails.magicSession.footer": "If you didn’t ask to login using this email, you can ignore this message.",
|
||||
"emails.magicSession.thanks": "Thanks",
|
||||
"emails.magicSession.signature": "{{project}} team",
|
||||
"emails.authNotify.subject": "New session alert for {{project}}",
|
||||
"emails.authNotify.hello": "Hey {{user}},",
|
||||
"emails.authNotify.body": "We're writing to inform you that a new session has been initiated on your {{project}} account on {{date}} at {{time}}. <br><hr><br>Here are the details: <br>Device: {{device}}<br>Location: {{country}}<br>IP Address: {{ip}} <br><hr><br>If you did not initiate this session, we strongly recommend that you review your account settings.",
|
||||
"emails.authNotify.footer": "If you recognize this activity, no further action is required.",
|
||||
"emails.authNotify.thanks": "Thanks",
|
||||
"emails.authNotify.signature": "{{project}} team",
|
||||
"emails.recovery.subject": "Password Reset",
|
||||
"emails.recovery.hello": "Hello {{user}}",
|
||||
"emails.recovery.body": "Follow this link to reset your {{project}} password.",
|
||||
|
||||
@@ -152,6 +152,16 @@ return [
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'supabase' => [
|
||||
'name' => 'Supabase',
|
||||
'developers' => 'https://supabase.com/docs',
|
||||
'icon' => 'icon-supabase',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => false,
|
||||
],
|
||||
'gitlab' => [
|
||||
'name' => 'GitLab',
|
||||
'developers' => 'https://docs.gitlab.com/ee/api/',
|
||||
@@ -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' => false,
|
||||
'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',
|
||||
]
|
||||
];
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -440,7 +440,7 @@ return [
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_SMS_PROVIDER',
|
||||
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, text-magic, telesign, msg91, and vonage.",
|
||||
'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'.\n\nEnsure `[USER]` and `[SECRET]` are URL encoded if they contain any non-alphanumeric characters.\n\nAvailable providers are twilio, Textmagic, telesign, msg91, and vonage.",
|
||||
'introduction' => '0.15.0',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
|
||||
+1
-1
Submodule app/console updated: 49d039ed07...b31040bff6
+655
-141
File diff suppressed because it is too large
Load Diff
@@ -84,8 +84,8 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
$accessTokenExpiry = $gitHubSession->getAttribute('providerAccessTokenExpiry');
|
||||
$refreshToken = $gitHubSession->getAttribute('providerRefreshToken');
|
||||
|
||||
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
|
||||
|
||||
|
||||
@@ -1571,7 +1571,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new DatetimeValidator(), 'Default value for the attribute in ISO 8601 format. Cannot be set when attribute is required.', true)
|
||||
->param('default', null, new DatetimeValidator(), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -85,7 +85,7 @@ App::post('/v1/projects')
|
||||
}
|
||||
|
||||
$auth = Config::getParam('auth', []);
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false];
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordAi' => false, 'renewal' => false, 'passwordDictionary' => false, 'passwordDictionaryLength' => '10k', 'notify' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false];
|
||||
foreach ($auth as $index => $method) {
|
||||
$auths[$method['key'] ?? ''] = true;
|
||||
}
|
||||
@@ -159,7 +159,7 @@ App::post('/v1/projects')
|
||||
'legalTaxId' => ID::custom($legalTaxId),
|
||||
'services' => new stdClass(),
|
||||
'platforms' => null,
|
||||
'authProviders' => [],
|
||||
'oAuthProviders' => [],
|
||||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'auths' => $auths,
|
||||
@@ -598,7 +598,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('oAuthProviders')), 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)
|
||||
@@ -612,7 +612,7 @@ App::patch('/v1/projects/:projectId/oauth2')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$providers = $project->getAttribute('authProviders', []);
|
||||
$providers = $project->getAttribute('oAuthProviders', []);
|
||||
|
||||
if ($appId !== null) {
|
||||
$providers[$provider . 'Appid'] = $appId;
|
||||
@@ -626,7 +626,7 @@ App::patch('/v1/projects/:projectId/oauth2')
|
||||
$providers[$provider . 'Enabled'] = $enabled;
|
||||
}
|
||||
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('authProviders', $providers));
|
||||
$project = $dbForConsole->updateDocument('projects', $project->getId(), $project->setAttribute('oAuthProviders', $providers));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
@@ -674,9 +674,10 @@ App::patch('/v1/projects/:projectId/auth/duration')
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('duration', 31536000, new Range(0, 31536000), 'Project session length in seconds. Max length: 31536000 seconds.')
|
||||
->param('renewal', false, new Boolean(), 'Automatic session refresh. If enabled, sessions are automatically extended to session duration on every request.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, int $duration, Response $response, Database $dbForConsole) {
|
||||
->action(function (string $projectId, int $duration, bool $renewal, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
@@ -686,6 +687,7 @@ App::patch('/v1/projects/:projectId/auth/duration')
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['duration'] = $duration;
|
||||
$auths['renewal'] = $renewal;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
@@ -759,7 +761,7 @@ App::patch('/v1/projects/:projectId/auth/password-history')
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dicitonary check for user password')
|
||||
->desc('Update authentication password dictionary status. Use this endpoint to enable or disable the dictionary check for user password')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
@@ -770,6 +772,39 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.')
|
||||
->param('length', '10k', new WhiteList(['10k', '100k', '1m', '10m'], true), 'Set the length of the password dictionary to use', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, string $length, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['passwordDictionary'] = $enabled;
|
||||
$auths['passwordDictionaryLength'] = $length;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/password-ai')
|
||||
->desc('Update authentication password ai status.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateAuthPasswordAi')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against AI opinion. Default is false.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
|
||||
@@ -781,7 +816,40 @@ App::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['passwordDictionary'] = $enabled;
|
||||
$auths['passwordAi'] = $enabled;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/notify')
|
||||
->desc('Update authentication notify status. Enable or disable notifications for new session creations.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateAuthNotify')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('enabled', false, new Boolean(false), 'Set whether or not to enable notifications for new session creations. Default is false.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
|
||||
|
||||
if (!empty($projectId)) {
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['notify'] = $enabled;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
@@ -1151,7 +1219,7 @@ App::post('/v1/projects/:projectId/keys')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true)
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
|
||||
@@ -1268,7 +1336,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->param('keyId', '', new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in ISO 8601 format. Use null for unlimited expiration.', true)
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForConsole) {
|
||||
|
||||
@@ -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;
|
||||
@@ -388,7 +388,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForEvents')
|
||||
->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 $queueForMails, EventPhone $queueForMessaging, Event $queueForEvents) {
|
||||
->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 $queueForMails, Messaging $queueForMessaging, Event $queueForEvents) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
@@ -628,6 +628,10 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->trigger()
|
||||
;
|
||||
} elseif (!empty($phone)) {
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
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] ?? [];
|
||||
@@ -638,9 +642,18 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
$message = $message->setParam('{{token}}', $url);
|
||||
$message = $message->render();
|
||||
|
||||
$messageDoc = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'data' => [
|
||||
'content' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($message)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType('SMS')
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Password;
|
||||
use Appwrite\Auth\Validator\PasswordAi;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
@@ -9,6 +10,7 @@ use Appwrite\Event\Event;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Identities;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Targets;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
@@ -70,6 +72,13 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
}
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['passwordAi'] ?? false) {
|
||||
$passwordAiValidator = new PasswordAi();
|
||||
if (!$passwordAiValidator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_AI);
|
||||
}
|
||||
}
|
||||
|
||||
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
|
||||
$user = $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
@@ -98,6 +107,42 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $phone, $name]),
|
||||
]));
|
||||
|
||||
if ($email) {
|
||||
try {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerType' => 'email',
|
||||
'identifier' => $email,
|
||||
]));
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
|
||||
} catch (Duplicate) {
|
||||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($phone) {
|
||||
try {
|
||||
$target = $dbForProject->createDocument('targets', new Document([
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'providerType' => 'sms',
|
||||
'identifier' => $phone,
|
||||
]));
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $target]);
|
||||
} catch (Duplicate) {
|
||||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$phone]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -125,13 +170,35 @@ App::post('/v1/users')
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', null, new Email(), 'User email.', true)
|
||||
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
|
||||
->param('password', '', fn ($project, $passwordsDictionary10k) => new PasswordDictionary($passwordsDictionary10k, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary10k'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
|
||||
->inject('passwordsDictionary10k')
|
||||
->inject('passwordsDictionary100k')
|
||||
->inject('passwordsDictionary1M')
|
||||
->inject('passwordsDictionary10M')
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, array $passwordsDictionary10k, array $passwordsDictionary100k, array $passwordsDictionary1M, array $passwordsDictionary10M) {
|
||||
if ($project->getAttribute('auths', [])['passwordDictionary'] ?? false) {
|
||||
$passwordDictionaryLength = $project->getAttribute('auths', [])['passwordDictionaryLength'] ?? '10k';
|
||||
if ($passwordDictionaryLength == '10k') {
|
||||
$passwordDictionary = $passwordsDictionary10k;
|
||||
} elseif ($passwordDictionaryLength == '100k') {
|
||||
$passwordDictionary = $passwordsDictionary100k;
|
||||
} elseif ($passwordDictionaryLength == '1m') {
|
||||
$passwordDictionary = $passwordsDictionary1M;
|
||||
} elseif ($passwordDictionaryLength == '10m') {
|
||||
$passwordDictionary = $passwordsDictionary10M;
|
||||
} else {
|
||||
throw new Exception('Password dictionary length is not supported');
|
||||
}
|
||||
$passwordDictionaryValidator = new PasswordDictionary($passwordDictionary, true);
|
||||
if (!$passwordDictionaryValidator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_DICTIONARY);
|
||||
}
|
||||
}
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents);
|
||||
|
||||
$response
|
||||
@@ -379,6 +446,98 @@ 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', 'target.create')
|
||||
->label('audits.resource', 'target/response.$id')
|
||||
->label('event', 'users.[userId].targets.[targetId].create')
|
||||
->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('targetId', '', new CustomId(), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
|
||||
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
|
||||
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, string $userId, string $providerType, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) {
|
||||
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
|
||||
|
||||
$provider = new Document();
|
||||
|
||||
if ($providerType === MESSAGE_TYPE_PUSH) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($providerType) {
|
||||
case 'email':
|
||||
$validator = new Email();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_SMS:
|
||||
$validator = new Phone();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_PHONE);
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_PUSH:
|
||||
break;
|
||||
default:
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
$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 ?? null,
|
||||
'providerInternalId' => $provider->getInternalId() ?? null,
|
||||
'providerType' => $providerType,
|
||||
'userId' => $userId,
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'identifier' => $identifier,
|
||||
'name' => ($name !== '') ? $name : null,
|
||||
]));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('targetId', $target->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::get('/v1/users')
|
||||
->desc('List users')
|
||||
->groups(['api', 'users'])
|
||||
@@ -482,6 +641,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'])
|
||||
@@ -646,6 +837,53 @@ 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.')
|
||||
->param('queries', [], new Targets(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Users::ALLOWED_ATTRIBUTES), true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, array $queries, Response $response, Database $dbForProject) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
$queries[] = Query::equal('userId', [$userId]);
|
||||
|
||||
// Get cursor document if there was a cursor query
|
||||
$cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]);
|
||||
$cursor = reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
$targetId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('targets', $targetId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Target '{$targetId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'targets' => $dbForProject->find('targets', $queries),
|
||||
'total' => $dbForProject->count('targets', $queries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_TARGET_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/users/identities')
|
||||
->desc('List Identities')
|
||||
->groups(['api', 'users'])
|
||||
@@ -861,12 +1099,16 @@ App::patch('/v1/users/:userId/password')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('password', '', fn ($project, $passwordsDictionary10k) => new PasswordDictionary($passwordsDictionary10k, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary10k'])
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $queueForEvents) {
|
||||
->inject('passwordsDictionary10k')
|
||||
->inject('passwordsDictionary100k')
|
||||
->inject('passwordsDictionary1M')
|
||||
->inject('passwordsDictionary10M')
|
||||
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, array $passwordsDictionary10k, array $passwordsDictionary100k, array $passwordsDictionary1M, array $passwordsDictionary10M) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -880,6 +1122,32 @@ App::patch('/v1/users/:userId/password')
|
||||
throw new Exception(Exception::USER_PASSWORD_PERSONAL_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['passwordAi'] ?? false) {
|
||||
$passwordAiValidator = new PasswordAi();
|
||||
if (!$passwordAiValidator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_AI);
|
||||
}
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['passwordDictionary'] ?? false) {
|
||||
$passwordDictionaryLength = $project->getAttribute('auths', [])['passwordDictionaryLength'] ?? '10k';
|
||||
if ($passwordDictionaryLength == '10k') {
|
||||
$passwordDictionary = $passwordsDictionary10k;
|
||||
} elseif ($passwordDictionaryLength == '100k') {
|
||||
$passwordDictionary = $passwordsDictionary100k;
|
||||
} elseif ($passwordDictionaryLength == '1m') {
|
||||
$passwordDictionary = $passwordsDictionary1M;
|
||||
} elseif ($passwordDictionaryLength == '10m') {
|
||||
$passwordDictionary = $passwordsDictionary10M;
|
||||
} else {
|
||||
throw new Exception('Password dictionary length is not supported');
|
||||
}
|
||||
$passwordDictionaryValidator = new PasswordDictionary($passwordDictionary, true);
|
||||
if (!$passwordDictionaryValidator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_DICTIONARY);
|
||||
}
|
||||
}
|
||||
|
||||
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
@@ -949,6 +1217,16 @@ App::patch('/v1/users/:userId/email')
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$target = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
|
||||
if ($target instanceof Document && !$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$oldEmail = $user->getAttribute('email');
|
||||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false)
|
||||
@@ -957,6 +1235,15 @@ App::patch('/v1/users/:userId/email')
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
/**
|
||||
* @var Document $oldTarget
|
||||
*/
|
||||
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email));
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -994,13 +1281,32 @@ App::patch('/v1/users/:userId/phone')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$oldPhone = $user->getAttribute('phone');
|
||||
|
||||
$user
|
||||
->setAttribute('phone', $number)
|
||||
->setAttribute('phoneVerification', false)
|
||||
;
|
||||
|
||||
$target = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$number]),
|
||||
]);
|
||||
|
||||
if ($target instanceof Document && !$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
/**
|
||||
* @var Document $oldTarget
|
||||
*/
|
||||
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $number));
|
||||
}
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -1080,6 +1386,96 @@ App::patch('/v1/users/:userId/prefs')
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::patch('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Update User target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.update')
|
||||
->label('audits.resource', 'target/{response.$id}')
|
||||
->label('event', 'users.[userId].targets.[targetId].update')
|
||||
->label('scope', 'targets.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'users')
|
||||
->label('sdk.method', 'updateTarget')
|
||||
->label('sdk.description', '/docs/references/users/update-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.')
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', true)
|
||||
->param('providerId', '', new UID(), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true)
|
||||
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, string $targetId, string $identifier, string $providerId, string $name, Event $queueForEvents, Response $response, Database $dbForProject) {
|
||||
$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);
|
||||
}
|
||||
|
||||
if ($identifier) {
|
||||
$providerType = $target->getAttribute('providerType');
|
||||
|
||||
switch ($providerType) {
|
||||
case 'email':
|
||||
$validator = new Email();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_SMS:
|
||||
$validator = new Phone();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_PHONE);
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_PUSH:
|
||||
break;
|
||||
default:
|
||||
throw new Exception(Exception::PROVIDER_INCORRECT_TYPE);
|
||||
}
|
||||
|
||||
$target->setAttribute('identifier', $identifier);
|
||||
}
|
||||
|
||||
if ($providerId) {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
throw new Exception(Exception::PROVIDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target->setAttribute('providerId', $provider->getId());
|
||||
$target->setAttribute('providerInternalId', $provider->getInternalId());
|
||||
}
|
||||
|
||||
if ($name) {
|
||||
$target->setAttribute('name', $name);
|
||||
}
|
||||
|
||||
$target = $dbForProject->updateDocument('targets', $target->getId(), $target);
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('targetId', $target->getId());
|
||||
|
||||
$response
|
||||
->dynamic($target, Response::MODEL_TARGET);
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
->desc('Delete user session')
|
||||
->groups(['api', 'users'])
|
||||
@@ -1210,6 +1606,53 @@ App::delete('/v1/users/:userId')
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/:userId/targets/:targetId')
|
||||
->desc('Delete user target')
|
||||
->groups(['api', 'users'])
|
||||
->label('audits.event', 'target.delete')
|
||||
->label('audits.resource', 'target/{request.$targetId}')
|
||||
->label('event', 'users.[userId].targets.[targetId].delete')
|
||||
->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('queueForEvents')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, string $targetId, Event $queueForEvents, Response $response, Database $dbForProject) {
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('targets', $target->getId());
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('targetId', $target->getId());
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::delete('/v1/users/identities/:identityId')
|
||||
->desc('Delete Identity')
|
||||
->groups(['api', 'users'])
|
||||
@@ -1251,7 +1694,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('oAuthProviders', [])))), true), 'Provider Name.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('register')
|
||||
|
||||
@@ -154,8 +154,7 @@ App::init()
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $limit)
|
||||
->addHeader('X-RateLimit-Remaining', $remaining)
|
||||
->addHeader('X-RateLimit-Reset', $time)
|
||||
;
|
||||
->addHeader('X-RateLimit-Reset', $time);
|
||||
}
|
||||
|
||||
$enabled = App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
|
||||
@@ -170,6 +169,27 @@ App::init()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Session refresh
|
||||
*/
|
||||
if ($project->getAttribute('auths', [])['renewal'] ?? false) {
|
||||
if ($user && !$user->isEmpty()) {
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$currentSessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret, $authDuration);
|
||||
|
||||
if ($currentSessionId) {
|
||||
$currentSession = $dbForProject->getDocument('sessions', $currentSessionId);
|
||||
if (!$currentSession->isEmpty()) {
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $authDuration));
|
||||
$currentSession = $currentSession->setAttribute('expire', $expire);
|
||||
|
||||
$dbForProject->updateDocument('sessions', $currentSession->getId(), $currentSession);
|
||||
$dbForProject->deleteCachedDocument('users', $user->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
@@ -240,7 +260,7 @@ App::init()
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn() => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
@@ -252,8 +272,7 @@ App::init()
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT')
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->setContentType($data['contentType'])
|
||||
->send(base64_decode($data['payload']))
|
||||
;
|
||||
->send(base64_decode($data['payload']));
|
||||
} else {
|
||||
$response->addHeader('X-Appwrite-Cache', 'miss');
|
||||
}
|
||||
@@ -495,7 +514,7 @@ App::shutdown()
|
||||
'resource' => $resource,
|
||||
'contentType' => $response->getContentType(),
|
||||
'payload' => base64_encode($data['payload']),
|
||||
]) ;
|
||||
]);
|
||||
|
||||
$signature = md5($data);
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
@@ -503,10 +522,10 @@ App::shutdown()
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'accessedAt' => $now,
|
||||
'signature' => $signature,
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'accessedAt' => $now,
|
||||
'signature' => $signature,
|
||||
])));
|
||||
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
|
||||
$cacheLog->setAttribute('accessedAt', $now);
|
||||
|
||||
+158
-28
@@ -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;
|
||||
@@ -47,13 +47,7 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
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\Registry\Registry;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
@@ -83,6 +77,7 @@ use Utopia\Validator\Range;
|
||||
use Utopia\Validator\IP;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\CLI\Console;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
@@ -103,6 +98,7 @@ const APP_LIMIT_COMPRESSION = 20000000; //20MB
|
||||
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
|
||||
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
|
||||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1000000;
|
||||
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
|
||||
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
@@ -173,11 +169,14 @@ 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_TOPIC = 'topic';
|
||||
// Compression type
|
||||
const COMPRESSION_TYPE_NONE = 'none';
|
||||
const COMPRESSION_TYPE_GZIP = 'gzip';
|
||||
const COMPRESSION_TYPE_ZSTD = 'zstd';
|
||||
// Mail Types
|
||||
const MAIL_TYPE_VERIFICATION_URL = 'verificationUrl';
|
||||
const MAIL_TYPE_VERIFICATION_CODE = 'verificationCode';
|
||||
const MAIL_TYPE_VERIFICATION = 'verification';
|
||||
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
|
||||
const MAIL_TYPE_RECOVERY = 'recovery';
|
||||
@@ -193,6 +192,10 @@ const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
|
||||
// Function headers
|
||||
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
|
||||
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
|
||||
// Message types
|
||||
const MESSAGE_TYPE_EMAIL = 'email';
|
||||
const MESSAGE_TYPE_SMS = 'sms';
|
||||
const MESSAGE_TYPE_PUSH = 'push';
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
@@ -237,7 +240,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('oAuthProviders', __DIR__ . '/config/oAuthProviders.php');
|
||||
Config::load('platforms', __DIR__ . '/config/platforms.php');
|
||||
Config::load('collections', __DIR__ . '/config/collections.php');
|
||||
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
|
||||
@@ -526,6 +529,107 @@ 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()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
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()]),
|
||||
Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY)
|
||||
])
|
||||
));
|
||||
if (\count($targetIds) > 0) {
|
||||
return $database->find('targets', [Query::equal('$id', $targetIds)]);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'providerSearch',
|
||||
function (mixed $value, Document $provider) {
|
||||
$searchValues = [
|
||||
$provider->getId(),
|
||||
$provider->getAttribute('name', ''),
|
||||
$provider->getAttribute('provider', ''),
|
||||
$provider->getAttribute('type', '')
|
||||
];
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'topicSearch',
|
||||
function (mixed $value, Document $topic) {
|
||||
$searchValues = [
|
||||
$topic->getId(),
|
||||
$topic->getAttribute('name', ''),
|
||||
$topic->getAttribute('description', ''),
|
||||
];
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
Database::addFilter(
|
||||
'messageSearch',
|
||||
function (mixed $value, Document $message) {
|
||||
$searchValues = [
|
||||
$message->getId(),
|
||||
$message->getAttribute('description', ''),
|
||||
$message->getAttribute('status', ''),
|
||||
];
|
||||
|
||||
$data = \json_decode($message->getAttribute('data', []), true);
|
||||
$providerType = $message->getAttribute('providerType', '');
|
||||
|
||||
if ($providerType === MESSAGE_TYPE_EMAIL) {
|
||||
$searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]);
|
||||
} elseif ($providerType === MESSAGE_TYPE_SMS) {
|
||||
$searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]);
|
||||
} else {
|
||||
$searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]);
|
||||
}
|
||||
|
||||
$search = \implode(' ', \array_filter($searchValues));
|
||||
|
||||
return $search;
|
||||
},
|
||||
function (mixed $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* DB Formats
|
||||
*/
|
||||
@@ -823,12 +927,34 @@ $register->set('smtp', function () {
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2023-01.mmdb');
|
||||
});
|
||||
$register->set('passwordsDictionary', function () {
|
||||
$register->set('passwordsDictionary10k', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
|
||||
$content = explode("\n", $content);
|
||||
$content = array_flip($content);
|
||||
return $content;
|
||||
});
|
||||
|
||||
$register->set('passwordsDictionary100k', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/assets/security/100k-common-passwords.txt');
|
||||
$content = explode("\n", $content);
|
||||
$content = array_flip($content);
|
||||
return $content;
|
||||
});
|
||||
|
||||
$register->set('passwordsDictionary1M', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/assets/security/1m-common-passwords.txt');
|
||||
$content = explode("\n", $content);
|
||||
$content = array_flip($content);
|
||||
return $content;
|
||||
});
|
||||
|
||||
$register->set('passwordsDictionary10M', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/assets/security/1m-common-passwords.txt');
|
||||
$content = explode("\n", $content);
|
||||
$content = array_flip($content);
|
||||
return $content;
|
||||
});
|
||||
|
||||
$register->set('promiseAdapter', function () {
|
||||
return new Swoole();
|
||||
});
|
||||
@@ -887,7 +1013,7 @@ App::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
}, ['pools']);
|
||||
App::setResource('queueForMessaging', function (Connection $queue) {
|
||||
return new Phone($queue);
|
||||
return new Messaging($queue);
|
||||
}, ['queue']);
|
||||
App::setResource('queueForMails', function (Connection $queue) {
|
||||
return new Mail($queue);
|
||||
@@ -1100,13 +1226,17 @@ App::setResource('console', function () {
|
||||
'invites' => App::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
|
||||
'limit' => (App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
|
||||
'notify' => true,
|
||||
],
|
||||
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
|
||||
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
|
||||
'authProviders' => [
|
||||
'oAuthProviders' => [
|
||||
'githubEnabled' => true,
|
||||
'githubSecret' => App::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
|
||||
'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
|
||||
'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', ''),
|
||||
'supabaseEnabled' => true,
|
||||
'supabaseSecret' => App::getEnv('_APP_CONSOLE_SUPABASE_SECRET', ''),
|
||||
'supabaseAppid' => App::getEnv('_APP_CONSOLE_SUPABASE_APP_ID', ''),
|
||||
],
|
||||
]);
|
||||
}, []);
|
||||
@@ -1322,26 +1452,26 @@ App::setResource('geodb', function ($register) {
|
||||
return $register->get('geodb');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('passwordsDictionary', function ($register) {
|
||||
App::setResource('passwordsDictionary10k', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('passwordsDictionary');
|
||||
return $register->get('passwordsDictionary10k');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('sms', function () {
|
||||
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$user = $dsn->getUser();
|
||||
$secret = $dsn->getPassword();
|
||||
|
||||
return 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
|
||||
};
|
||||
});
|
||||
App::setResource('passwordsDictionary100k', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('passwordsDictionary100k');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('passwordsDictionary1M', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('passwordsDictionary1M');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('passwordsDictionary10M', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('passwordsDictionary10M');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('servers', function () {
|
||||
$platforms = Config::getParam('platforms');
|
||||
|
||||
+1
-2
@@ -12,7 +12,6 @@ use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Swoole\Runtime;
|
||||
@@ -129,7 +128,7 @@ Server::setResource('queueForDatabase', function (Connection $queue) {
|
||||
return new EventDatabase($queue);
|
||||
}, ['queue']);
|
||||
Server::setResource('queueForMessaging', function (Connection $queue) {
|
||||
return new Phone($queue);
|
||||
return new Messaging($queue);
|
||||
}, ['queue']);
|
||||
Server::setResource('queueForMails', function (Connection $queue) {
|
||||
return new Mail($queue);
|
||||
|
||||
Generated
+39
-38
@@ -156,11 +156,11 @@
|
||||
},
|
||||
{
|
||||
"name": "appwrite/php-runtimes",
|
||||
"version": "0.13.1",
|
||||
"version": "0.13.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/runtimes.git",
|
||||
"reference": "b584d19cdcd82737d0ee5c34d23de791f5ed3610"
|
||||
"reference": "214a37c2c66e0f2bc9c30fdfde66955d9fd084a1"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
@@ -195,7 +195,7 @@
|
||||
"php",
|
||||
"runtimes"
|
||||
],
|
||||
"time": "2023-10-16T15:39:53+00:00"
|
||||
"time": "2023-11-22T15:36:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "chillerlan/php-qrcode",
|
||||
@@ -1465,7 +1465,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.3.0",
|
||||
"version": "v3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
@@ -1512,7 +1512,7 @@
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2217,16 +2217,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/logger",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/logger.git",
|
||||
"reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc"
|
||||
"reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/logger/zipball/de623f1ec1c672c795d113dd25c5bf212f7ef4fc",
|
||||
"reference": "de623f1ec1c672c795d113dd25c5bf212f7ef4fc",
|
||||
"url": "https://api.github.com/repos/utopia-php/logger/zipball/ba763c10688fe2ed715ad2bed3f13d18dfec6253",
|
||||
"reference": "ba763c10688fe2ed715ad2bed3f13d18dfec6253",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2264,9 +2264,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/logger/issues",
|
||||
"source": "https://github.com/utopia-php/logger/tree/0.3.1"
|
||||
"source": "https://github.com/utopia-php/logger/tree/0.3.2"
|
||||
},
|
||||
"time": "2023-02-10T15:52:50+00:00"
|
||||
"time": "2023-11-22T14:45:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/messaging",
|
||||
@@ -3136,16 +3136,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "0.35.2",
|
||||
"version": "0.35.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b"
|
||||
"reference": "4c431d5324a8f8cd2cab9a5515c170a5b427d44c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/2dfe0430a64ffd2a07078d83b20144b871acac3b",
|
||||
"reference": "2dfe0430a64ffd2a07078d83b20144b871acac3b",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/4c431d5324a8f8cd2cab9a5515c170a5b427d44c",
|
||||
"reference": "4c431d5324a8f8cd2cab9a5515c170a5b427d44c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3181,9 +3181,9 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.35.2"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.35.3"
|
||||
},
|
||||
"time": "2023-09-14T14:59:50+00:00"
|
||||
"time": "2023-11-12T05:56:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
@@ -3890,16 +3890,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpdoc-parser",
|
||||
"version": "1.24.2",
|
||||
"version": "1.24.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||
"reference": "bcad8d995980440892759db0c32acae7c8e79442"
|
||||
"reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442",
|
||||
"reference": "bcad8d995980440892759db0c32acae7c8e79442",
|
||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6bd0c26f3786cd9b7c359675cb789e35a8e07496",
|
||||
"reference": "6bd0c26f3786cd9b7c359675cb789e35a8e07496",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3931,9 +3931,9 @@
|
||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2"
|
||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.4"
|
||||
},
|
||||
"time": "2023-09-26T12:28:12+00:00"
|
||||
"time": "2023-11-26T18:29:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -5676,16 +5676,16 @@
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/theseer/tokenizer.git",
|
||||
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
|
||||
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
|
||||
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
|
||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
|
||||
"reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5714,7 +5714,7 @@
|
||||
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
|
||||
"support": {
|
||||
"issues": "https://github.com/theseer/tokenizer/issues",
|
||||
"source": "https://github.com/theseer/tokenizer/tree/1.2.1"
|
||||
"source": "https://github.com/theseer/tokenizer/tree/1.2.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5722,30 +5722,31 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-07-28T10:34:58+00:00"
|
||||
"time": "2023-11-20T00:12:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.7.1",
|
||||
"version": "v3.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554"
|
||||
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
|
||||
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
|
||||
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-ctype": "^1.8",
|
||||
"symfony/polyfill-mbstring": "^1.3"
|
||||
"symfony/polyfill-mbstring": "^1.3",
|
||||
"symfony/polyfill-php80": "^1.22"
|
||||
},
|
||||
"require-dev": {
|
||||
"psr/container": "^1.0|^2.0",
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.3"
|
||||
"symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -5781,7 +5782,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.7.1"
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.8.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5793,7 +5794,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-08-28T11:09:02+00:00"
|
||||
"time": "2023-11-21T18:54:41+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
@@ -5822,5 +5823,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.0"
|
||||
},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
||||
+13
-3
@@ -188,7 +188,11 @@ services:
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
- _APP_ASSISTANT_OPENAI_API_KEY
|
||||
|
||||
- _APP_MESSAGE_SMS_TEST_DSN
|
||||
- _APP_MESSAGE_EMAIL_TEST_DSN
|
||||
- _APP_MESSAGE_PUSH_TEST_DSN
|
||||
- _APP_CONSOLE_SUPABASE_SECRET
|
||||
- _APP_CONSOLE_SUPABASE_APP_ID
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
<<: *x-logging
|
||||
@@ -568,14 +572,20 @@ services:
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_SMS_PROVIDER
|
||||
- _APP_SMS_FROM
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_SMS_FROM
|
||||
- _APP_SMS_PROVIDER
|
||||
|
||||
appwrite-worker-migrations:
|
||||
entrypoint: worker-migrations
|
||||
|
||||
@@ -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/oAuthProviders.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 `oAuthProviders.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/oAuthProviders.php`.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<directory>./tests/e2e/Services/Projects</directory>
|
||||
<directory>./tests/e2e/Services/Storage</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>
|
||||
|
||||
@@ -363,7 +363,7 @@ class Auth
|
||||
$session->isSet('secret') &&
|
||||
$session->isSet('provider') &&
|
||||
$session->getAttribute('secret') === self::hash($secret) &&
|
||||
DateTime::formatTz(DateTime::addSeconds(new \DateTime($session->getCreatedAt()), $expires)) >= DateTime::formatTz(DateTime::now())
|
||||
DateTime::formatTz($session->getAttribute('expire')) >= DateTime::formatTz(DateTime::now())
|
||||
) {
|
||||
return $session->getId();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
use Appwrite\Auth\OAuth2;
|
||||
|
||||
// Reference Material
|
||||
// https://supabase.com/docs/guides/platform/oauth-apps/build-a-supabase-integration
|
||||
|
||||
class Supabase extends OAuth2
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $endpoint = 'https://api.supabase.com/v1';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $user = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $tokens = [];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'supabase';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLoginURL(): string
|
||||
{
|
||||
$url = $this->endpoint . '/oauth/authorize?' .
|
||||
\http_build_query([
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->appID,
|
||||
'state' => \json_encode($this->state),
|
||||
'redirect_uri' => $this->callback
|
||||
]);
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTokens(string $code): array
|
||||
{
|
||||
if (empty($this->tokens)) {
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/oauth/token',
|
||||
['Content-Type: application/x-www-form-urlencoded'],
|
||||
\http_build_query([
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $this->callback,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
])
|
||||
), true);
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $refreshToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function refreshTokens(string $refreshToken): array
|
||||
{
|
||||
$this->tokens = \json_decode($this->request(
|
||||
'POST',
|
||||
$this->endpoint . '/oauth/token',
|
||||
['Content-Type: application/x-www-form-urlencoded'],
|
||||
\http_build_query([
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
])
|
||||
), true);
|
||||
|
||||
if (empty($this->tokens['refresh_token'])) {
|
||||
$this->tokens['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserID(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['id'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserEmail(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['email'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the OAuth email is verified
|
||||
*
|
||||
* @link https://discord.com/developers/docs/resources/user
|
||||
*
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
if ($user['verified'] ?? false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUserName(string $accessToken): string
|
||||
{
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['username'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$user = $this->request(
|
||||
'GET',
|
||||
$this->endpoint . '/organizations',
|
||||
['Authorization: Bearer ' . \urlencode($accessToken)]
|
||||
);
|
||||
$this->user = \json_decode($user, true);
|
||||
$this->user = [
|
||||
'username' => $this->user[0]['name'],
|
||||
'verified' => false,
|
||||
'email' => $this->user[0]['name'] . '@supabase.internal',
|
||||
'id' => $this->user[0]['id']
|
||||
];
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
/**
|
||||
* Password.
|
||||
*
|
||||
* Validates user password string
|
||||
*/
|
||||
class PasswordAi extends Password
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Password must be at least 8 characters and must be approved by AI.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!parent::isValid($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$prompt = 'My password is "' . $value . '". Check my password and tell me if it could include day, year, month, date, name, surname, animal name, location, city, animal breed, or state in any shape or form. Answer "Yes" if it does, and "no" if it doesnt. Only answer with 1 word.';
|
||||
|
||||
$answer = '';
|
||||
|
||||
$ch = curl_init('http://appwrite-assistant:3003/');
|
||||
$responseHeaders = [];
|
||||
$query = json_encode(['prompt' => $prompt]);
|
||||
$headers = ['accept: text/event-stream'];
|
||||
$handleEvent = function ($ch, $data) use ($answer) {
|
||||
$answer .= $data;
|
||||
return \strlen($data);
|
||||
};
|
||||
|
||||
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent);
|
||||
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 9000);
|
||||
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
|
||||
$len = strlen($header);
|
||||
$header = explode(':', $header, 2);
|
||||
|
||||
if (count($header) < 2) { // ignore invalid headers
|
||||
return $len;
|
||||
}
|
||||
|
||||
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
|
||||
|
||||
return $len;
|
||||
});
|
||||
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
|
||||
|
||||
curl_exec($ch);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if ($answer !== 'Yes') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\Queue\Client;
|
||||
|
||||
class Messaging extends Event
|
||||
{
|
||||
protected ?string $messageId = null;
|
||||
protected ?Document $message = null;
|
||||
protected ?array $recipients = null;
|
||||
protected ?string $scheduledAt = null;
|
||||
protected ?string $providerType = null;
|
||||
|
||||
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
parent::__construct($connection);
|
||||
|
||||
$this
|
||||
->setQueue(Event::MESSAGING_QUEUE_NAME)
|
||||
->setClass(Event::MESSAGING_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the messaging event.
|
||||
*
|
||||
* @param string[] $recipients
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipients(array $recipients): self
|
||||
{
|
||||
$this->recipients = $recipients;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for messaging event.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRecipient(): array
|
||||
{
|
||||
return $this->recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets message document for the messaging event.
|
||||
*
|
||||
* @param Document $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessage(Document $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns message document for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage(): Document
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 provider type for the messaging event.
|
||||
*
|
||||
* @param string $providerType
|
||||
* @return self
|
||||
*/
|
||||
public function setProviderType(string $providerType): self
|
||||
{
|
||||
$this->providerType = $providerType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set provider type for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProviderType(): string
|
||||
{
|
||||
return $this->providerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Scheduled delivery time for the messaging event.
|
||||
*
|
||||
* @param string $scheduledAt
|
||||
* @return self
|
||||
*/
|
||||
public function setScheduledAt(string $scheduledAt): self
|
||||
{
|
||||
$this->scheduledAt = $scheduledAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set Delivery Time for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getScheduledAt(): string
|
||||
{
|
||||
return $this->scheduledAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
return $client->enqueue([
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'messageId' => $this->messageId,
|
||||
'message' => $this->message,
|
||||
'recipients' => $this->recipients,
|
||||
'providerType' => $this->providerType,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
|
||||
class Phone extends Event
|
||||
{
|
||||
protected string $recipient = '';
|
||||
protected string $message = '';
|
||||
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
parent::__construct($connection);
|
||||
|
||||
$this
|
||||
->setQueue(Event::MESSAGING_QUEUE_NAME)
|
||||
->setClass(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
|
||||
{
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
return $client->enqueue([
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'payload' => $this->payload,
|
||||
'recipient' => $this->recipient,
|
||||
'message' => $this->message,
|
||||
'events' => Event::generateEvents($this->getEvent(), $this->getParams())
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,8 @@ class Exception extends \Exception
|
||||
public const GENERAL_CODES_DISABLED = 'general_codes_disabled';
|
||||
public const GENERAL_USAGE_DISABLED = 'general_usage_disabled';
|
||||
public const GENERAL_NOT_IMPLEMENTED = 'general_not_implemented';
|
||||
public const GENERAL_INVALID_EMAIL = 'general_invalid_email';
|
||||
public const GENERAL_INVALID_PHONE = 'general_invalid_phone';
|
||||
|
||||
/** Users */
|
||||
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
|
||||
@@ -72,6 +74,8 @@ class Exception extends \Exception
|
||||
public const USER_NOT_FOUND = 'user_not_found';
|
||||
public const USER_PASSWORD_RECENTLY_USED = 'password_recently_used';
|
||||
public const USER_PASSWORD_PERSONAL_DATA = 'password_personal_data';
|
||||
public const USER_PASSWORD_DICTIONARY = 'password_dictionary';
|
||||
public const USER_PASSWORD_AI = 'password_ai';
|
||||
public const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
|
||||
public const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
|
||||
public const USER_SESSION_NOT_FOUND = 'user_session_not_found';
|
||||
@@ -86,6 +90,8 @@ class Exception extends \Exception
|
||||
public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error';
|
||||
public const USER_EMAIL_ALREADY_VERIFIED = 'user_email_alread_verified';
|
||||
public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified';
|
||||
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';
|
||||
@@ -233,6 +239,26 @@ class Exception extends \Exception
|
||||
public const REALTIME_TOO_MANY_MESSAGES = 'realtime_too_many_messages';
|
||||
public const REALTIME_POLICY_VIOLATION = 'realtime_policy_violation';
|
||||
|
||||
/** 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';
|
||||
public const PROVIDER_INTERNAL_UPDATE_DISABLED = 'provider_internal_update_disabled';
|
||||
|
||||
/** 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_MISSING_TARGET = 'message_missing_target';
|
||||
public const MESSAGE_ALREADY_SENT = 'message_already_sent';
|
||||
public const MESSAGE_ALREADY_SCHEDULED = 'message_already_scheduled';
|
||||
|
||||
protected string $type = '';
|
||||
protected array $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('oAuthProviders', []))
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -124,23 +124,23 @@ class V16 extends Migration
|
||||
/**
|
||||
* Enable OAuth providers with data
|
||||
*/
|
||||
$authProviders = $document->getAttribute('authProviders', []);
|
||||
$oAuthProviders = $document->getAttribute('oAuthProviders', []);
|
||||
|
||||
foreach (Config::getParam('providers') as $provider => $value) {
|
||||
foreach (Config::getParam('oAuthProviders') as $provider => $value) {
|
||||
if (!$value['enabled']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($authProviders[$provider . 'Appid'] ?? false) && ($authProviders[$provider . 'Secret'] ?? false)) {
|
||||
if (array_key_exists($provider . 'Enabled', $authProviders)) {
|
||||
if (($oAuthProviders[$provider . 'Appid'] ?? false) && ($oAuthProviders[$provider . 'Secret'] ?? false)) {
|
||||
if (array_key_exists($provider . 'Enabled', $oAuthProviders)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$authProviders[$provider . 'Enabled'] = true;
|
||||
$oAuthProviders[$provider . 'Enabled'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$document->setAttribute('authProviders', $authProviders);
|
||||
$document->setAttribute('oAuthProviders', $oAuthProviders);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Executor\Executor;
|
||||
use Throwable;
|
||||
use Utopia\Abuse\Abuse;
|
||||
@@ -149,6 +150,9 @@ class Deletes extends Action
|
||||
case DELETE_TYPE_SCHEDULES:
|
||||
$this->deleteSchedules($dbForConsole, $getProjectDB, $datetime);
|
||||
break;
|
||||
case DELETE_TYPE_TOPIC:
|
||||
$this->deleteTopic($project, $getProjectDB, $document);
|
||||
break;
|
||||
default:
|
||||
Console::error('No delete operation for type: ' . $type);
|
||||
break;
|
||||
@@ -193,6 +197,25 @@ class Deletes extends Action
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
* @param Document $topic
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function deleteTopic(Document $project, callable $getProjectDB, Document $topic)
|
||||
{
|
||||
if ($topic->isEmpty()) {
|
||||
Console::error('Failed to delete subscribers. Topic not found');
|
||||
return;
|
||||
}
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$this->deleteByGroup('subscribers', [
|
||||
Query::equal('topicInternalId', [$topic->getInternalId()])
|
||||
], $dbForProject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
|
||||
@@ -2,30 +2,41 @@
|
||||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Exception;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Messaging\Messages\Sms;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
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\Textmagic;
|
||||
use Utopia\Messaging\Adapters\SMS\Twilio;
|
||||
use Utopia\Messaging\Adapters\SMS\Vonage;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
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 function Swoole\Coroutine\batch;
|
||||
|
||||
class Messaging extends Action
|
||||
{
|
||||
private ?DSN $dsn = null;
|
||||
private string $user = '';
|
||||
private string $secret = '';
|
||||
private string $provider = '';
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'messaging';
|
||||
return "messaging";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,25 +44,20 @@ class Messaging extends Action
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->provider = App::getEnv('_APP_SMS_PROVIDER', '');
|
||||
if (!empty($this->provider)) {
|
||||
$this->dsn = new DSN($this->provider);
|
||||
$this->user = $this->dsn->getUser();
|
||||
$this->secret = $this->dsn->getPassword();
|
||||
}
|
||||
|
||||
$this
|
||||
->desc('Messaging worker')
|
||||
->inject('message')
|
||||
->callback(fn($message) => $this->action($message));
|
||||
->inject('dbForProject')
|
||||
->callback(fn(Message $message, Database $dbForProject) => $this->action($message, $dbForProject));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param Database $dbForProject
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function action(Message $message): void
|
||||
public function action(Message $message, Database $dbForProject): void
|
||||
{
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
@@ -60,48 +66,316 @@ class Messaging extends Action
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($payload['recipient'])) {
|
||||
Console::error('Recipient arg not found');
|
||||
if (!\is_null($payload['message']) && !\is_null($payload['recipients'])) {
|
||||
if ($payload['providerType'] === MESSAGE_TYPE_SMS) {
|
||||
$this->processInternalSMSMessage(new Document($payload['message']), $payload['recipients']);
|
||||
}
|
||||
} else {
|
||||
$message = $dbForProject->getDocument('messages', $payload['messageId']);
|
||||
|
||||
$this->processMessage($dbForProject, $message);
|
||||
}
|
||||
}
|
||||
|
||||
private function processMessage(Database $dbForProject, Document $message): void
|
||||
{
|
||||
$topicsId = $message->getAttribute('topics', []);
|
||||
$targetsId = $message->getAttribute('targets', []);
|
||||
$usersId = $message->getAttribute('users', []);
|
||||
|
||||
/**
|
||||
* @var Document[] $recipients
|
||||
*/
|
||||
$recipients = [];
|
||||
|
||||
if (\count($topicsId) > 0) {
|
||||
$topics = $dbForProject->find('topics', [Query::equal('$id', $topicsId)]);
|
||||
foreach ($topics as $topic) {
|
||||
$recipients = \array_merge($recipients, $topic->getAttribute('targets'));
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($usersId) > 0) {
|
||||
$users = $dbForProject->find('users', [Query::equal('$id', $usersId)]);
|
||||
foreach ($users as $user) {
|
||||
$recipients = \array_merge($recipients, $user->getAttribute('targets'));
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($targetsId) > 0) {
|
||||
$targets = $dbForProject->find('targets', [Query::equal('$id', $targetsId)]);
|
||||
$recipients = \array_merge($recipients, $targets);
|
||||
}
|
||||
|
||||
$primaryProvider = $dbForProject->findOne('providers', [
|
||||
Query::equal('enabled', [true]),
|
||||
Query::equal('type', [$recipients[0]->getAttribute('providerType')]),
|
||||
]);
|
||||
|
||||
/**
|
||||
* @var array<string, array<string>> $identifiersByProviderId
|
||||
*/
|
||||
$identifiersByProviderId = [];
|
||||
|
||||
/**
|
||||
* @var Document[] $providers
|
||||
*/
|
||||
$providers = [];
|
||||
foreach ($recipients as $recipient) {
|
||||
$providerId = $recipient->getAttribute('providerId');
|
||||
|
||||
if (!$providerId && $primaryProvider instanceof Document && !$primaryProvider->isEmpty()) {
|
||||
$providerId = $primaryProvider->getId();
|
||||
}
|
||||
|
||||
if ($providerId) {
|
||||
if (!isset($identifiersByProviderId[$providerId])) {
|
||||
$identifiersByProviderId[$providerId] = [];
|
||||
}
|
||||
$identifiersByProviderId[$providerId][] = $recipient->getAttribute('identifier');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array[] $results
|
||||
*/
|
||||
$results = batch(\array_map(function ($providerId) use ($identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
|
||||
return function () use ($providerId, $identifiersByProviderId, $providers, $primaryProvider, $message, $dbForProject) {
|
||||
$provider = new Document();
|
||||
|
||||
if ($primaryProvider->getId() === $providerId) {
|
||||
$provider = $primaryProvider;
|
||||
} else {
|
||||
$provider = $dbForProject->getDocument('providers', $providerId, [Query::equal('enabled', [true])]);
|
||||
|
||||
if ($provider->isEmpty()) {
|
||||
$provider = $primaryProvider;
|
||||
}
|
||||
}
|
||||
|
||||
$providers[] = $provider;
|
||||
$identifiers = $identifiersByProviderId[$providerId];
|
||||
|
||||
$adapter = match ($provider->getAttribute('type')) {
|
||||
MESSAGE_TYPE_SMS => $this->sms($provider),
|
||||
MESSAGE_TYPE_PUSH => $this->push($provider),
|
||||
MESSAGE_TYPE_EMAIL => $this->email($provider),
|
||||
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
|
||||
};
|
||||
|
||||
$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) {
|
||||
$deliveredTotal = 0;
|
||||
$deliveryErrors = [];
|
||||
$messageData = clone $message;
|
||||
$messageData->setAttribute('to', $batch);
|
||||
|
||||
$data = match ($provider->getAttribute('type')) {
|
||||
MESSAGE_TYPE_SMS => $this->buildSMSMessage($messageData, $provider),
|
||||
MESSAGE_TYPE_PUSH => $this->buildPushMessage($messageData),
|
||||
MESSAGE_TYPE_EMAIL => $this->buildEmailMessage($messageData, $provider),
|
||||
default => throw new Exception(Exception::PROVIDER_INCORRECT_TYPE)
|
||||
};
|
||||
|
||||
try {
|
||||
$adapter->send($data);
|
||||
$deliveredTotal += \count($batch);
|
||||
} catch (\Exception $e) {
|
||||
$deliveryErrors[] = 'Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage();
|
||||
} finally {
|
||||
$batchIndex++;
|
||||
return [
|
||||
'deliveredTotal' => $deliveredTotal,
|
||||
'deliveryErrors' => $deliveryErrors,
|
||||
];
|
||||
}
|
||||
};
|
||||
}, $batches));
|
||||
|
||||
return $results;
|
||||
};
|
||||
}, \array_keys($identifiersByProviderId)));
|
||||
|
||||
$results = array_merge(...$results);
|
||||
|
||||
$deliveredTotal = 0;
|
||||
$deliveryErrors = [];
|
||||
|
||||
foreach ($results as $result) {
|
||||
$deliveredTotal += $result['deliveredTotal'];
|
||||
$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->removeAttribute('to');
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
$message->setAttribute('search', "{$message->getAttribute('search')} {$provider->getAttribute('name')} {$provider->getAttribute('provider')} {$provider->getAttribute('type')}");
|
||||
}
|
||||
|
||||
$message->setAttribute('deliveredTotal', $deliveredTotal);
|
||||
$message->setAttribute('deliveredAt', DateTime::now());
|
||||
|
||||
$dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
}
|
||||
|
||||
private function processInternalSMSMessage(Document $message, array $recipients): void
|
||||
{
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER')) || empty(App::getEnv('_APP_SMS_FROM'))) {
|
||||
Console::info('Skipped SMS processing. No Phone configuration has been set.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($payload['message'])) {
|
||||
Console::error('Message arg not found');
|
||||
return;
|
||||
}
|
||||
|
||||
$sms = match ($this->dsn->getHost()) {
|
||||
'mock' => new Mock($this->user, $this->secret), // used for tests
|
||||
'twilio' => new Twilio($this->user, $this->secret),
|
||||
'text-magic' => new TextMagic($this->user, $this->secret),
|
||||
'telesign' => new Telesign($this->user, $this->secret),
|
||||
'msg91' => new Msg91($this->user, $this->secret),
|
||||
'vonage' => new Vonage($this->user, $this->secret),
|
||||
default => null
|
||||
};
|
||||
|
||||
if (empty(App::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
Console::error('Skipped sms processing. No Phone provider has been set.');
|
||||
return;
|
||||
}
|
||||
$smsDSN = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$host = $smsDSN->getHost();
|
||||
$password = $smsDSN->getPassword();
|
||||
$user = $smsDSN->getUser();
|
||||
|
||||
$from = App::getEnv('_APP_SMS_FROM');
|
||||
|
||||
if (empty($from)) {
|
||||
Console::error('Skipped sms processing. No phone number has been set.');
|
||||
return;
|
||||
}
|
||||
$provider = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'provider' => $host,
|
||||
'type' => MESSAGE_TYPE_SMS,
|
||||
'name' => 'Internal SMS',
|
||||
'enabled' => true,
|
||||
'credentials' => match ($host) {
|
||||
'twilio' => [
|
||||
'accountSid' => $user,
|
||||
'authToken' => $password
|
||||
],
|
||||
'textmagic' => [
|
||||
'username' => $user,
|
||||
'apiKey' => $password
|
||||
],
|
||||
'telesign' => [
|
||||
'username' => $user,
|
||||
'password' => $password
|
||||
],
|
||||
'msg91' => [
|
||||
'senderId' => $user,
|
||||
'authKey' => $password
|
||||
],
|
||||
'vonage' => [
|
||||
'apiKey' => $user,
|
||||
'apiSecret' => $password
|
||||
],
|
||||
default => null
|
||||
},
|
||||
'options' => [
|
||||
'from' => $from
|
||||
]
|
||||
]);
|
||||
|
||||
$message = new SMS(
|
||||
to: [$payload['recipient']],
|
||||
content: $payload['message'],
|
||||
from: $from,
|
||||
);
|
||||
$adapter = $this->sms($provider);
|
||||
|
||||
try {
|
||||
$sms->send($message);
|
||||
} catch (\Exception $error) {
|
||||
throw new Exception('Error sending message: ' . $error->getMessage(), 500);
|
||||
}
|
||||
$maxBatchSize = $adapter->getMaxMessagesPerRequest();
|
||||
$batches = \array_chunk($recipients, $maxBatchSize);
|
||||
$batchIndex = 0;
|
||||
|
||||
batch(\array_map(function ($batch) use ($message, $provider, $adapter, $batchIndex) {
|
||||
return function () use ($batch, $message, $provider, $adapter, $batchIndex) {
|
||||
$message->setAttribute('to', $batch);
|
||||
|
||||
$data = $this->buildSMSMessage($message, $provider);
|
||||
|
||||
try {
|
||||
$adapter->send($data);
|
||||
} catch (\Exception $e) {
|
||||
Console::error('Failed sending to targets ' . $batchIndex + 1 . '-' . \count($batch) . ' with error: ' . $e->getMessage());
|
||||
}
|
||||
};
|
||||
}, $batches));
|
||||
}
|
||||
|
||||
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']),
|
||||
'textmagic' => 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,28 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Messages extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'topics',
|
||||
'users',
|
||||
'targets',
|
||||
'providerId',
|
||||
'deliveredAt',
|
||||
'deliveredTo',
|
||||
'deliveryErrors',
|
||||
'status',
|
||||
'description',
|
||||
'data'
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('messages', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Providers extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'name',
|
||||
'provider',
|
||||
'type',
|
||||
'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,21 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Targets extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'userId',
|
||||
'providerId',
|
||||
'identifier',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('targets', 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);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ class Users extends Base
|
||||
'email',
|
||||
'phone',
|
||||
'status',
|
||||
'hash',
|
||||
'passwordUpdate',
|
||||
'registration',
|
||||
'emailVerification',
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -60,8 +60,8 @@ class V13 extends Filter
|
||||
|
||||
protected function parseProject($content)
|
||||
{
|
||||
$content['providers'] = $content['authProviders'];
|
||||
unset($content['authProviders']);
|
||||
$content['providers'] = $content['oAuthProviders'];
|
||||
unset($content['oAuthProviders']);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
@@ -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['oAuthProviders'] ?? [] as $i => $provider) {
|
||||
$content['oAuthProviders'][$i]['name'] = \ucfirst($provider['key']);
|
||||
unset($content['oAuthProviders'][$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,131 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document as DatabaseDocument;
|
||||
|
||||
class Message extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Message ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Message creation time in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Message update date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('providerType', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Message provider type.',
|
||||
'default' => '',
|
||||
'example' => MESSAGE_TYPE_EMAIL,
|
||||
])
|
||||
->addRule('topics', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Topic IDs set as recipients.',
|
||||
'default' => '',
|
||||
'array' => true,
|
||||
'example' => ['5e5ea5c16897e'],
|
||||
])
|
||||
->addRule('users', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User IDs set as recipients.',
|
||||
'default' => '',
|
||||
'array' => true,
|
||||
'example' => ['5e5ea5c16897e'],
|
||||
])
|
||||
->addRule('targets', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Target IDs set as recipients.',
|
||||
'default' => '',
|
||||
'array' => true,
|
||||
'example' => ['5e5ea5c16897e'],
|
||||
])
|
||||
->addRule('scheduledAt', [
|
||||
'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('deliveredTotal', [
|
||||
'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;
|
||||
}
|
||||
}
|
||||
@@ -132,15 +132,51 @@ class Project extends Model
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authPasswordDictionaryLength', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'How many most commonly used password to check against. Possible values are: 10k, 100k, 1m, 10m',
|
||||
'default' => '10k',
|
||||
'example' => '1m',
|
||||
])
|
||||
->addRule('authPasswordAi', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to check user\'s password against against AI opinion',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authNotify', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to send notification on new session creation.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authSessionRefresh', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not sessions are automatically extended to session duration on every request',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authPasswordAi', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to check user\'s password against against AI opinion',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authRenewal', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not sessions are automatically extended to session duration on every request',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authPersonalDataCheck', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to check the user password for similarity with their personal data.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('providers', [
|
||||
'type' => Response::MODEL_PROVIDER,
|
||||
'description' => 'List of Providers.',
|
||||
->addRule('oAuthProviders', [
|
||||
'type' => Response::MODEL_AUTH_PROVIDER,
|
||||
'description' => 'List of Auth Providers.',
|
||||
'default' => [],
|
||||
'example' => [new \stdClass()],
|
||||
'array' => true,
|
||||
@@ -220,8 +256,7 @@ class Project extends Model
|
||||
'description' => 'SMTP server secure protocol',
|
||||
'default' => '',
|
||||
'example' => 'tls',
|
||||
])
|
||||
;
|
||||
]);
|
||||
|
||||
$services = Config::getParam('services', []);
|
||||
$auth = Config::getParam('auth', []);
|
||||
@@ -236,8 +271,7 @@ class Project extends Model
|
||||
'description' => $name . ' auth method status',
|
||||
'example' => true,
|
||||
'default' => true,
|
||||
])
|
||||
;
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($services as $service) {
|
||||
@@ -254,8 +288,7 @@ class Project extends Model
|
||||
'description' => $name . ' service status',
|
||||
'example' => true,
|
||||
'default' => true,
|
||||
])
|
||||
;
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +353,10 @@ class Project extends Model
|
||||
$document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT);
|
||||
$document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0);
|
||||
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
|
||||
$document->setAttribute('authPasswordDictionaryLength', $authValues['passwordDictionaryLength'] ?? false);
|
||||
$document->setAttribute('authPasswordAi', $authValues['passwordAi'] ?? false);
|
||||
$document->setAttribute('authNotify', $authValues['notify'] ?? false);
|
||||
$document->setAttribute('authRenewal', $authValues['renewal'] ?? false);
|
||||
$document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false);
|
||||
|
||||
foreach ($auth as $index => $method) {
|
||||
@@ -328,9 +365,9 @@ class Project extends Model
|
||||
$document->setAttribute('auth' . ucfirst($key), $value);
|
||||
}
|
||||
|
||||
// Providers
|
||||
$providers = Config::getParam('providers', []);
|
||||
$providerValues = $document->getAttribute('authProviders', []);
|
||||
// OAuth Providers
|
||||
$providers = Config::getParam('oAuthProviders', []);
|
||||
$providerValues = $document->getAttribute('oAuthProviders', []);
|
||||
$projectProviders = [];
|
||||
|
||||
foreach ($providers as $key => $provider) {
|
||||
@@ -348,7 +385,7 @@ class Project extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
$document->setAttribute("providers", $projectProviders);
|
||||
$document->setAttribute('oAuthProviders', $projectProviders);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
@@ -7,44 +7,68 @@ 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('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Provider creation time in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Provider update date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->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',
|
||||
])
|
||||
->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',
|
||||
'example' => 'mailgun',
|
||||
])
|
||||
->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' => MESSAGE_TYPE_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,85 @@
|
||||
<?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('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Subscriber creation time in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Subscriber update date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('targetId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Target ID.',
|
||||
'default' => '',
|
||||
'example' => '259125845563242502',
|
||||
])
|
||||
->addRule('target', [
|
||||
'type' => Response::MODEL_TARGET,
|
||||
'description' => 'Target.',
|
||||
'default' => [],
|
||||
'example' => [
|
||||
'$id' => '259125845563242502',
|
||||
'$createdAt' => self::TYPE_DATETIME_EXAMPLE,
|
||||
'$updatedAt' => self::TYPE_DATETIME_EXAMPLE,
|
||||
'providerType' => 'email',
|
||||
'providerId' => '259125845563242502',
|
||||
'name' => 'ageon-app-email',
|
||||
'identifier' => 'random-mail@email.org',
|
||||
'userId' => '5e5ea5c16897e',
|
||||
],
|
||||
])
|
||||
->addRule('userName', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User Name.',
|
||||
'default' => '',
|
||||
'example' => 'Aegon Targaryen',
|
||||
])
|
||||
->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,83 @@
|
||||
<?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('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Target creation time in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Target update date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Target Name.',
|
||||
'default' => '',
|
||||
'example' => 'Aegon apple token',
|
||||
])
|
||||
->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('providerType', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The target provider type. Can be one of the following: `email`, `sms` or `push`.',
|
||||
'default' => '',
|
||||
'example' => 'email',
|
||||
])
|
||||
->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;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,12 @@ class Token extends Model
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('securityPhrase', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Token security phrase.',
|
||||
'default' => '',
|
||||
'example' => 'Tiger Whisper',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Topic creation time in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Topic update date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The name of the topic.',
|
||||
'default' => '',
|
||||
'example' => 'events',
|
||||
])
|
||||
->addRule('total', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Total count of subscribers subscribed to topic.',
|
||||
'default' => 0,
|
||||
'example' => 100,
|
||||
])
|
||||
->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.',
|
||||
|
||||
@@ -82,7 +82,17 @@ trait ProjectCustom
|
||||
'avatars.read',
|
||||
'health.read',
|
||||
'rules.read',
|
||||
'rules.write'
|
||||
'rules.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'providers.read',
|
||||
'providers.write',
|
||||
'messages.read',
|
||||
'messages.write',
|
||||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
@@ -415,7 +415,7 @@ trait AccountBase
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'queries' => [ 'limit(1)' ],
|
||||
'queries' => ['limit(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($responseLimit['headers']['status-code'], 200);
|
||||
@@ -432,7 +432,7 @@ trait AccountBase
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'queries' => [ 'offset(1)' ],
|
||||
'queries' => ['offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($responseOffset['headers']['status-code'], 200);
|
||||
@@ -449,7 +449,7 @@ trait AccountBase
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'queries' => [ 'limit(1)', 'offset(1)' ],
|
||||
'queries' => ['limit(1)', 'offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($responseLimitOffset['headers']['status-code'], 200);
|
||||
@@ -1115,6 +1115,14 @@ trait AccountBase
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 204);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account/sessions', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
$this->assertEquals($response['headers']['status-code'], 401);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
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;
|
||||
use Utopia\DSN\DSN;
|
||||
|
||||
use function sleep;
|
||||
|
||||
@@ -764,6 +764,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
|
||||
|
||||
@@ -6,7 +6,9 @@ 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;
|
||||
use Utopia\DSN\DSN;
|
||||
|
||||
class AccountTest extends Scope
|
||||
{
|
||||
@@ -123,6 +125,7 @@ class AccountTest extends Scope
|
||||
public function testCreatePhoneVerification(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_PHONE_VERIFICATION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
|
||||
@@ -109,6 +109,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';
|
||||
@@ -199,6 +204,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';
|
||||
|
||||
@@ -881,6 +933,55 @@ trait Base
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_USER_TARGET:
|
||||
return 'mutation createUserTarget($userId: String!, $targetId: String!, $providerType: String!, $identifier: String! $providerId: String){
|
||||
usersCreateTarget(userId: $userId, targetId: $targetId, providerType: $providerType, identifier: $identifier, providerId: $providerId) {
|
||||
_id
|
||||
userId
|
||||
providerType
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}';
|
||||
case self::$LIST_USER_TARGETS:
|
||||
return 'query listUserTargets($userId: String!) {
|
||||
usersListTargets(userId: $userId) {
|
||||
total
|
||||
targets {
|
||||
_id
|
||||
userId
|
||||
providerType
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_USER_TARGET:
|
||||
return 'query getUserTarget($userId: String!, $targetId: String!) {
|
||||
usersGetTarget(userId: $userId, targetId: $targetId) {
|
||||
_id
|
||||
userId
|
||||
providerType
|
||||
providerId
|
||||
identifier
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_USER_TARGET:
|
||||
return 'mutation updateUserTarget($userId: String!, $targetId: String!, $providerId: String, $identifier: String){
|
||||
usersUpdateTarget(userId: $userId, targetId: $targetId, providerId: $providerId, identifier: $identifier) {
|
||||
_id
|
||||
userId
|
||||
providerType
|
||||
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 {
|
||||
@@ -1688,6 +1789,439 @@ trait Base
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_MAILGUN_PROVIDER:
|
||||
return 'mutation createMailgunProvider($providerId: String!, $name: String!, $domain: String!, $apiKey: String!, $from: String!, $isEuRegion: Boolean!) {
|
||||
messagingCreateMailgunProvider(providerId: $providerId, name: $name, domain: $domain, apiKey: $apiKey, from: $from, isEuRegion: $isEuRegion) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_SENDGRID_PROVIDER:
|
||||
return 'mutation createSendgridProvider($providerId: String!, $name: String!, $from: String!, $apiKey: String!) {
|
||||
messagingCreateSendgridProvider(providerId: $providerId, name: $name, from: $from, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TWILIO_PROVIDER:
|
||||
return 'mutation createTwilioProvider($providerId: String!, $name: String!, $from: String!, $accountSid: String!, $authToken: String!) {
|
||||
messagingCreateTwilioProvider(providerId: $providerId, name: $name, from: $from, accountSid: $accountSid, authToken: $authToken) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TELESIGN_PROVIDER:
|
||||
return 'mutation createTelesignProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $password: String!) {
|
||||
messagingCreateTelesignProvider(providerId: $providerId, name: $name, from: $from, username: $username, password: $password) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TEXTMAGIC_PROVIDER:
|
||||
return 'mutation createTextmagicProvider($providerId: String!, $name: String!, $from: String!, $username: String!, $apiKey: String!) {
|
||||
messagingCreateTextmagicProvider(providerId: $providerId, name: $name, from: $from, username: $username, apiKey: $apiKey) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_MSG91_PROVIDER:
|
||||
return 'mutation createMsg91Provider($providerId: String!, $name: String!, $from: String!, $senderId: String!, $authKey: String!, $enabled: Boolean) {
|
||||
messagingCreateMsg91Provider(providerId: $providerId, name: $name, from: $from, senderId: $senderId, authKey: $authKey, enabled: $enabled) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_VONAGE_PROVIDER:
|
||||
return 'mutation createVonageProvider($providerId: String!, $name: String!, $from: String!, $apiKey: String!, $apiSecret: String!) {
|
||||
messagingCreateVonageProvider(providerId: $providerId, name: $name, from: $from, apiKey: $apiKey, apiSecret: $apiSecret) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
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
|
||||
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
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$LIST_PROVIDERS:
|
||||
return 'query listProviders {
|
||||
messagingListProviders {
|
||||
total
|
||||
providers {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
|
||||
enabled
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_PROVIDER:
|
||||
return 'query getProvider($providerId: String!) {
|
||||
messagingGetProvider(providerId: $providerId) {
|
||||
_id
|
||||
name
|
||||
provider
|
||||
type
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
enabled
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_PROVIDER:
|
||||
return 'mutation deleteProvider($providerId: String!) {
|
||||
messagingDeleteProvider(providerId: $providerId) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_TOPIC:
|
||||
return 'mutation createTopic($topicId: String!, $name: String!, $description: String!) {
|
||||
messagingCreateTopic(topicId: $topicId, name: $name, description: $description) {
|
||||
_id
|
||||
name
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$LIST_TOPICS:
|
||||
return 'query listTopics {
|
||||
messagingListTopics {
|
||||
total
|
||||
topics {
|
||||
_id
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_TOPIC:
|
||||
return 'query getTopic($topicId: String!) {
|
||||
messagingGetTopic(topicId: $topicId) {
|
||||
_id
|
||||
name
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TOPIC:
|
||||
return 'mutation updateTopic($topicId: String!, $name: String!, $description: String!) {
|
||||
messagingUpdateTopic(topicId: $topicId, name: $name, description: $description) {
|
||||
_id
|
||||
name
|
||||
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
|
||||
userName
|
||||
target {
|
||||
_id
|
||||
userId
|
||||
name
|
||||
providerType
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$LIST_SUBSCRIBERS:
|
||||
return 'query listSubscribers($topicId: String!) {
|
||||
messagingListSubscribers(topicId: $topicId) {
|
||||
total
|
||||
subscribers {
|
||||
_id
|
||||
targetId
|
||||
topicId
|
||||
userName
|
||||
target {
|
||||
_id
|
||||
userId
|
||||
name
|
||||
providerType
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_SUBSCRIBER:
|
||||
return 'query getSubscriber($topicId: String!, $subscriberId: String!) {
|
||||
messagingGetSubscriber(topicId: $topicId, subscriberId: $subscriberId) {
|
||||
_id
|
||||
targetId
|
||||
topicId
|
||||
userName
|
||||
target {
|
||||
_id
|
||||
userId
|
||||
name
|
||||
providerType
|
||||
identifier
|
||||
}
|
||||
}
|
||||
}';
|
||||
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!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String!, $content: String!, $status: String, $description: String, $html: Boolean, $scheduledAt: String) {
|
||||
messagingCreateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_SMS:
|
||||
return 'mutation createSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String!, $status: String, $description: String, $scheduledAt: String) {
|
||||
messagingCreateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_PUSH_NOTIFICATION:
|
||||
return 'mutation createPushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String!, $body: String!, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) {
|
||||
messagingCreatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$LIST_MESSAGES:
|
||||
return 'query listMessages {
|
||||
messagingListMessages {
|
||||
total
|
||||
messages {
|
||||
_id
|
||||
providerType
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}
|
||||
}';
|
||||
case self::$GET_MESSAGE:
|
||||
return 'query getMessage($messageId: String!) {
|
||||
messagingGetMessage(messageId: $messageId) {
|
||||
_id
|
||||
providerType
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_EMAIL:
|
||||
return 'mutation updateEmail($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $subject: String, $content: String, $status: String, $description: String, $html: Boolean, $scheduledAt: String) {
|
||||
messagingUpdateEmail(messageId: $messageId, topics: $topics, users: $users, targets: $targets, subject: $subject, content: $content, status: $status, description: $description, html: $html, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_SMS:
|
||||
return 'mutation updateSMS($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $content: String, $status: String, $description: String, $scheduledAt: String) {
|
||||
messagingUpdateSMS(messageId: $messageId, topics: $topics, users: $users, targets: $targets, content: $content, status: $status, description: $description, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
status
|
||||
description
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_PUSH_NOTIFICATION:
|
||||
return 'mutation updatePushNotification($messageId: String!, $topics: [String!], $users: [String!], $targets: [String!], $title: String, $body: String, $data: Json, $action: String, $icon: String, $sound: String, $color: String, $tag: String, $badge: String, $status: String, $description: String, $scheduledAt: String) {
|
||||
messagingUpdatePushNotification(messageId: $messageId, topics: $topics, users: $users, targets: $targets, title: $title, body: $body, data: $data, action: $action, icon: $icon, sound: $sound, color: $color, tag: $tag, badge: $badge, status: $status, description: $description, scheduledAt: $scheduledAt) {
|
||||
_id
|
||||
topics
|
||||
users
|
||||
targets
|
||||
scheduledAt
|
||||
deliveredAt
|
||||
deliveryErrors
|
||||
deliveredTotal
|
||||
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) {
|
||||
@@ -1940,7 +2474,7 @@ trait Base
|
||||
protected string $stdout = '';
|
||||
protected string $stderr = '';
|
||||
|
||||
protected function packageCode($folder)
|
||||
protected function packageCode($folder): void
|
||||
{
|
||||
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,56 @@ 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.com',
|
||||
'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'],
|
||||
'providerType' => 'email',
|
||||
'providerId' => $providerId,
|
||||
'identifier' => 'random-email@mail.org',
|
||||
]
|
||||
];
|
||||
|
||||
$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('random-email@mail.org', $target['body']['data']['usersCreateTarget']['identifier']);
|
||||
|
||||
return $target['body']['data']['usersCreateTarget'];
|
||||
}
|
||||
|
||||
public function testGetUsers()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -176,6 +226,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(2, $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('random-email@mail.org', $target['body']['data']['usersGetTarget']['identifier']);
|
||||
}
|
||||
|
||||
public function testUpdateUserStatus()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -360,6 +458,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' => 'random-email1@mail.org',
|
||||
],
|
||||
];
|
||||
|
||||
$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('random-email1@mail.org', $target['body']['data']['usersUpdateTarget']['identifier']);
|
||||
}
|
||||
|
||||
public function testDeleteUserSessions()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -407,6 +530,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,413 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Messaging;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
class MessagingConsoleClientTest extends Scope
|
||||
{
|
||||
use MessagingBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
|
||||
/**
|
||||
* @depends testListProviders
|
||||
*/
|
||||
public function testGetProviderLogs(array $providers): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $providers[0]['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$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' => ID::unique(),
|
||||
'name' => 'Sengrid1',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => 'sender-email@my-domain.com',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $provider['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/messaging/providers/sendgrid/' . $provider['body']['$id'], \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'sendgrid' => [
|
||||
'name' => 'Sendgrid2',
|
||||
]]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
$this->assertCount(2, $logs['body']['logs']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)', 'offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['equal("$id", "asdf")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['orderAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $provider['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['cursorAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testListTopic
|
||||
*/
|
||||
public function testGetTopicLogs(string $topicId): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topicId . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'topicId' => ID::unique(),
|
||||
'name' => 'my-app',
|
||||
'description' => 'web app'
|
||||
]);
|
||||
$this->assertEquals(201, $topic['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/messaging/topics/' . $topic['body']['$id'], \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'description' => 'updated-description'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertCount(2, $logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)', 'offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['equal("$id", "asdf")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['orderAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topic['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['cursorAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testSendEmail
|
||||
*/
|
||||
public function testGetMessageLogs(array $email): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$email = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'messageId' => ID::unique(),
|
||||
'status' => 'draft',
|
||||
'topics' => [ID::unique()],
|
||||
'subject' => 'Khali beats Undertaker',
|
||||
'content' => 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $email['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/messaging/messages/email/' . $email['body']['$id'], \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'subject' => 'Khali beats John Cena!',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
$this->assertCount(2, $logs['body']['logs']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(1)', 'offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['equal("$id", "asdf")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['orderAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $email['body']['$id'] . '/logs', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['cursorAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
}
|
||||
@@ -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/oAuthProviders.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']['oAuthProviders'] 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']['oAuthProviders'] as $responseProvider) {
|
||||
if ($responseProvider['key'] === $key) {
|
||||
// On first provider, test enabled=false
|
||||
$this->assertEquals($i !== 0, $responseProvider['enabled']);
|
||||
|
||||
@@ -23,6 +23,7 @@ trait UsersBase
|
||||
'password' => 'password',
|
||||
'name' => 'Cristiano Ronaldo',
|
||||
], false);
|
||||
$this->assertEquals($user['headers']['status-code'], 201);
|
||||
|
||||
// Test empty prefs is object not array
|
||||
$bodyString = $user['body'];
|
||||
@@ -1223,6 +1224,99 @@ 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' => ID::unique(),
|
||||
'name' => 'Sengrid1',
|
||||
'apiKey' => 'my-apikey',
|
||||
'from' => 'from@domain.com',
|
||||
]);
|
||||
$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'],
|
||||
'providerType' => 'email',
|
||||
'identifier' => 'random-email@mail.org',
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals($provider['body']['$id'], $response['body']['providerId']);
|
||||
$this->assertEquals('random-email@mail.org', $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'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'identifier' => 'random-email1@mail.org',
|
||||
]);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('random-email1@mail.org', $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(2, \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(1, $response['body']['total']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
|
||||
@@ -154,9 +154,9 @@ class V16Test extends TestCase
|
||||
public function projectProvider(): array
|
||||
{
|
||||
return [
|
||||
'providers' => [
|
||||
'oAuthProviders' => [
|
||||
[
|
||||
'providers' => [
|
||||
'oAuthProviders' => [
|
||||
[
|
||||
'key' => 'github',
|
||||
'name' => 'GitHub',
|
||||
@@ -167,7 +167,7 @@ class V16Test extends TestCase
|
||||
],
|
||||
],
|
||||
[
|
||||
'providers' => [
|
||||
'oAuthProviders' => [
|
||||
[
|
||||
'name' => 'Github',
|
||||
'appId' => 'client_id',
|
||||
|
||||
Reference in New Issue
Block a user