mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
181
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e6165fcbe | ||
|
|
8a52975048 | ||
|
|
45e3267930 | ||
|
|
2b96d60c1e | ||
|
|
417bb22790 | ||
|
|
1b70bc812b | ||
|
|
e7e429142f | ||
|
|
774e3af61c | ||
|
|
a0514d2aac | ||
|
|
ac137cee3d | ||
|
|
58e091099f | ||
|
|
6410a245eb | ||
|
|
670edd845e | ||
|
|
834c18f9fa | ||
|
|
77930a0cec | ||
|
|
5d9201466b | ||
|
|
d583b5e228 | ||
|
|
c463a9a662 | ||
|
|
bd9f02b689 | ||
|
|
472e2c282d | ||
|
|
7a89779827 | ||
|
|
126de78ce2 | ||
|
|
e919896eef | ||
|
|
fd3475f945 | ||
|
|
9c8f565211 | ||
|
|
08f30224b6 | ||
|
|
6d02554f2a | ||
|
|
61c619f373 | ||
|
|
b32dd316e1 | ||
|
|
22042b4749 | ||
|
|
aa17eeb6e3 | ||
|
|
1529357821 | ||
|
|
3a98361736 | ||
|
|
0c425dbac3 | ||
|
|
69b861cd87 | ||
|
|
0599d23cc5 | ||
|
|
a37b4a40d9 | ||
|
|
7e37046959 | ||
|
|
f9efdfd98e | ||
|
|
d53090647b | ||
|
|
e8b8a8fe52 | ||
|
|
73d3d15ed8 | ||
|
|
b78be65376 | ||
|
|
c3bc3dcb3b | ||
|
|
0e75793bef | ||
|
|
2f4a362e1b | ||
|
|
cda843d8f6 | ||
|
|
d6a40a35ee | ||
|
|
e111b4cc18 | ||
|
|
cdbd80ceda | ||
|
|
58751bbdf1 | ||
|
|
21334ecdff | ||
|
|
47ea2893c3 | ||
|
|
bbeca28026 | ||
|
|
9ab9f6d709 | ||
|
|
309fde38f7 | ||
|
|
3310d271bf | ||
|
|
4e12b6236e | ||
|
|
4df8157d74 | ||
|
|
37e76e52f6 | ||
|
|
cd824faf64 | ||
|
|
96b11b02e6 | ||
|
|
9f24cb4aa3 | ||
|
|
9e4e23ad3f | ||
|
|
3e96f9c2f1 | ||
|
|
dd9f6ed820 | ||
|
|
f7f1bc8352 | ||
|
|
bf50fbeb6c | ||
|
|
1f7bfb3352 | ||
|
|
406181e887 | ||
|
|
a7b243aff9 | ||
|
|
dc85d44647 | ||
|
|
71621f4ccf | ||
|
|
68deee4a56 | ||
|
|
a20392d13a | ||
|
|
e43292052c | ||
|
|
ee7103c4b0 | ||
|
|
1b1ef80b10 | ||
|
|
33f90fcf6a | ||
|
|
2f1eee390b | ||
|
|
35bf13cfab | ||
|
|
f814fd980b | ||
|
|
3ddfd9c3b0 | ||
|
|
75d8110ad9 | ||
|
|
c958f3f1cb | ||
|
|
06e3a144a3 | ||
|
|
36519c13a8 | ||
|
|
9fb1aaadaf | ||
|
|
d9ac4f2f2d | ||
|
|
e61f3c7a42 | ||
|
|
1b6d51a116 | ||
|
|
0058e88217 | ||
|
|
81b4065921 | ||
|
|
d094d6a081 | ||
|
|
cf90653deb | ||
|
|
bae194e866 | ||
|
|
144e88452e | ||
|
|
89980b1f0e | ||
|
|
16fb25ce5a | ||
|
|
7dea3f19a9 | ||
|
|
e78887001a | ||
|
|
aa4ecdf138 | ||
|
|
9bda83104d | ||
|
|
3783378288 | ||
|
|
937cac454a | ||
|
|
36eb28dc22 | ||
|
|
5aff78a619 | ||
|
|
e6781cb4c3 | ||
|
|
62b391ef68 | ||
|
|
6f1d2d094a | ||
|
|
36690229c5 | ||
|
|
fe5d9cc662 | ||
|
|
f46ec96040 | ||
|
|
88bd35ce98 | ||
|
|
2dc813342a | ||
|
|
fd08f7dd36 | ||
|
|
13f4aa9a80 | ||
|
|
db0dbeb27b | ||
|
|
0e27088e2a | ||
|
|
70a7deaa38 | ||
|
|
3cf8f151c4 | ||
|
|
4e0945d0ed | ||
|
|
ddb0d5db26 | ||
|
|
d4517384dc | ||
|
|
a94ccdb4f5 | ||
|
|
9518764cae | ||
|
|
d174233cd6 | ||
|
|
3d0aa74ffe | ||
|
|
e0a937912c | ||
|
|
8951a8465c | ||
|
|
2fa95b52a0 | ||
|
|
0f84f21bb4 | ||
|
|
a14d5c584e | ||
|
|
b79ff7fbff | ||
|
|
0a0cf26269 | ||
|
|
4713ae5baa | ||
|
|
e47802d1e1 | ||
|
|
77face0f2d | ||
|
|
13f886f07f | ||
|
|
7041cae48c | ||
|
|
b5537bb714 | ||
|
|
7392f6aeff | ||
|
|
c34649cb50 | ||
|
|
fde30b6216 | ||
|
|
e0e9b2ba6f | ||
|
|
250a7da1ee | ||
|
|
f208aa206d | ||
|
|
d472978c1a | ||
|
|
1d0c522fad | ||
|
|
beea3abfc7 | ||
|
|
2dfa7d9542 | ||
|
|
22c90ca7ad | ||
|
|
2563a8f967 | ||
|
|
eb1c119c21 | ||
|
|
27d88a421e | ||
|
|
e4755c624f | ||
|
|
21ef33ea45 | ||
|
|
5b2fdf7e5b | ||
|
|
cf6605746d | ||
|
|
b061f95f6d | ||
|
|
c5a45aaded | ||
|
|
d3fac8ae07 | ||
|
|
013e961c37 | ||
|
|
b899e13763 | ||
|
|
5b3659af7c | ||
|
|
95e29d1046 | ||
|
|
aed0079cad | ||
|
|
67311fe5d6 | ||
|
|
5da1f791a5 | ||
|
|
f216ea5d65 | ||
|
|
fb95a05599 | ||
|
|
b76f01b144 | ||
|
|
35e3294dc4 | ||
|
|
81c2ff501b | ||
|
|
1bfd413040 | ||
|
|
c2ea1b5877 | ||
|
|
4dc4cc2c11 | ||
|
|
830eda7460 | ||
|
|
b69bf3d269 | ||
|
|
cee9906073 | ||
|
|
4ad249f918 |
@@ -69,8 +69,8 @@ _APP_STORAGE_ANTIVIRUS_PORT=3310
|
||||
_APP_SMTP_HOST=maildev
|
||||
_APP_SMTP_PORT=1025
|
||||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_SMTP_USERNAME=user
|
||||
_APP_SMTP_PASSWORD=password
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_SMS_PROJECTS_DENY_LIST=
|
||||
@@ -101,6 +101,7 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
|
||||
_APP_STATS_RESOURCES_INTERVAL=30
|
||||
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
||||
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
|
||||
_APP_INTERVAL_DOMAIN_VERIFICATION=60
|
||||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_LOGGING_CONFIG_REALTIME=
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# AGENTS.md
|
||||
|
||||
Appwrite is an end-to-end backend server for web, mobile, native, and backend apps. This guide provides context and instructions for AI coding agents working on the Appwrite codebase.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Appwrite is a self-hosted Backend-as-a-Service (BaaS) platform that provides developers with a set of APIs and tools to build secure, scalable applications. The project uses a hybrid monolithic-microservice architecture built with PHP, running on Swoole for high performance.
|
||||
|
||||
**Key Technologies:**
|
||||
- **Backend:** PHP 8.3+, Swoole
|
||||
- **Libraries:** Utopia PHP
|
||||
- **Database:** MariaDB, Redis
|
||||
- **Cache:** Redis
|
||||
- **Queue:** Redis
|
||||
- **Containers:** Docker
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Run Appwrite
|
||||
docker compose up -d --force-recreate --build
|
||||
|
||||
# Run specific test
|
||||
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] --filter=[FunctionName]
|
||||
|
||||
# Format code
|
||||
composer format
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) coding standard
|
||||
- Use PSR-4 autoloading
|
||||
- Strict type declarations where applicable
|
||||
- Comprehensive PHPDoc comments
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
#### `resourceType` Naming Rule
|
||||
|
||||
When a collection has a combination of `resourceType`, `resourceId`, and/or `resourceInternalId`, the value of `resourceType` MUST always be **plural** - for example: `functions`, `sites`, `deployments`.
|
||||
|
||||
Examples:
|
||||
```php
|
||||
'resourceType' => 'functions'
|
||||
'resourceType' => 'sites'
|
||||
'resourceType' => 'deployments'
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Critical Security Practices
|
||||
|
||||
- **Never hardcode credentials** - Use environment variables
|
||||
- **Rate limiting** - Respect abuse prevention mechanisms
|
||||
|
||||
## Dependencies
|
||||
|
||||
Avoid introducing new dependencies other than utopia-php.
|
||||
|
||||
## Pull Request Guidelines
|
||||
### Before Submitting
|
||||
|
||||
- Run `composer format`
|
||||
- Update documentation if adding features
|
||||
- Add/update tests for your changes
|
||||
- Check that Docker build succeeds
|
||||
`docs/specs/authentication.drawio.svg`
|
||||
|
||||
## Known Issues and Gotchas
|
||||
|
||||
- **Hot Reload:** Code changes require container restart in some cases
|
||||
- **Logging:** There is no central place for logs, so when debugging, ensure to check all possibly relevant containers
|
||||
@@ -57,6 +57,7 @@ RUN mkdir -p /storage/uploads && \
|
||||
# Executables
|
||||
RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/install && \
|
||||
chmod +x /usr/local/bin/interval && \
|
||||
chmod +x /usr/local/bin/maintenance && \
|
||||
chmod +x /usr/local/bin/migrate && \
|
||||
chmod +x /usr/local/bin/realtime && \
|
||||
|
||||
@@ -462,4 +462,15 @@ return [
|
||||
'mock' => true,
|
||||
'class' => 'Appwrite\\Auth\\OAuth2\\Mock',
|
||||
],
|
||||
'mock-unverified' => [
|
||||
'name' => 'MockUnverified',
|
||||
'developers' => 'https://appwrite.io',
|
||||
'icon' => 'icon-appwrite',
|
||||
'enabled' => true,
|
||||
'sandbox' => false,
|
||||
'form' => false,
|
||||
'beta' => false,
|
||||
'mock' => true,
|
||||
'class' => 'Appwrite\\Auth\\OAuth2\\MockUnverified',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -58,6 +58,8 @@ $admins = [
|
||||
'projects.write',
|
||||
'keys.read',
|
||||
'keys.write',
|
||||
'devKeys.read',
|
||||
'devKeys.write',
|
||||
'webhooks.read',
|
||||
'webhooks.write',
|
||||
'locale.read',
|
||||
|
||||
+12
-10
@@ -116,6 +116,7 @@ return [
|
||||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'namespace' => 'io.appwrite',
|
||||
'version' => '11.4.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
@@ -226,7 +227,7 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '12.0.1',
|
||||
'version' => '13.0.0-rc.2',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -262,7 +263,7 @@ return [
|
||||
[
|
||||
'key' => 'nodejs',
|
||||
'name' => 'Node.js',
|
||||
'version' => '21.0.0',
|
||||
'version' => '21.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-node',
|
||||
'package' => 'https://www.npmjs.com/package/node-appwrite',
|
||||
'enabled' => true,
|
||||
@@ -281,7 +282,7 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '19.0.0',
|
||||
'version' => '19.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -300,7 +301,7 @@ return [
|
||||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '14.0.0',
|
||||
'version' => '14.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
@@ -319,7 +320,7 @@ return [
|
||||
[
|
||||
'key' => 'ruby',
|
||||
'name' => 'Ruby',
|
||||
'version' => '20.0.0',
|
||||
'version' => '20.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-ruby',
|
||||
'package' => 'https://rubygems.org/gems/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -338,7 +339,7 @@ return [
|
||||
[
|
||||
'key' => 'go',
|
||||
'name' => 'Go',
|
||||
'version' => 'v0.15.0',
|
||||
'version' => 'v0.16.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-go',
|
||||
'enabled' => true,
|
||||
@@ -357,7 +358,7 @@ return [
|
||||
[
|
||||
'key' => 'dotnet',
|
||||
'name' => '.NET',
|
||||
'version' => '0.23.0',
|
||||
'version' => '0.24.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
|
||||
'package' => 'https://www.nuget.org/packages/Appwrite',
|
||||
'enabled' => true,
|
||||
@@ -376,7 +377,7 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '20.0.0',
|
||||
'version' => '20.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
@@ -395,7 +396,8 @@ return [
|
||||
[
|
||||
'key' => 'kotlin',
|
||||
'name' => 'Kotlin',
|
||||
'version' => '13.0.0',
|
||||
'namespace' => 'io.appwrite',
|
||||
'version' => '13.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
|
||||
'enabled' => true,
|
||||
@@ -418,7 +420,7 @@ return [
|
||||
[
|
||||
'key' => 'swift',
|
||||
'name' => 'Swift',
|
||||
'version' => '14.0.0',
|
||||
'version' => '14.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-swift',
|
||||
'enabled' => true,
|
||||
|
||||
@@ -407,8 +407,8 @@
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-jwt.md",
|
||||
"rate-limit": 100,
|
||||
"rate-time": 3600,
|
||||
"rate-limit": 120,
|
||||
"rate-time": 60,
|
||||
"rate-key": "url:{url},userId:{userId}",
|
||||
"scope": "account",
|
||||
"platforms": [
|
||||
@@ -429,7 +429,23 @@
|
||||
"Session": [],
|
||||
"JWT": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
|
||||
"x-example": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"\/account\/logs": {
|
||||
@@ -535,7 +551,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"group": "mfa",
|
||||
"weight": 306,
|
||||
"weight": 307,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa.md",
|
||||
@@ -607,7 +623,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 308,
|
||||
"weight": 309,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-authenticator.md",
|
||||
@@ -731,7 +747,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 309,
|
||||
"weight": 310,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-authenticator.md",
|
||||
@@ -871,7 +887,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 310,
|
||||
"weight": 311,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/delete-mfa-authenticator.md",
|
||||
@@ -995,7 +1011,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 314,
|
||||
"weight": 315,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-challenge.md",
|
||||
@@ -1129,7 +1145,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 315,
|
||||
"weight": 316,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-challenge.md",
|
||||
@@ -1267,7 +1283,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"group": "mfa",
|
||||
"weight": 307,
|
||||
"weight": 308,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/list-mfa-factors.md",
|
||||
@@ -1368,7 +1384,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 313,
|
||||
"weight": 314,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/get-mfa-recovery-codes.md",
|
||||
@@ -1467,7 +1483,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 311,
|
||||
"weight": 312,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-recovery-codes.md",
|
||||
@@ -1566,7 +1582,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 312,
|
||||
"weight": 313,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-recovery-codes.md",
|
||||
@@ -2558,7 +2574,8 @@
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock"
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": []
|
||||
@@ -3488,7 +3505,8 @@
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock"
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": []
|
||||
@@ -5825,7 +5843,7 @@
|
||||
"avif",
|
||||
"gif"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
"default": ""
|
||||
},
|
||||
@@ -5858,7 +5876,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 380,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
@@ -5925,7 +5943,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 376,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
@@ -5995,7 +6013,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 377,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
@@ -6059,7 +6077,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 378,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
@@ -6137,7 +6155,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
@@ -6203,7 +6221,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 381,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
@@ -6288,7 +6306,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 339,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
@@ -6400,7 +6418,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 331,
|
||||
"weight": 332,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
@@ -6561,7 +6579,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 332,
|
||||
"weight": 333,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
@@ -6672,7 +6690,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 335,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
@@ -6827,7 +6845,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 333,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
@@ -6939,7 +6957,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 337,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
@@ -7046,7 +7064,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 342,
|
||||
"weight": 343,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
@@ -7173,7 +7191,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 341,
|
||||
"weight": 342,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
@@ -7300,7 +7318,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 472,
|
||||
"weight": 473,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
@@ -7387,7 +7405,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 470,
|
||||
"weight": 471,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
@@ -7505,7 +7523,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 471,
|
||||
"weight": 472,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
@@ -7580,7 +7598,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"group": "graphql",
|
||||
"weight": 241,
|
||||
"weight": 242,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/query.md",
|
||||
@@ -7634,7 +7652,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"group": "graphql",
|
||||
"weight": 240,
|
||||
"weight": 241,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/mutation.md",
|
||||
@@ -8120,7 +8138,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 290,
|
||||
"weight": 291,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
@@ -8204,7 +8222,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 294,
|
||||
"weight": 295,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
@@ -9113,7 +9131,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 445,
|
||||
"weight": 446,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
@@ -9183,7 +9201,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 441,
|
||||
"weight": 442,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
@@ -9256,7 +9274,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 442,
|
||||
"weight": 443,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
@@ -9323,7 +9341,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 443,
|
||||
"weight": 444,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
@@ -9404,7 +9422,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 444,
|
||||
"weight": 445,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
@@ -9473,7 +9491,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 446,
|
||||
"weight": 447,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
@@ -9561,7 +9579,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 437,
|
||||
"weight": 438,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
@@ -9672,7 +9690,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 429,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
@@ -9828,7 +9846,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 430,
|
||||
"weight": 431,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
@@ -9938,7 +9956,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 433,
|
||||
"weight": 434,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
@@ -10088,7 +10106,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 431,
|
||||
"weight": 432,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
@@ -10199,7 +10217,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 435,
|
||||
"weight": 436,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
@@ -10305,7 +10323,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 440,
|
||||
"weight": 441,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
@@ -10431,7 +10449,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 439,
|
||||
"weight": 440,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -407,8 +407,8 @@
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-jwt.md",
|
||||
"rate-limit": 100,
|
||||
"rate-time": 3600,
|
||||
"rate-limit": 120,
|
||||
"rate-time": 60,
|
||||
"rate-key": "url:{url},userId:{userId}",
|
||||
"scope": "account",
|
||||
"platforms": [
|
||||
@@ -429,7 +429,23 @@
|
||||
"Session": [],
|
||||
"JWT": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
|
||||
"x-example": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"\/account\/logs": {
|
||||
@@ -535,7 +551,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"group": "mfa",
|
||||
"weight": 306,
|
||||
"weight": 307,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa.md",
|
||||
@@ -607,7 +623,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 308,
|
||||
"weight": 309,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-authenticator.md",
|
||||
@@ -731,7 +747,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 309,
|
||||
"weight": 310,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-authenticator.md",
|
||||
@@ -871,7 +887,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 310,
|
||||
"weight": 311,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/delete-mfa-authenticator.md",
|
||||
@@ -995,7 +1011,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 314,
|
||||
"weight": 315,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-challenge.md",
|
||||
@@ -1129,7 +1145,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 315,
|
||||
"weight": 316,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-challenge.md",
|
||||
@@ -1267,7 +1283,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"group": "mfa",
|
||||
"weight": 307,
|
||||
"weight": 308,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/list-mfa-factors.md",
|
||||
@@ -1368,7 +1384,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 313,
|
||||
"weight": 314,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/get-mfa-recovery-codes.md",
|
||||
@@ -1467,7 +1483,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 311,
|
||||
"weight": 312,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-recovery-codes.md",
|
||||
@@ -1566,7 +1582,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 312,
|
||||
"weight": 313,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-recovery-codes.md",
|
||||
@@ -2558,7 +2574,8 @@
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock"
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": []
|
||||
@@ -3488,7 +3505,8 @@
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock"
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": []
|
||||
@@ -5825,7 +5843,7 @@
|
||||
"avif",
|
||||
"gif"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
"default": ""
|
||||
},
|
||||
@@ -5858,7 +5876,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 380,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
@@ -5925,7 +5943,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 376,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
@@ -5995,7 +6013,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 377,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
@@ -6059,7 +6077,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 378,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
@@ -6137,7 +6155,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
@@ -6203,7 +6221,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 381,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
@@ -6288,7 +6306,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 339,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
@@ -6400,7 +6418,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 331,
|
||||
"weight": 332,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
@@ -6561,7 +6579,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 332,
|
||||
"weight": 333,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
@@ -6672,7 +6690,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 335,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
@@ -6827,7 +6845,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 333,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
@@ -6939,7 +6957,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 337,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
@@ -7046,7 +7064,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 342,
|
||||
"weight": 343,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
@@ -7173,7 +7191,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 341,
|
||||
"weight": 342,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
@@ -7300,7 +7318,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 472,
|
||||
"weight": 473,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
@@ -7387,7 +7405,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 470,
|
||||
"weight": 471,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
@@ -7505,7 +7523,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 471,
|
||||
"weight": 472,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
@@ -7580,7 +7598,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"group": "graphql",
|
||||
"weight": 241,
|
||||
"weight": 242,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/query.md",
|
||||
@@ -7634,7 +7652,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"group": "graphql",
|
||||
"weight": 240,
|
||||
"weight": 241,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/mutation.md",
|
||||
@@ -8120,7 +8138,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 290,
|
||||
"weight": 291,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
@@ -8204,7 +8222,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 294,
|
||||
"weight": 295,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
@@ -9113,7 +9131,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 445,
|
||||
"weight": 446,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
@@ -9183,7 +9201,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 441,
|
||||
"weight": 442,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
@@ -9256,7 +9274,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 442,
|
||||
"weight": 443,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
@@ -9323,7 +9341,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 443,
|
||||
"weight": 444,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
@@ -9404,7 +9422,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 444,
|
||||
"weight": 445,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
@@ -9473,7 +9491,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 446,
|
||||
"weight": 447,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
@@ -9561,7 +9579,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 437,
|
||||
"weight": 438,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
@@ -9672,7 +9690,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 429,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
@@ -9828,7 +9846,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 430,
|
||||
"weight": 431,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
@@ -9938,7 +9956,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 433,
|
||||
"weight": 434,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
@@ -10088,7 +10106,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 431,
|
||||
"weight": 432,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
@@ -10199,7 +10217,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 435,
|
||||
"weight": 436,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
@@ -10305,7 +10323,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 440,
|
||||
"weight": 441,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
@@ -10431,7 +10449,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 439,
|
||||
"weight": 440,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -464,8 +464,8 @@
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-jwt.md",
|
||||
"rate-limit": 100,
|
||||
"rate-time": 3600,
|
||||
"rate-limit": 120,
|
||||
"rate-time": 60,
|
||||
"rate-key": "url:{url},userId:{userId}",
|
||||
"scope": "account",
|
||||
"platforms": [
|
||||
@@ -486,6 +486,23 @@
|
||||
"Session": [],
|
||||
"JWT": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
|
||||
"default": 900,
|
||||
"x-example": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -591,7 +608,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"group": "mfa",
|
||||
"weight": 306,
|
||||
"weight": 307,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa.md",
|
||||
@@ -666,7 +683,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 308,
|
||||
"weight": 309,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-authenticator.md",
|
||||
@@ -790,7 +807,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 309,
|
||||
"weight": 310,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-authenticator.md",
|
||||
@@ -931,7 +948,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 310,
|
||||
"weight": 311,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/delete-mfa-authenticator.md",
|
||||
@@ -1055,7 +1072,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 314,
|
||||
"weight": 315,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-challenge.md",
|
||||
@@ -1192,7 +1209,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 315,
|
||||
"weight": 316,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-challenge.md",
|
||||
@@ -1332,7 +1349,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"group": "mfa",
|
||||
"weight": 307,
|
||||
"weight": 308,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/list-mfa-factors.md",
|
||||
@@ -1433,7 +1450,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 313,
|
||||
"weight": 314,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/get-mfa-recovery-codes.md",
|
||||
@@ -1534,7 +1551,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 311,
|
||||
"weight": 312,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-recovery-codes.md",
|
||||
@@ -1635,7 +1652,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 312,
|
||||
"weight": 313,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-recovery-codes.md",
|
||||
@@ -2667,7 +2684,8 @@
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock"
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": [],
|
||||
@@ -3621,7 +3639,8 @@
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock"
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": [],
|
||||
@@ -5931,7 +5950,7 @@
|
||||
"avif",
|
||||
"gif"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
"default": "",
|
||||
"in": "query"
|
||||
@@ -5963,7 +5982,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 380,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
@@ -6030,7 +6049,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 376,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
@@ -6100,7 +6119,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 377,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
@@ -6163,7 +6182,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 378,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
@@ -6242,7 +6261,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
@@ -6307,7 +6326,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 381,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
@@ -6388,7 +6407,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 339,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
@@ -6492,7 +6511,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 331,
|
||||
"weight": 332,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
@@ -6651,7 +6670,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 332,
|
||||
"weight": 333,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
@@ -6754,7 +6773,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 335,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
@@ -6905,7 +6924,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 333,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
@@ -7015,7 +7034,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 337,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
@@ -7116,7 +7135,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 342,
|
||||
"weight": 343,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
@@ -7237,7 +7256,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 341,
|
||||
"weight": 342,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
@@ -7356,7 +7375,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 472,
|
||||
"weight": 473,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
@@ -7439,7 +7458,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 470,
|
||||
"weight": 471,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
@@ -7558,7 +7577,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 471,
|
||||
"weight": 472,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
@@ -7630,7 +7649,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"group": "graphql",
|
||||
"weight": 241,
|
||||
"weight": 242,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/query.md",
|
||||
@@ -7705,7 +7724,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"group": "graphql",
|
||||
"weight": 240,
|
||||
"weight": 241,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/mutation.md",
|
||||
@@ -8204,7 +8223,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 290,
|
||||
"weight": 291,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
@@ -8289,7 +8308,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 294,
|
||||
"weight": 295,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
@@ -9146,7 +9165,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 445,
|
||||
"weight": 446,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
@@ -9216,7 +9235,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 441,
|
||||
"weight": 442,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
@@ -9289,7 +9308,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 442,
|
||||
"weight": 443,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
@@ -9355,7 +9374,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 443,
|
||||
"weight": 444,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
@@ -9437,7 +9456,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 444,
|
||||
"weight": 445,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
@@ -9505,7 +9524,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 446,
|
||||
"weight": 447,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
@@ -9589,7 +9608,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 437,
|
||||
"weight": 438,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
@@ -9692,7 +9711,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 429,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
@@ -9846,7 +9865,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 430,
|
||||
"weight": 431,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
@@ -9948,7 +9967,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 433,
|
||||
"weight": 434,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
@@ -10094,7 +10113,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 431,
|
||||
"weight": 432,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
@@ -10203,7 +10222,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 435,
|
||||
"weight": 436,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
@@ -10303,7 +10322,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 440,
|
||||
"weight": 441,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
@@ -10423,7 +10442,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 439,
|
||||
"weight": 440,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -464,8 +464,8 @@
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-jwt.md",
|
||||
"rate-limit": 100,
|
||||
"rate-time": 3600,
|
||||
"rate-limit": 120,
|
||||
"rate-time": 60,
|
||||
"rate-key": "url:{url},userId:{userId}",
|
||||
"scope": "account",
|
||||
"platforms": [
|
||||
@@ -486,6 +486,23 @@
|
||||
"Session": [],
|
||||
"JWT": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
|
||||
"default": 900,
|
||||
"x-example": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -591,7 +608,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"group": "mfa",
|
||||
"weight": 306,
|
||||
"weight": 307,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa.md",
|
||||
@@ -666,7 +683,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 308,
|
||||
"weight": 309,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-authenticator.md",
|
||||
@@ -790,7 +807,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 309,
|
||||
"weight": 310,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-authenticator.md",
|
||||
@@ -931,7 +948,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 310,
|
||||
"weight": 311,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/delete-mfa-authenticator.md",
|
||||
@@ -1055,7 +1072,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 314,
|
||||
"weight": 315,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-challenge.md",
|
||||
@@ -1192,7 +1209,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 315,
|
||||
"weight": 316,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-challenge.md",
|
||||
@@ -1332,7 +1349,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"group": "mfa",
|
||||
"weight": 307,
|
||||
"weight": 308,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/list-mfa-factors.md",
|
||||
@@ -1433,7 +1450,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 313,
|
||||
"weight": 314,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/get-mfa-recovery-codes.md",
|
||||
@@ -1534,7 +1551,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 311,
|
||||
"weight": 312,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-recovery-codes.md",
|
||||
@@ -1635,7 +1652,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 312,
|
||||
"weight": 313,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-recovery-codes.md",
|
||||
@@ -2667,7 +2684,8 @@
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock"
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": [],
|
||||
@@ -3621,7 +3639,8 @@
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock"
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": [],
|
||||
@@ -5931,7 +5950,7 @@
|
||||
"avif",
|
||||
"gif"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-name": "ImageFormat",
|
||||
"x-enum-keys": [],
|
||||
"default": "",
|
||||
"in": "query"
|
||||
@@ -5963,7 +5982,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 380,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
@@ -6030,7 +6049,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 376,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
@@ -6100,7 +6119,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 377,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
@@ -6163,7 +6182,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 378,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
@@ -6242,7 +6261,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
@@ -6307,7 +6326,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 381,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
@@ -6388,7 +6407,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 339,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
@@ -6492,7 +6511,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 331,
|
||||
"weight": 332,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
@@ -6651,7 +6670,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 332,
|
||||
"weight": 333,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
@@ -6754,7 +6773,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 335,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
@@ -6905,7 +6924,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 333,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
@@ -7015,7 +7034,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 337,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
@@ -7116,7 +7135,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 342,
|
||||
"weight": 343,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
@@ -7237,7 +7256,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 341,
|
||||
"weight": 342,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
@@ -7356,7 +7375,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 472,
|
||||
"weight": 473,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
@@ -7439,7 +7458,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 470,
|
||||
"weight": 471,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
@@ -7558,7 +7577,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 471,
|
||||
"weight": 472,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
@@ -7630,7 +7649,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"group": "graphql",
|
||||
"weight": 241,
|
||||
"weight": 242,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/query.md",
|
||||
@@ -7705,7 +7724,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"group": "graphql",
|
||||
"weight": 240,
|
||||
"weight": 241,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/mutation.md",
|
||||
@@ -8204,7 +8223,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 290,
|
||||
"weight": 291,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
@@ -8289,7 +8308,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 294,
|
||||
"weight": 295,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
@@ -9146,7 +9165,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 445,
|
||||
"weight": 446,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
@@ -9216,7 +9235,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 441,
|
||||
"weight": 442,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
@@ -9289,7 +9308,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 442,
|
||||
"weight": 443,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
@@ -9355,7 +9374,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 443,
|
||||
"weight": 444,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
@@ -9437,7 +9456,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 444,
|
||||
"weight": 445,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
@@ -9505,7 +9524,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 446,
|
||||
"weight": 447,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
@@ -9589,7 +9608,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 437,
|
||||
"weight": 438,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
@@ -9692,7 +9711,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 429,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
@@ -9846,7 +9865,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 430,
|
||||
"weight": 431,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
@@ -9948,7 +9967,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 433,
|
||||
"weight": 434,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
@@ -10094,7 +10113,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 431,
|
||||
"weight": 432,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
@@ -10203,7 +10222,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 435,
|
||||
"weight": 436,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
@@ -10303,7 +10322,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 440,
|
||||
"weight": 441,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
@@ -10423,7 +10442,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 439,
|
||||
"weight": 440,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,7 @@ use Utopia\Validator;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -1634,9 +1635,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
$failureRedirect(Exception::USER_UNAUTHORIZED, 'OAuth provider failed to return email.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Is verified is not used yet, since we don't know after an account is created anymore if it was verified or not.
|
||||
*/
|
||||
$isVerified = $oauth2->isEmailVerified($accessToken);
|
||||
|
||||
$identity = $dbForProject->findOne('identities', [
|
||||
@@ -1648,16 +1646,32 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
$user = $dbForProject->getDocument('users', $identity->getAttribute('userId'));
|
||||
}
|
||||
|
||||
// If user is not found, check if there is an identity with the same provider user ID
|
||||
// If user is not found, check if there is a user with the same email
|
||||
if ($user === false || $user->isEmpty()) {
|
||||
$userWithEmail = $dbForProject->findOne('users', [
|
||||
Query::equal('email', [$email]),
|
||||
]);
|
||||
if (!$userWithEmail->isEmpty()) {
|
||||
if (!$isVerified) {
|
||||
$failureRedirect(Exception::GENERAL_BAD_REQUEST);
|
||||
}
|
||||
$user->setAttributes($userWithEmail->getArrayCopy());
|
||||
}
|
||||
}
|
||||
|
||||
// If user is not found, check if there is an identity with the same email
|
||||
if ($user === false || $user->isEmpty()) {
|
||||
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
|
||||
Query::equal('providerEmail', [$email]),
|
||||
]);
|
||||
if (!$identityWithMatchingEmail->isEmpty()) {
|
||||
if (!$isVerified) {
|
||||
$failureRedirect(Exception::GENERAL_BAD_REQUEST);
|
||||
}
|
||||
$user->setAttributes($dbForProject->getDocument('users', $identityWithMatchingEmail->getAttribute('userId'))->getArrayCopy());
|
||||
}
|
||||
}
|
||||
|
||||
if ($user === false || $user->isEmpty()) { // Last option -> create the user
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
||||
@@ -1669,14 +1683,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
}
|
||||
}
|
||||
|
||||
// Makes sure this email is not already used in another identity
|
||||
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
|
||||
Query::equal('providerEmail', [$email]),
|
||||
]);
|
||||
if (!$identityWithMatchingEmail->isEmpty()) {
|
||||
$failureRedirect(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
@@ -1730,7 +1736,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'providerType' => MESSAGE_TYPE_EMAIL,
|
||||
'identifier' => $email,
|
||||
]));
|
||||
|
||||
} catch (Duplicate) {
|
||||
$failureRedirect(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
@@ -2949,20 +2954,22 @@ App::post('/v1/account/jwts')
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
))
|
||||
->label('abuse-limit', 100)
|
||||
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
|
||||
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
|
||||
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
|
||||
->label('abuse-key', 'url:{url},userId:{userId}')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (Response $response, User $user, Store $store, ProofsToken $proofForToken) {
|
||||
->action(function (int $duration, Response $response, User $user, Store $store, ProofsToken $proofForToken) {
|
||||
$sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken);
|
||||
|
||||
if (!$sessionId) {
|
||||
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
||||
@@ -945,18 +945,18 @@ App::get('/v1/health/queue/failed/:name')
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('name', '', new WhiteList([
|
||||
Event::DATABASE_QUEUE_NAME,
|
||||
Event::DELETE_QUEUE_NAME,
|
||||
Event::AUDITS_QUEUE_NAME,
|
||||
Event::MAILS_QUEUE_NAME,
|
||||
Event::FUNCTIONS_QUEUE_NAME,
|
||||
Event::STATS_RESOURCES_QUEUE_NAME,
|
||||
Event::STATS_USAGE_QUEUE_NAME,
|
||||
Event::WEBHOOK_QUEUE_NAME,
|
||||
Event::CERTIFICATES_QUEUE_NAME,
|
||||
Event::BUILDS_QUEUE_NAME,
|
||||
Event::MESSAGING_QUEUE_NAME,
|
||||
Event::MIGRATIONS_QUEUE_NAME
|
||||
System::getEnv('_APP_DATABASE_QUEUE_NAME', Event::DATABASE_QUEUE_NAME),
|
||||
System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME),
|
||||
System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME),
|
||||
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME),
|
||||
System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME),
|
||||
System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME),
|
||||
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME),
|
||||
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME),
|
||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME),
|
||||
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME),
|
||||
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME),
|
||||
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME)
|
||||
]), 'The name of the queue')
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('response')
|
||||
@@ -993,18 +993,18 @@ App::get('/v1/health/queue/failed/:name')
|
||||
|
||||
/** @var Event $queue */
|
||||
$queue = match ($name) {
|
||||
Event::DATABASE_QUEUE_NAME => $queueForDatabase,
|
||||
Event::DELETE_QUEUE_NAME => $queueForDeletes,
|
||||
Event::AUDITS_QUEUE_NAME => $queueForAudits,
|
||||
Event::MAILS_QUEUE_NAME => $queueForMails,
|
||||
Event::FUNCTIONS_QUEUE_NAME => $queueForFunctions,
|
||||
Event::STATS_RESOURCES_QUEUE_NAME => $queueForStatsResources,
|
||||
Event::STATS_USAGE_QUEUE_NAME => $queueForStatsUsage,
|
||||
Event::WEBHOOK_QUEUE_NAME => $queueForWebhooks,
|
||||
Event::CERTIFICATES_QUEUE_NAME => $queueForCertificates,
|
||||
Event::BUILDS_QUEUE_NAME => $queueForBuilds,
|
||||
Event::MESSAGING_QUEUE_NAME => $queueForMessaging,
|
||||
Event::MIGRATIONS_QUEUE_NAME => $queueForMigrations,
|
||||
System::getEnv('_APP_DATABASE_QUEUE_NAME', Event::DATABASE_QUEUE_NAME) => $queueForDatabase,
|
||||
System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME) => $queueForDeletes,
|
||||
System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME) => $queueForAudits,
|
||||
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME) => $queueForMails,
|
||||
System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME) => $queueForFunctions,
|
||||
System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME) => $queueForStatsResources,
|
||||
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME) => $queueForStatsUsage,
|
||||
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME) => $queueForWebhooks,
|
||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $queueForCertificates,
|
||||
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $queueForBuilds,
|
||||
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
|
||||
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $queueForMigrations,
|
||||
};
|
||||
$failed = $queue->getSize(failed: true);
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ App::post('/v1/projects')
|
||||
|
||||
// Hook allowing instant project mirroring during migration
|
||||
// Outside of migration, hook is not registered and has no effect
|
||||
$hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]);
|
||||
$hooks->trigger('afterProjectCreation', [$project, $pools, $cache]);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -2079,6 +2079,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
if ($enabled) {
|
||||
$mail = new PHPMailer(true);
|
||||
$mail->isSMTP();
|
||||
$mail->SMTPAuth = (!empty($username) && !empty($password));
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->Host = $host;
|
||||
@@ -2094,7 +2095,7 @@ App::patch('/v1/projects/:projectId/smtp')
|
||||
throw new Exception('Connection is not valid.');
|
||||
}
|
||||
} catch (Throwable $error) {
|
||||
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, 'Could not connect to SMTP server: ' . $error->getMessage());
|
||||
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, $error->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2665,7 +2666,7 @@ App::patch('/v1/projects/:projectId/auth/session-invalidation')
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['invalidateSessions'] = $enabled;
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
@@ -461,7 +461,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
// Add permissions for current the user if none were provided.
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!empty($user->getId())) {
|
||||
if (!empty($user->getId()) && !$isPrivilegedUser) {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
@@ -470,7 +470,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
if (!User::isApp($roles) && !User::isPrivileged($roles)) {
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
$permission = Permission::parse($permission);
|
||||
|
||||
@@ -1048,18 +1048,15 @@ App::init()
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$cache[$domain->get()] = false;
|
||||
Config::setParam('hostnames', $cache);
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Check if domain is a main domain
|
||||
if (!in_array($domain->get(), $platformHostnames)) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,28 @@ App::get('/v1/mock/tests/general/oauth2/user')
|
||||
'id' => 1,
|
||||
'name' => 'User Name',
|
||||
'email' => 'useroauth@localhost.test',
|
||||
'verified' => true,
|
||||
]);
|
||||
});
|
||||
|
||||
App::get('/v1/mock/tests/general/oauth2/user-unverified')
|
||||
->desc('OAuth2 User Unverified')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('token', '', new Text(100), 'OAuth2 Access Token.')
|
||||
->inject('response')
|
||||
->action(function (string $token, Response $response) {
|
||||
|
||||
if ($token != '123456') {
|
||||
throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
|
||||
}
|
||||
|
||||
$response->json([
|
||||
'id' => 2,
|
||||
'name' => 'User Name Unverified',
|
||||
'email' => 'useroauthunverified@localhost.test',
|
||||
'verified' => false,
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -275,6 +275,7 @@ App::setResource('cors', fn (array $allowedHostnames) => new Cors(
|
||||
'X-Appwrite-ID',
|
||||
'X-Appwrite-Timestamp',
|
||||
'X-Appwrite-Session',
|
||||
'X-Appwrite-Platform', // for `$platform` injection and SDK generator
|
||||
// SDK generator
|
||||
'X-SDK-Version',
|
||||
'X-SDK-Name',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php interval $@
|
||||
Generated
+48
-48
@@ -3654,16 +3654,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
"version": "0.13.1",
|
||||
"version": "0.13.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/cache.git",
|
||||
"reference": "97220cb3b3822b166ee016d1646e2ae2815dc540"
|
||||
"reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/cache/zipball/97220cb3b3822b166ee016d1646e2ae2815dc540",
|
||||
"reference": "97220cb3b3822b166ee016d1646e2ae2815dc540",
|
||||
"url": "https://api.github.com/repos/utopia-php/cache/zipball/5768498c9f451482f0bf3eede4d6452ddcd4a0f6",
|
||||
"reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3672,7 +3672,7 @@
|
||||
"ext-redis": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/pools": "0.8.*",
|
||||
"utopia-php/telemetry": "0.1.*"
|
||||
"utopia-php/telemetry": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
@@ -3700,9 +3700,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/cache/issues",
|
||||
"source": "https://github.com/utopia-php/cache/tree/0.13.1"
|
||||
"source": "https://github.com/utopia-php/cache/tree/0.13.2"
|
||||
},
|
||||
"time": "2025-05-09T14:43:52+00:00"
|
||||
"time": "2025-12-17T08:55:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cli",
|
||||
@@ -3896,16 +3896,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "af15066255a5fd7bd2926de37bcbf3d8500fc155"
|
||||
"reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/af15066255a5fd7bd2926de37bcbf3d8500fc155",
|
||||
"reference": "af15066255a5fd7bd2926de37bcbf3d8500fc155",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/c8c1b2f5770245dd4006e2680681e3efbe8b1fa7",
|
||||
"reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3948,9 +3948,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/3.6.0"
|
||||
"source": "https://github.com/utopia-php/database/tree/3.6.1"
|
||||
},
|
||||
"time": "2025-12-08T05:23:04+00:00"
|
||||
"time": "2025-12-16T09:55:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -3999,23 +3999,23 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/dns",
|
||||
"version": "1.4.0",
|
||||
"version": "1.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/dns.git",
|
||||
"reference": "dce3453364a4524b7250db8d8eb74820b814409e"
|
||||
"reference": "5daf8b683dad877491c4df84c6be24850b2f363b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/dce3453364a4524b7250db8d8eb74820b814409e",
|
||||
"reference": "dce3453364a4524b7250db8d8eb74820b814409e",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/5daf8b683dad877491c4df84c6be24850b2f363b",
|
||||
"reference": "5daf8b683dad877491c4df84c6be24850b2f363b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"utopia-php/console": "0.0.*",
|
||||
"utopia-php/domains": "0.9.*",
|
||||
"utopia-php/telemetry": "0.1.*",
|
||||
"utopia-php/telemetry": "*",
|
||||
"utopia-php/validators": "0.*"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -4050,9 +4050,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/dns/issues",
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.4.0"
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.4.1"
|
||||
},
|
||||
"time": "2025-12-05T10:09:00+00:00"
|
||||
"time": "2025-12-17T09:09:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
@@ -4730,21 +4730,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/pools",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/pools.git",
|
||||
"reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d"
|
||||
"reference": "ad7d6ba946376e81c603204285ce9a674b6502b8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/pools/zipball/05c67aba42eb68ac65489cc1e7fc5db83db2dd4d",
|
||||
"reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d",
|
||||
"url": "https://api.github.com/repos/utopia-php/pools/zipball/ad7d6ba946376e81c603204285ce9a674b6502b8",
|
||||
"reference": "ad7d6ba946376e81c603204285ce9a674b6502b8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"utopia-php/telemetry": "0.1.*"
|
||||
"php": ">=8.4",
|
||||
"utopia-php/telemetry": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.*",
|
||||
@@ -4776,9 +4776,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/pools/issues",
|
||||
"source": "https://github.com/utopia-php/pools/tree/0.8.2"
|
||||
"source": "https://github.com/utopia-php/pools/tree/0.8.3"
|
||||
},
|
||||
"time": "2025-04-17T02:04:54+00:00"
|
||||
"time": "2025-12-17T09:35:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/preloader",
|
||||
@@ -4835,16 +4835,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/queue",
|
||||
"version": "0.11.1",
|
||||
"version": "0.11.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/queue.git",
|
||||
"reference": "498bbbef418b1db71b51e1bb62f5d1d752ddd8d6"
|
||||
"reference": "a854f7c4abc18e0eca55fc5608cd7088d71eb19f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/498bbbef418b1db71b51e1bb62f5d1d752ddd8d6",
|
||||
"reference": "498bbbef418b1db71b51e1bb62f5d1d752ddd8d6",
|
||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/a854f7c4abc18e0eca55fc5608cd7088d71eb19f",
|
||||
"reference": "a854f7c4abc18e0eca55fc5608cd7088d71eb19f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4854,7 +4854,7 @@
|
||||
"utopia-php/fetch": "0.4.*",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/pools": "0.8.*",
|
||||
"utopia-php/telemetry": "0.1.*"
|
||||
"utopia-php/telemetry": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-redis": "*",
|
||||
@@ -4895,9 +4895,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/queue/issues",
|
||||
"source": "https://github.com/utopia-php/queue/tree/0.11.1"
|
||||
"source": "https://github.com/utopia-php/queue/tree/0.11.2"
|
||||
},
|
||||
"time": "2025-05-30T11:50:34+00:00"
|
||||
"time": "2025-12-17T09:32:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/registry",
|
||||
@@ -4953,16 +4953,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/storage",
|
||||
"version": "0.18.16",
|
||||
"version": "0.18.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/storage.git",
|
||||
"reference": "0c7b8ad68de8e1eb23ccc8af9f27a30eb832930f"
|
||||
"reference": "acaea524f315f87b8811a2c34450fe2b502f49d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/0c7b8ad68de8e1eb23ccc8af9f27a30eb832930f",
|
||||
"reference": "0c7b8ad68de8e1eb23ccc8af9f27a30eb832930f",
|
||||
"url": "https://api.github.com/repos/utopia-php/storage/zipball/acaea524f315f87b8811a2c34450fe2b502f49d8",
|
||||
"reference": "acaea524f315f87b8811a2c34450fe2b502f49d8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5005,9 +5005,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/storage/issues",
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.18.16"
|
||||
"source": "https://github.com/utopia-php/storage/tree/0.18.18"
|
||||
},
|
||||
"time": "2025-12-03T02:15:45+00:00"
|
||||
"time": "2025-12-17T07:33:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/swoole",
|
||||
@@ -5436,16 +5436,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.5.9",
|
||||
"version": "1.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "ee434aa00a9185380b9a39bb46bf86d7104d3a93"
|
||||
"reference": "3876d486e2c00b788fbda677ef9fcc77391b8898"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/ee434aa00a9185380b9a39bb46bf86d7104d3a93",
|
||||
"reference": "ee434aa00a9185380b9a39bb46bf86d7104d3a93",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/3876d486e2c00b788fbda677ef9fcc77391b8898",
|
||||
"reference": "3876d486e2c00b788fbda677ef9fcc77391b8898",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5481,9 +5481,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/1.5.9"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.7.2"
|
||||
},
|
||||
"time": "2025-11-25T05:22:25+00:00"
|
||||
"time": "2025-12-24T07:49:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/annotations",
|
||||
@@ -8943,7 +8943,7 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
||||
@@ -555,6 +555,7 @@ services:
|
||||
- _APP_DOMAIN_TARGET_CAA
|
||||
- _APP_DNS
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_EMAIL_CERTIFICATES
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
@@ -753,6 +754,7 @@ services:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
@@ -785,6 +787,43 @@ services:
|
||||
- _APP_MAINTENANCE_START_TIME
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-task-interval:
|
||||
entrypoint: interval
|
||||
<<: *x-logging
|
||||
container_name: appwrite-task-interval
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_DOMAIN
|
||||
- _APP_DOMAIN_TARGET_CNAME
|
||||
- _APP_DOMAIN_TARGET_AAAA
|
||||
- _APP_DOMAIN_TARGET_A
|
||||
- _APP_DOMAIN_TARGET_CAA
|
||||
- _APP_DNS
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
- _APP_INTERVAL_DOMAIN_VERIFICATION
|
||||
|
||||
appwrite-task-stats-resources:
|
||||
container_name: appwrite-task-stats-resources
|
||||
entrypoint: stats-resources
|
||||
@@ -1131,6 +1170,9 @@ services:
|
||||
- "traefik.http.routers.appwrite_maildev_https.rule=Host(`mail.localhost`)"
|
||||
- "traefik.http.routers.appwrite_maildev_https.service=appwrite_maildev"
|
||||
- "traefik.http.routers.appwrite_maildev_https.tls=true"
|
||||
environment:
|
||||
- MAILDEV_INCOMING_USER=${_APP_SMTP_USERNAME}
|
||||
- MAILDEV_INCOMING_PASS=${_APP_SMTP_PASSWORD}
|
||||
|
||||
request-catcher-webhook: # used mainly for dev tests (mock HTTP webhook)
|
||||
image: appwrite/requestcatcher:1.0.0
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Change Log
|
||||
|
||||
## 13.0.0-rc.2
|
||||
|
||||
* Fixes a lot of typescript errors throughout the codebase
|
||||
|
||||
## 13.0.0-rc.1
|
||||
|
||||
* Migrates codebase from JavaScript to TypeScript
|
||||
|
||||
## 12.0.1
|
||||
|
||||
Fix type generation for `point`, `lineString` and `polygon` columns
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 20.1.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## 20.0.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 0.24.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## 0.23.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## v0.16.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## v0.15.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 13.1.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## 13.0.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 21.1.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## 21.0.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 19.1.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## 19.0.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 14.1.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## 14.0.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 20.1.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## 20.0.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 14.1.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
## 14.0.0
|
||||
|
||||
* Rename `VCSDeploymentType` enum to `VCSReferenceType`
|
||||
|
||||
@@ -130,7 +130,9 @@ class Mock extends OAuth2
|
||||
*/
|
||||
public function isEmailVerified(string $accessToken): bool
|
||||
{
|
||||
return true;
|
||||
$user = $this->getUser($accessToken);
|
||||
|
||||
return $user['verified'] ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Auth\OAuth2;
|
||||
|
||||
class MockUnverified extends Mock
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'mock-unverified';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUser(string $accessToken): array
|
||||
{
|
||||
if (empty($this->user)) {
|
||||
$user = $this->request('GET', 'http://localhost/' . $this->version . '/mock/tests/general/oauth2/user-unverified?token=' . \urlencode($accessToken));
|
||||
|
||||
$this->user = \json_decode($user, true);
|
||||
}
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ interface Adapter
|
||||
{
|
||||
public function issueCertificate(string $certName, string $domain, ?string $domainType): ?string;
|
||||
|
||||
public function isInstantGeneration(string $domain, ?string $domainType): bool;
|
||||
|
||||
public function getCertificateStatus(string $domain, ?string $domainType): string;
|
||||
|
||||
public function isRenewRequired(string $domain, ?string $domainType, Log $log): bool;
|
||||
|
||||
public function deleteCertificate(string $domain): void;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Certificates\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
// Exception thrown during certificate status retrieval
|
||||
class CertificateStatus extends Exception
|
||||
{
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Certificates;
|
||||
|
||||
use Appwrite\Certificates\Exception\CertificateStatus as CertificateStatusException;
|
||||
use Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
@@ -84,6 +85,16 @@ class LetsEncrypt implements Adapter
|
||||
return DateTime::addSeconds($dt, -60 * 60 * 24 * 30);
|
||||
}
|
||||
|
||||
public function isInstantGeneration(string $domain, ?string $domainType): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getCertificateStatus(string $domain, ?string $domainType): string
|
||||
{
|
||||
throw new CertificateStatusException('Certificate status retrieval is not supported for LetsEncrypt.');
|
||||
}
|
||||
|
||||
public function isRenewRequired(string $domain, ?string $domainType, Log $log): bool
|
||||
{
|
||||
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Audit extends Event
|
||||
{
|
||||
@@ -19,8 +20,8 @@ class Audit extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::AUDITS_QUEUE_NAME)
|
||||
->setClass(Event::AUDITS_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_AUDITS_CLASS_NAME', Event::AUDITS_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Appwrite\Event;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Build extends Event
|
||||
{
|
||||
@@ -18,8 +19,8 @@ class Build extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::BUILDS_QUEUE_NAME)
|
||||
->setClass(Event::BUILDS_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_BUILDS_CLASS_NAME', Event::BUILDS_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,10 +4,14 @@ namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Certificate extends Event
|
||||
{
|
||||
public const string ACTION_DOMAIN_VERIFICATION = 'verification';
|
||||
public const string ACTION_GENERATION = 'generation';
|
||||
protected bool $skipRenewCheck = false;
|
||||
protected string $action = self::ACTION_GENERATION;
|
||||
protected ?Document $domain = null;
|
||||
protected ?string $validationDomain = null;
|
||||
|
||||
@@ -16,8 +20,8 @@ class Certificate extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::CERTIFICATES_QUEUE_NAME)
|
||||
->setClass(Event::CERTIFICATES_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_CERTIFICATES_CLASS_NAME', Event::CERTIFICATES_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +94,28 @@ class Certificate extends Event
|
||||
return $this->skipRenewCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set action for this certificate event.
|
||||
*
|
||||
* @param string $action
|
||||
* @return self
|
||||
*/
|
||||
public function setAction(string $action): self
|
||||
{
|
||||
$this->action = $action;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action for this certificate event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAction(): string
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the payload for the event
|
||||
@@ -102,7 +128,8 @@ class Certificate extends Event
|
||||
'project' => $this->project,
|
||||
'domain' => $this->domain,
|
||||
'skipRenewCheck' => $this->skipRenewCheck,
|
||||
'validationDomain' => $this->validationDomain
|
||||
'validationDomain' => $this->validationDomain,
|
||||
'action' => $this->action
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Appwrite\Event;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Database extends Event
|
||||
{
|
||||
@@ -24,7 +25,7 @@ class Database extends Event
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this->setClass(Event::DATABASE_CLASS_NAME);
|
||||
$this->setClass(System::getEnv('_APP_DATABASE_CLASS_NAME', Event::DATABASE_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Delete extends Event
|
||||
{
|
||||
@@ -20,8 +21,8 @@ class Delete extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::DELETE_QUEUE_NAME)
|
||||
->setClass(Event::DELETE_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_DELETE_CLASS_NAME', Event::DELETE_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Appwrite\Event;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Func extends Event
|
||||
{
|
||||
@@ -25,8 +26,8 @@ class Func extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
|
||||
->setClass(Event::FUNCTIONS_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_FUNCTIONS_CLASS_NAME', Event::FUNCTIONS_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Mail extends Event
|
||||
{
|
||||
@@ -24,8 +25,8 @@ class Mail extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::MAILS_QUEUE_NAME)
|
||||
->setClass(Event::MAILS_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_MAILS_CLASS_NAME', Event::MAILS_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Messaging extends Event
|
||||
{
|
||||
@@ -19,8 +20,8 @@ class Messaging extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::MESSAGING_QUEUE_NAME)
|
||||
->setClass(Event::MESSAGING_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_MESSAGING_CLASS_NAME', Event::MESSAGING_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Appwrite\Event;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Migration extends Event
|
||||
{
|
||||
@@ -16,8 +17,8 @@ class Migration extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::MIGRATIONS_QUEUE_NAME)
|
||||
->setClass(Event::MIGRATIONS_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_MIGRATIONS_CLASS_NAME', Event::MIGRATIONS_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class StatsResources extends Event
|
||||
{
|
||||
@@ -13,8 +14,8 @@ class StatsResources extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::STATS_RESOURCES_QUEUE_NAME)
|
||||
->setClass(Event::STATS_RESOURCES_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_STATS_RESOURCES_CLASS_NAME', Event::STATS_RESOURCES_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class StatsUsage extends Event
|
||||
{
|
||||
@@ -18,8 +19,8 @@ class StatsUsage extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::STATS_USAGE_QUEUE_NAME)
|
||||
->setClass(Event::STATS_USAGE_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_STATS_USAGE_CLASS_NAME', Event::STATS_USAGE_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Webhook extends Event
|
||||
{
|
||||
@@ -11,8 +12,8 @@ class Webhook extends Event
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME);
|
||||
->setQueue(System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_WEBHOOK_CLASS_NAME', Event::WEBHOOK_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,9 +22,9 @@ class Action extends UtopiaAction
|
||||
protected mixed $logError;
|
||||
|
||||
protected array $filters = [
|
||||
'subQueryKeys', 'subQueryWebhooks', 'subQueryPlatforms', 'subQueryProjectVariables', 'subQueryBlocks', 'subQueryDevKeys', // Project
|
||||
'subQueryKeys', 'subQueryWebhooks', 'subQueryPlatforms', 'subQueryBlocks', 'subQueryDevKeys', // Project
|
||||
'subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships', 'subQueryTargets', 'subQueryTopicTargets',// Users
|
||||
'subQueryVariables', // Sites
|
||||
'subQueryVariables', 'subQueryProjectVariables' // Sites / Functions
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -107,9 +107,11 @@ class Action extends UtopiaAction
|
||||
}
|
||||
}
|
||||
|
||||
public function disableSubqueries()
|
||||
public function disableSubqueries(array $filters = []): void
|
||||
{
|
||||
$filters = $this->filters;
|
||||
if (empty($filters)) {
|
||||
$filters = $this->filters;
|
||||
}
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
Database::addFilter(
|
||||
@@ -189,6 +191,11 @@ class Action extends UtopiaAction
|
||||
}
|
||||
}
|
||||
|
||||
// found a wildcard, return!
|
||||
if (\in_array('*', $attributes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$responseModel = $response->getModel($model);
|
||||
foreach ($responseModel->getRules() as $ruleName => $rule) {
|
||||
if (\str_starts_with($ruleName, '$')) {
|
||||
|
||||
@@ -80,7 +80,7 @@ class Create extends Action
|
||||
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('documentSecurity', false, new Boolean(true), 'Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(), 'Is collection enabled? When set to \'disabled\', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.', true)
|
||||
->param('attributes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.', true)
|
||||
->param('attributes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options.', true)
|
||||
->param('indexes', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
||||
+5
-3
@@ -382,9 +382,11 @@ abstract class Action extends DatabasesAction
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
if (!empty($queueForEvents->getProject()?->getAttribute('webhooks', []))) {
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
$queueForEvents->reset();
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@ class Create extends Action
|
||||
// Add permissions for current the user if none were provided.
|
||||
if (\is_null($permissions)) {
|
||||
$permissions = [];
|
||||
if (!empty($user->getId())) {
|
||||
if (!empty($user->getId()) && !$isPrivilegedUser) {
|
||||
foreach ($allowedPermissions as $permission) {
|
||||
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class Create extends Action
|
||||
->setHttpPath('/v1/projects/:projectId/dev-keys')
|
||||
->desc('Create dev key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'devKeys.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
|
||||
@@ -28,7 +28,7 @@ class Delete extends Action
|
||||
->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId')
|
||||
->desc('Delete dev key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'devKeys.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
|
||||
@@ -28,7 +28,7 @@ class Get extends Action
|
||||
->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId')
|
||||
->desc('Get dev key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('scope', 'devKeys.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
|
||||
@@ -29,7 +29,7 @@ class Update extends Action
|
||||
->setHttpPath('/v1/projects/:projectId/dev-keys/:keyId')
|
||||
->desc('Update dev key')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('scope', 'devKeys.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
|
||||
@@ -32,7 +32,7 @@ class XList extends Action
|
||||
->setHttpPath('/v1/projects/:projectId/dev-keys')
|
||||
->desc('List dev keys')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.read')
|
||||
->label('scope', 'devKeys.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'devKeys',
|
||||
|
||||
@@ -3,19 +3,21 @@
|
||||
namespace Appwrite\Platform\Modules\Projects\Http\Projects;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Boolean;
|
||||
@@ -24,6 +26,10 @@ use Utopia\Validator\Text;
|
||||
class XList extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
// cached mapping of columns to their subQuery filters
|
||||
private static ?array $attributeToSubQueryFilters = null;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'listProjects';
|
||||
@@ -61,12 +67,13 @@ class XList extends Action
|
||||
->param('queries', [], $this->getQueriesValidator(), '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(', ', Projects::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(array $queries, string $search, bool $includeTotal, Response $response, Database $dbForPlatform)
|
||||
public function action(array $queries, string $search, bool $includeTotal, Request $request, Response $response, Database $dbForPlatform)
|
||||
{
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
@@ -103,16 +110,89 @@ class XList extends Action
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
try {
|
||||
$projects = $dbForPlatform->find('projects', $queries);
|
||||
$selectQueries = Query::groupByType($queries)['selections'] ?? [];
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$projects = $this->find($dbForPlatform, $queries, $selectQueries);
|
||||
$total = $includeTotal ? $dbForPlatform->count('projects', $filterQueries, APP_LIMIT_COUNT) : 0;
|
||||
} catch (Order $e) {
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$this->applySelectQueries($request, $response, Response::MODEL_PROJECT);
|
||||
$response->dynamic(new Document([
|
||||
'projects' => $projects,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_PROJECT_LIST);
|
||||
}
|
||||
|
||||
// Build mapping of columns to their subQuery filters
|
||||
private static function getAttributeToSubQueryFilters(): array
|
||||
{
|
||||
if (self::$attributeToSubQueryFilters !== null) {
|
||||
return self::$attributeToSubQueryFilters;
|
||||
}
|
||||
|
||||
self::$attributeToSubQueryFilters = [];
|
||||
|
||||
$collections = Config::getParam('collections', []);
|
||||
$projectAttributes = $collections['platform']['projects']['attributes'] ?? [];
|
||||
|
||||
foreach ($projectAttributes as $attribute) {
|
||||
$attributeId = $attribute['$id'] ?? null;
|
||||
$filters = $attribute['filters'] ?? [];
|
||||
|
||||
if ($attributeId === null || empty($filters)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// extract only subQuery filters
|
||||
$subQueryFilters = \array_filter($filters, function ($filter) {
|
||||
return \str_starts_with($filter, 'subQuery');
|
||||
});
|
||||
|
||||
if (!empty($subQueryFilters)) {
|
||||
self::$attributeToSubQueryFilters[$attributeId] = \array_values($subQueryFilters);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$attributeToSubQueryFilters;
|
||||
}
|
||||
|
||||
private function find(Database $dbForPlatform, array $queries, array $selectQueries): array
|
||||
{
|
||||
if (empty($selectQueries)) {
|
||||
return $dbForPlatform->find('projects', $queries);
|
||||
}
|
||||
|
||||
$selectedAttributes = [];
|
||||
foreach ($selectQueries as $query) {
|
||||
foreach ($query->getValues() as $value) {
|
||||
$selectedAttributes[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (\in_array('*', $selectedAttributes)) {
|
||||
return $dbForPlatform->find('projects', $queries);
|
||||
}
|
||||
|
||||
$filtersToSkipMap = [];
|
||||
$selectedAttributesMap = \array_flip($selectedAttributes);
|
||||
$attributeToSubQueryFilters = self::getAttributeToSubQueryFilters();
|
||||
|
||||
foreach ($attributeToSubQueryFilters as $attributeName => $subQueryFilters) {
|
||||
if (!isset($selectedAttributesMap[$attributeName])) {
|
||||
foreach ($subQueryFilters as $filter) {
|
||||
$filtersToSkipMap[$filter] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filtersToSkip = \array_keys($filtersToSkipMap);
|
||||
|
||||
return empty($filtersToSkip)
|
||||
? $dbForPlatform->find('projects', $queries)
|
||||
: $dbForPlatform->skipFilters(fn () => $dbForPlatform->find('projects', $queries), $filtersToSkip);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
|
||||
namespace Appwrite\Platform\Modules\Proxy;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\DNS as ValidatorDNS;
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\API;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Action;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -125,6 +125,7 @@ class Create extends Action
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Function;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Action;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -143,6 +143,7 @@ class Create extends Action
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Action;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -147,6 +147,7 @@ class Create extends Action
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Site;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Action;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -143,6 +143,7 @@ class Create extends Action
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Verification;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Http\Rules\Action;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Services;
|
||||
|
||||
use Appwrite\Platform\Tasks\Doctor;
|
||||
use Appwrite\Platform\Tasks\Install;
|
||||
use Appwrite\Platform\Tasks\Interval;
|
||||
use Appwrite\Platform\Tasks\Maintenance;
|
||||
use Appwrite\Platform\Tasks\Migrate;
|
||||
use Appwrite\Platform\Tasks\QueueRetry;
|
||||
@@ -28,6 +29,7 @@ class Tasks extends Service
|
||||
$this
|
||||
->addAction(Doctor::getName(), new Doctor())
|
||||
->addAction(Install::getName(), new Install())
|
||||
->addAction(Interval::getName(), new Interval())
|
||||
->addAction(Maintenance::getName(), new Maintenance())
|
||||
->addAction(Migrate::getName(), new Migrate())
|
||||
->addAction(QueueRetry::getName(), new QueueRetry())
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use DateTime;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime as DatabaseDateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Interval extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'interval';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Schedules tasks on regular intervals by publishing them to our queues')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForCertificates')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(Database $dbForPlatform, Certificate $queueForCertificates): void
|
||||
{
|
||||
Console::title('Interval V1');
|
||||
Console::success(APP_NAME . ' interval process v1 has started');
|
||||
|
||||
$intervalDomainVerification = (int) System::getEnv('_APP_INTERVAL_DOMAIN_VERIFICATION', '60'); // 1 minute
|
||||
|
||||
\go(function () use ($dbForPlatform, $queueForCertificates, $intervalDomainVerification) {
|
||||
Console::loop(function () use ($dbForPlatform, $queueForCertificates) {
|
||||
$this->verifyDomain($dbForPlatform, $queueForCertificates);
|
||||
}, $intervalDomainVerification);
|
||||
});
|
||||
}
|
||||
|
||||
private function verifyDomain(Database $dbForPlatform, Certificate $queueForCertificates): void
|
||||
{
|
||||
$time = DatabaseDateTime::now();
|
||||
$fromTime = new DateTime('-3 days'); // Max 3 days old
|
||||
|
||||
$rules = $dbForPlatform->find('rules', [
|
||||
Query::createdAfter(DatabaseDateTime::format($fromTime)),
|
||||
Query::equal('status', [RULE_STATUS_CREATED]), // Created but not verified yet
|
||||
Query::orderAsc('$updatedAt'), // Pick the ones waiting for another attempt for longest
|
||||
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]), // Only current region
|
||||
Query::limit(30), // Reasonable pagination limit, processable within a minute
|
||||
]);
|
||||
|
||||
if (\count($rules) === 0) {
|
||||
Console::info("[{$time}] No rules for domain verification.");
|
||||
return; // No rules to verify
|
||||
}
|
||||
|
||||
Console::info("[{$time}] Found " . \count($rules) . " rules for domain verification, scheduling jobs.");
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_DOMAIN_VERIFICATION)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,33 +125,36 @@ class Maintenance extends Action
|
||||
Query::limit(200), // Limit 200 comes from LetsEncrypt (300 orders per 3 hours, keeping some for new domains)
|
||||
]);
|
||||
|
||||
if (\count($certificates) === 0) {
|
||||
Console::info("[{$time}] No certificates for renewal.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (\count($certificates) > 0) {
|
||||
Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
|
||||
Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
|
||||
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$appRegion = System::getEnv('_APP_REGION', 'default');
|
||||
|
||||
foreach ($certificates as $certificate) {
|
||||
$domain = $certificate->getAttribute('domain');
|
||||
$rule = $isMd5
|
||||
? $dbForPlatform->getDocument('rules', md5($domain))
|
||||
: $dbForPlatform->findOne('rules', [
|
||||
foreach ($certificates as $certificate) {
|
||||
$domain = $certificate->getAttribute('domain');
|
||||
$rule = $isMd5 ?
|
||||
$dbForPlatform->getDocument('rules', md5($domain)) :
|
||||
$dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
Query::limit(1)
|
||||
]);
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('region') !== System::getEnv('_APP_REGION', 'default')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$queueForCertificate
|
||||
->setDomain(new Document([
|
||||
'domain' => $certificate->getAttribute('domain')
|
||||
]))
|
||||
->trigger();
|
||||
if ($rule->isEmpty() || $rule->getAttribute('region') !== $appRegion) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
Console::info("[{$time}] No certificates for renewal.");
|
||||
|
||||
$queueForCertificate
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ class SDKs extends Action
|
||||
return 'sdks';
|
||||
}
|
||||
|
||||
public static function getPlatforms(): array
|
||||
{
|
||||
return Specs::getPlatforms();
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
@@ -55,7 +60,7 @@ class SDKs extends Action
|
||||
public function action(?string $selectedPlatform, ?string $selectedSDK, ?string $version, ?string $git, ?string $production, ?string $message, ?string $release, ?string $commit, ?string $sdks): void
|
||||
{
|
||||
if (!$sdks) {
|
||||
$selectedPlatform ??= Console::confirm('Choose Platform ("' . APP_SDK_PLATFORM_CLIENT . '", "' . APP_SDK_PLATFORM_SERVER . '", "' . APP_SDK_PLATFORM_CONSOLE . '" or "*" for all):');
|
||||
$selectedPlatform ??= Console::confirm('Choose Platform ("' . implode('", "', static::getPlatforms()) . '" or "*" for all):');
|
||||
$selectedSDK ??= \strtolower(Console::confirm('Choose SDK ("*" for all):'));
|
||||
} else {
|
||||
$sdks = explode(',', $sdks);
|
||||
@@ -188,8 +193,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
break;
|
||||
case 'php':
|
||||
$config = new PHP();
|
||||
$config->setComposerVendor('appwrite');
|
||||
$config->setComposerPackage('appwrite');
|
||||
$config->setComposerVendor($language['composerVendor'] ?? 'appwrite');
|
||||
$config->setComposerPackage($language['composerPackage'] ?? 'appwrite');
|
||||
break;
|
||||
case 'nodejs':
|
||||
$config = new Node();
|
||||
@@ -374,9 +379,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
|
||||
$sdk
|
||||
->setName($language['name'])
|
||||
->setNamespace('io appwrite')
|
||||
->setDescription("Appwrite is an open-source backend as a service server that abstract and simplify complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the {$language['name']} SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)")
|
||||
->setShortDescription('Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API')
|
||||
->setNamespace($language['namespace'] ?? 'appwrite')
|
||||
->setDescription($language['description'] ?? "Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the {$language['name']} SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)")
|
||||
->setShortDescription('Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API')
|
||||
->setLicense($license)
|
||||
->setLicenseContent($licenseContent)
|
||||
->setVersion($language['version'])
|
||||
|
||||
@@ -66,7 +66,7 @@ class Specs extends Action
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
protected function getPlatforms(): array
|
||||
public static function getPlatforms(): array
|
||||
{
|
||||
return [
|
||||
APP_SDK_PLATFORM_CLIENT,
|
||||
@@ -239,7 +239,7 @@ class Specs extends Action
|
||||
App::setResource('dbForPlatform', fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
App::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
|
||||
$platforms = $this->getPlatforms();
|
||||
$platforms = static::getPlatforms();
|
||||
$authCounts = $this->getAuthCounts();
|
||||
$keys = $this->getKeys();
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Certificates\Adapter as CertificatesAdapter;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
use Exception;
|
||||
@@ -22,15 +24,12 @@ use Utopia\Database\Exception\Conflict;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\DNS\Message\Record;
|
||||
use Utopia\Database\Validator\Authorization as ValidatorAuthorization;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\AnyOf;
|
||||
use Utopia\Validator\IP;
|
||||
|
||||
class Certificates extends Action
|
||||
{
|
||||
@@ -42,8 +41,10 @@ class Certificates extends Action
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct(...$params)
|
||||
{
|
||||
parent::__construct(...$params);
|
||||
|
||||
$this
|
||||
->desc('Certificates worker')
|
||||
->inject('message')
|
||||
@@ -53,6 +54,7 @@ class Certificates extends Action
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForCertificates')
|
||||
->inject('log')
|
||||
->inject('certificates')
|
||||
->inject('plan')
|
||||
@@ -67,6 +69,7 @@ class Certificates extends Action
|
||||
* @param Webhook $queueForWebhooks
|
||||
* @param Func $queueForFunctions
|
||||
* @param Realtime $queueForRealtime
|
||||
* @param Certificate $queueForCertificates
|
||||
* @param Log $log
|
||||
* @param CertificatesAdapter $certificates
|
||||
* @return void
|
||||
@@ -81,6 +84,7 @@ class Certificates extends Action
|
||||
Webhook $queueForWebhooks,
|
||||
Func $queueForFunctions,
|
||||
Realtime $queueForRealtime,
|
||||
Certificate $queueForCertificates,
|
||||
Log $log,
|
||||
CertificatesAdapter $certificates,
|
||||
array $plan
|
||||
@@ -93,14 +97,96 @@ class Certificates extends Action
|
||||
|
||||
$document = new Document($payload['domain'] ?? []);
|
||||
$domain = new Domain($document->getAttribute('domain', ''));
|
||||
$domainType = $document->getAttribute('domainType');
|
||||
$skipRenewCheck = $payload['skipRenewCheck'] ?? false;
|
||||
$validationDomain = $payload['validationDomain'] ?? null;
|
||||
$action = $payload['action'] ?? Certificate::ACTION_GENERATION;
|
||||
|
||||
$log->addTag('domain', $domain->get());
|
||||
|
||||
$domainType = $document->getAttribute('domainType');
|
||||
switch ($action) {
|
||||
case Certificate::ACTION_DOMAIN_VERIFICATION:
|
||||
$this->handleDomainVerificationAction($domain, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $queueForCertificates, $log, $validationDomain);
|
||||
break;
|
||||
|
||||
$this->execute($domain, $domainType, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck, $plan, $validationDomain);
|
||||
case Certificate::ACTION_GENERATION:
|
||||
$this->handleCertificateGenerationAction($domain, $domainType, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $skipRenewCheck, $plan, $validationDomain);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Invalid action: ' . $action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Domain $domain
|
||||
* @param Database $dbForPlatform
|
||||
* @param Event $queueForEvents
|
||||
* @param Webhook $queueForWebhooks
|
||||
* @param Func $queueForFunctions
|
||||
* @param Realtime $queueForRealtime
|
||||
* @param Certificate $queueForCertificates
|
||||
* @param Log $log
|
||||
* @param string|null $validationDomain
|
||||
* @return void
|
||||
* @throws Throwable
|
||||
* @throws \Utopia\Database\Exception
|
||||
*/
|
||||
private function handleDomainVerificationAction(
|
||||
Domain $domain,
|
||||
Database $dbForPlatform,
|
||||
Event $queueForEvents,
|
||||
Webhook $queueForWebhooks,
|
||||
Func $queueForFunctions,
|
||||
Realtime $queueForRealtime,
|
||||
Certificate $queueForCertificates,
|
||||
Log $log,
|
||||
?string $validationDomain = null
|
||||
): void {
|
||||
// Get rule
|
||||
$rule = System::getEnv('_APP_RULES_FORMAT') === 'md5'
|
||||
? ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($domain->get())))
|
||||
: ValidatorAuthorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain->get()]),
|
||||
Query::limit(1),
|
||||
]));
|
||||
|
||||
// Skip if rule is not desired state (created but not verified yet).
|
||||
if ($rule->getAttribute('status', '') !== RULE_STATUS_CREATED) {
|
||||
Console::warning('Domain verification for ' . $rule->getAttribute('domain', '') . ' is not needed.');
|
||||
return;
|
||||
}
|
||||
|
||||
Console::info('Domain verification for ' . $rule->getAttribute('domain', '') . ' started.');
|
||||
|
||||
try {
|
||||
// Verify DNS records
|
||||
$this->validateDomain($rule, $domain, $log, $validationDomain);
|
||||
// Reset logs and status for the rule
|
||||
$rule->setAttribute('logs', '');
|
||||
$rule->setAttribute('status', RULE_STATUS_CERTIFICATE_GENERATING);
|
||||
|
||||
Console::success('Domain verification succeeded.');
|
||||
} catch (AppwriteException $err) {
|
||||
Console::warning('Domain verification failed: ' . $err->getMessage());
|
||||
$rule->setAttribute('logs', $err->getMessage());
|
||||
} finally {
|
||||
// Update rule and emit events
|
||||
$this->updateRuleAndSendEvents($rule, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
|
||||
}
|
||||
|
||||
// Issue a TLS certificate when domain is verified
|
||||
if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
|
||||
Console::success('Certificate generation triggered successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +205,7 @@ class Certificates extends Action
|
||||
* @throws Throwable
|
||||
* @throws \Utopia\Database\Exception
|
||||
*/
|
||||
private function execute(
|
||||
private function handleCertificateGenerationAction(
|
||||
Domain $domain,
|
||||
?string $domainType,
|
||||
Database $dbForPlatform,
|
||||
@@ -163,26 +249,42 @@ class Certificates extends Action
|
||||
* Note: Renewals are checked and scheduled from maintenance worker
|
||||
*/
|
||||
|
||||
// Get current certificate
|
||||
$certificate = $dbForPlatform->findOne('certificates', [Query::equal('domain', [$domain->get()])]);
|
||||
// Get rule document for domain
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$rule = System::getEnv('_APP_RULES_FORMAT') === 'md5'
|
||||
? ValidatorAuthorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($domain->get())))
|
||||
: ValidatorAuthorization::skip(fn () => $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain->get()]),
|
||||
Query::limit(1),
|
||||
]));
|
||||
|
||||
// If we don't have certificate for domain yet, let's create new document. At the end we save it
|
||||
// Rule not found (or) not in the expected state
|
||||
if ($rule->isEmpty() || $rule->getAttribute('status') !== RULE_STATUS_CERTIFICATE_GENERATING) {
|
||||
Console::warning('Certificate generation for ' . $domain->get() . ' is skipped as the associated rule is either empty or not in the expected state.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get associated certificate for the rule
|
||||
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId') ?? '');
|
||||
|
||||
// If we don't have certificate for the rule yet, let's create one.
|
||||
if ($certificate->isEmpty()) {
|
||||
$certificate = new Document();
|
||||
$certificate->setAttribute('domain', $domain->get());
|
||||
}
|
||||
|
||||
$success = false;
|
||||
|
||||
try {
|
||||
$date = \date('H:i:s');
|
||||
$certificate->setAttribute('logs', "\033[90m[{$date}] \033[97mCertificate generation started. \033[0m\n");
|
||||
|
||||
// Persist ASAP so that logs are reset in retry flow and user can see the latest logs on Console.
|
||||
$certificate = $this->upsertCertificate($rule, $certificate, $dbForPlatform);
|
||||
// Ensure certificate is associated with the rule
|
||||
$rule->setAttribute('certificateId', $certificate->getId());
|
||||
|
||||
// Validate domain and DNS records. Skip if job is forced
|
||||
if (!$skipRenewCheck) {
|
||||
$mainDomain = $validationDomain ?? $this->getMainDomain();
|
||||
$isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain;
|
||||
$this->validateDomain($domain, $isMainDomain, $log);
|
||||
$this->validateDomain($rule, $domain, $log, $validationDomain);
|
||||
|
||||
// If certificate exists already, double-check expiry date. Skip if job is forced
|
||||
if (!$certificates->isRenewRequired($domain->get(), $domainType, $log)) {
|
||||
@@ -191,85 +293,171 @@ class Certificates extends Action
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare unique cert name. Using this helps prevent miss-match in configuration when renewing certificates.
|
||||
// Prepare unique cert name. Using this helps prevent mismatch in configuration when renewing certificates.
|
||||
$certName = ID::unique();
|
||||
$renewDate = $certificates->issueCertificate($certName, $domain->get(), $domainType);
|
||||
|
||||
// Command succeeded, store all data into document
|
||||
$certificate->setAttribute('logs', 'Certificate successfully generated.');
|
||||
// If certificate is generated instantly, we can mark the rule as 'verified'.
|
||||
if ($certificates->isInstantGeneration($domain->get(), $domainType)) {
|
||||
$rule->setAttribute('status', RULE_STATUS_VERIFIED);
|
||||
$certificate->setAttribute('logs', 'Certificate successfully generated.');
|
||||
}
|
||||
|
||||
// Update certificate info stored in database
|
||||
$certificate->setAttribute('renewDate', $renewDate);
|
||||
$certificate->setAttribute('attempts', 0);
|
||||
$certificate->setAttribute('issueDate', DateTime::now());
|
||||
$success = true;
|
||||
$certificate->setAttributes([
|
||||
'attempts' => 0, // Reset attempts count
|
||||
'issueDate' => DateTime::now(), // Store current time as issue date
|
||||
'renewDate' => $renewDate,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
$logs = $e->getMessage();
|
||||
$currentLogs = $certificate->getAttribute('logs', '');
|
||||
$date = \date('H:i:s');
|
||||
$errorMessage = "\033[90m[{$date}] \033[31mCertificate generation failed: \033[0m\n";
|
||||
|
||||
$certificate->setAttribute('logs', $currentLogs . $errorMessage . \mb_strcut($logs, 0, 500000));// Limit to 500kb
|
||||
$attempts = $certificate->getAttribute('attempts', 0) + 1; // Increase attempts count
|
||||
|
||||
// Increase attempts count
|
||||
$attempts = $certificate->getAttribute('attempts', 0) + 1;
|
||||
$certificate->setAttribute('attempts', $attempts);
|
||||
// Update attributes on certificate document
|
||||
$certificate->setAttributes([
|
||||
'logs' => $currentLogs . $errorMessage . \mb_strcut($logs, 0, 500000), // Limit to 500kb
|
||||
'attempts' => $attempts,
|
||||
'renewDate' => DateTime::now(), // Store current time as renew date to ensure another attempt in next maintenance cycle.
|
||||
]);
|
||||
|
||||
// Store current time as renew date to ensure another attempt in next maintenance cycle.
|
||||
$certificate->setAttribute('renewDate', DateTime::now());
|
||||
// Mark rule as 'unverified'
|
||||
$rule->setAttribute('status', RULE_STATUS_CERTIFICATE_GENERATION_FAILED);
|
||||
|
||||
// Send email to security email
|
||||
$this->notifyError($domain->get(), $e->getMessage(), $attempts, $queueForMails, $plan);
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
// All actions result in new updatedAt date
|
||||
// All actions result in new 'updated' date
|
||||
$certificate->setAttribute('updated', DateTime::now());
|
||||
// Save certificate document to database
|
||||
$this->upsertCertificate($rule, $certificate, $dbForPlatform);
|
||||
|
||||
// Save all changes we made to certificate document into database
|
||||
$this->saveCertificateDocument($domain->get(), $certificate, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
|
||||
// Ensure certificate is associated with the rule
|
||||
$rule->setAttribute('certificateId', $certificate->getId());
|
||||
// Update rule and emit events
|
||||
$this->updateRuleAndSendEvents($rule, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save certificate data into database.
|
||||
* Save certificate data to database.
|
||||
*
|
||||
* @param string $domain Domain name that certificate is for
|
||||
* @param Document $rule Rule associated with the domain
|
||||
* @param Document $certificate Certificate document that we need to save
|
||||
* @param bool $success
|
||||
* @param Database $dbForPlatform Database connection for console
|
||||
* @param Event $queueForEvents
|
||||
* @param Func $queueForFunctions
|
||||
* @param Realtime $queueForRealtime
|
||||
* @return void
|
||||
* @return Document
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws Authorization
|
||||
* @throws Conflict
|
||||
* @throws Structure
|
||||
*/
|
||||
private function saveCertificateDocument(
|
||||
string $domain,
|
||||
private function upsertCertificate(
|
||||
Document $rule,
|
||||
Document $certificate,
|
||||
bool $success,
|
||||
Database $dbForPlatform,
|
||||
): Document {
|
||||
// Decide whether update (or) insert is needed
|
||||
$existingCertificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId') ?? '');
|
||||
|
||||
if ($existingCertificate->isEmpty()) {
|
||||
$certificate->removeAttribute('$sequence');
|
||||
$certificate = $dbForPlatform->createDocument('certificates', $certificate);
|
||||
} else {
|
||||
$certificate = new Document(\array_merge($existingCertificate->getArrayCopy(), $certificate->getArrayCopy()));
|
||||
$certificate = $dbForPlatform->updateDocument('certificates', $certificate->getId(), $certificate);
|
||||
}
|
||||
|
||||
return $certificate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all existing domain documents so they have relation to correct certificate document.
|
||||
* This solves issues:
|
||||
* - when adding a domain for which there is already a certificate
|
||||
* - when renew creates new document? It might?
|
||||
* - overall makes it more reliable
|
||||
*
|
||||
* @param Document $rule Rule document that is affected by new certificate
|
||||
* @param Database $dbForPlatform Database connection for console
|
||||
* @param Event $queueForEvents Event publisher for events
|
||||
* @param Webhook $queueForWebhooks Webhook publisher for webhooks
|
||||
* @param Func $queueForFunctions Function publisher for functions
|
||||
* @param Realtime $queueForRealtime Realtime publisher for realtime events
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function updateRuleAndSendEvents(
|
||||
Document $rule,
|
||||
Database $dbForPlatform,
|
||||
Event $queueForEvents,
|
||||
Webhook $queueForWebhooks,
|
||||
Func $queueForFunctions,
|
||||
Realtime $queueForRealtime
|
||||
): void {
|
||||
// Check if update or insert required
|
||||
$certificateDocument = $dbForPlatform->findOne('certificates', [Query::equal('domain', [$domain])]);
|
||||
if (!$certificateDocument->isEmpty()) {
|
||||
// Merge new data with current data
|
||||
$certificate = new Document(\array_merge($certificateDocument->getArrayCopy(), $certificate->getArrayCopy()));
|
||||
$certificate = $dbForPlatform->updateDocument('certificates', $certificate->getId(), $certificate);
|
||||
} else {
|
||||
$certificate->removeAttribute('$sequence');
|
||||
$certificate = $dbForPlatform->createDocument('certificates', $certificate);
|
||||
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
|
||||
// Skip events for console project (triggered by auto-ssl generation for 1 click setups)
|
||||
if ($projectId === 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
$certificateId = $certificate->getId();
|
||||
$this->updateDomainDocuments($certificateId, $domain, $success, $dbForPlatform, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime);
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
if ($project->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ruleModel = new Rule();
|
||||
$queueForEvents
|
||||
->setProject($project)
|
||||
->setEvent('rules.[ruleId].update')
|
||||
->setParam('ruleId', $rule->getId())
|
||||
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
|
||||
|
||||
/** Trigger Webhook */
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
/** Trigger Functions */
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
/** Trigger Realtime Events */
|
||||
$queueForRealtime
|
||||
->setSubscribers(['console', $projectId])
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal domain validation functionality to prevent unnecessary attempts. We check:
|
||||
* - Domain needs to be public and valid (prevents NFT domains that are not supported)
|
||||
* - Domain must have proper DNS record
|
||||
*
|
||||
* @param Document $rule Rule to validate
|
||||
* @param Domain $domain Domain to validate
|
||||
* @param Log $log Logger for adding metrics
|
||||
* @param string|null $validationDomain Override for main domain check
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateDomain(Document $rule, Domain $domain, Log $log, ?string $validationDomain = null): void
|
||||
{
|
||||
$mainDomain = $validationDomain ?? $this->getMainDomain();
|
||||
$isMainDomain = !isset($mainDomain) || $domain->get() === $mainDomain;
|
||||
if (!$isMainDomain) {
|
||||
$this->verifyRule($rule, $log);
|
||||
} else {
|
||||
// Main domain validation
|
||||
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,74 +476,7 @@ class Certificates extends Action
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal domain validation functionality to prevent unnecessary attempts. We check:
|
||||
* - Domain needs to be public and valid (prevents NFT domains that are not supported)
|
||||
* - Domain must have proper DNS record
|
||||
*
|
||||
* @param Domain $domain Domain which we validate
|
||||
* @param bool $isMainDomain In case of master domain, we look for different DNS configurations
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateDomain(Domain $domain, bool $isMainDomain, Log $log): void
|
||||
{
|
||||
if (empty($domain->get())) {
|
||||
throw new Exception('Missing certificate domain.');
|
||||
}
|
||||
|
||||
if (!$domain->isKnown() || $domain->isTest()) {
|
||||
throw new Exception('Unknown public suffix for domain.');
|
||||
}
|
||||
|
||||
if (!$isMainDomain) {
|
||||
$validationStart = \microtime(true);
|
||||
|
||||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), Record::TYPE_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_AAAA);
|
||||
}
|
||||
|
||||
// Validate if domain target is properly configured
|
||||
if (empty($validators)) {
|
||||
throw new Exception('At least one of domain targets environment variable must be configured.');
|
||||
}
|
||||
|
||||
// Verify domain with DNS records
|
||||
$validator = new AnyOf($validators, AnyOf::TYPE_STRING);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
throw new Exception('Failed to verify domain DNS records.');
|
||||
}
|
||||
|
||||
// Ensure CAA won't block certificate issuance
|
||||
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
|
||||
$validationStart = \microtime(true);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), Record::TYPE_CAA);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
$error = $validator->getDescription();
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
throw new Exception('Failed to verify domain DNS records. CAA records do not allow Appwrite\'s certificate issuer.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Main domain validation
|
||||
// TODO: Would be awesome to check A/AAAA record here. Maybe dry run?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to make sure information about error is delivered to admnistrator.
|
||||
* Method to make sure information about error is delivered to administrator.
|
||||
*
|
||||
* @param string $domain Domain that caused the error
|
||||
* @param string $errorMessage Verbose error message
|
||||
@@ -406,78 +527,4 @@ class Certificates extends Action
|
||||
->setRecipient(System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all existing domain documents so they have relation to correct certificate document.
|
||||
* This solved issues:
|
||||
* - when adding a domain for which there is already a certificate
|
||||
* - when renew creates new document? It might?
|
||||
* - overall makes it more reliable
|
||||
*
|
||||
* @param string $certificateId ID of a new or updated certificate document
|
||||
* @param string $domain Domain that is affected by new certificate
|
||||
* @param bool $success Was certificate generation successful?
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function updateDomainDocuments(
|
||||
string $certificateId,
|
||||
string $domain,
|
||||
bool $success,
|
||||
Database $dbForPlatform,
|
||||
Event $queueForEvents,
|
||||
Webhook $queueForWebhooks,
|
||||
Func $queueForFunctions,
|
||||
Realtime $queueForRealtime
|
||||
): void {
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$rule = $isMd5
|
||||
? $dbForPlatform->getDocument('rules', md5($domain))
|
||||
: $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
]);
|
||||
|
||||
if (!$rule->isEmpty()) {
|
||||
$rule->setAttribute('certificateId', $certificateId);
|
||||
$rule->setAttribute('status', $success ? 'verified' : 'unverified');
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
|
||||
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
|
||||
// Skip events for console project (triggered by auto-ssl generation for 1 click setups)
|
||||
if ($projectId === 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ruleModel = new Rule();
|
||||
$queueForEvents
|
||||
->setProject($project)
|
||||
->setEvent('rules.[ruleId].update')
|
||||
->setParam('ruleId', $rule->getId())
|
||||
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
|
||||
|
||||
/** Trigger Webhook */
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
/** Trigger Functions */
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
/** Trigger Realtime Events */
|
||||
$queueForRealtime
|
||||
->from($queueForEvents)
|
||||
->setSubscribers(['console', $projectId])
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,14 +122,16 @@ class Functions extends Action
|
||||
$log->addTag('type', $type);
|
||||
|
||||
if (!empty($events)) {
|
||||
$limit = 30;
|
||||
$sum = 30;
|
||||
$limit = 100;
|
||||
$sum = 100;
|
||||
$offset = 0;
|
||||
while ($sum >= $limit) {
|
||||
$functions = $dbForProject->find('functions', [
|
||||
Query::select(['$id', 'events']), // Skip variables subqueries
|
||||
Query::contains('events', $events),
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
Query::orderAsc('name'),
|
||||
Query::orderAsc('$sequence'),
|
||||
]);
|
||||
|
||||
$sum = \count($functions);
|
||||
@@ -147,6 +149,11 @@ class Functions extends Action
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* get variables subqueries cached
|
||||
*/
|
||||
$function = $dbForProject->getDocument('functions', $function->getId());
|
||||
|
||||
Console::success('Iterating function: ' . $function->getAttribute('name'));
|
||||
|
||||
$this->execute(
|
||||
|
||||
@@ -192,13 +192,13 @@ class StatsResources extends Action
|
||||
}
|
||||
|
||||
try {
|
||||
$this->countForDatabase($dbForProject, $region);
|
||||
$dbForProject->skipFilters(fn () => $this->countForDatabase($dbForProject, $region), ['subQueryAttributes', 'subQueryIndexes']);
|
||||
} catch (Throwable $th) {
|
||||
call_user_func_array($this->logError, [$th, "StatsResources", "count_for_database_{$project->getId()}"]);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->countForSitesAndFunctions($dbForProject, $region);
|
||||
$dbForProject->skipFilters(fn () => $this->countForSitesAndFunctions($dbForProject, $region), ['subQueryVariables', 'subQueryProjectVariables']);
|
||||
} catch (Throwable $th) {
|
||||
call_user_func_array($this->logError, [$th, "StatsResources", "count_for_functions_{$project->getId()}"]);
|
||||
}
|
||||
|
||||
@@ -204,6 +204,8 @@ abstract class Format
|
||||
switch ($param) {
|
||||
case 'permissions':
|
||||
return 'BrowserPermission';
|
||||
case 'output':
|
||||
return 'ImageFormat';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -17,4 +17,9 @@ class Projects extends Base
|
||||
{
|
||||
parent::__construct('projects', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
|
||||
public function isSelectQueryAllowed(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +341,20 @@ class Project extends Model
|
||||
*/
|
||||
public function filter(Document $document): Document
|
||||
{
|
||||
$this->expandSmtpFields($document);
|
||||
$this->expandServiceFields($document);
|
||||
$this->expandAuthFields($document);
|
||||
$this->expandOAuthProviders($document);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
private function expandSmtpFields(Document $document): void
|
||||
{
|
||||
if (!$document->isSet('smtp')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SMTP
|
||||
$smtp = $document->getAttribute('smtp', []);
|
||||
$document->setAttribute('smtpEnabled', $smtp['enabled'] ?? false);
|
||||
@@ -352,8 +366,14 @@ class Project extends Model
|
||||
$document->setAttribute('smtpUsername', $smtp['username'] ?? '');
|
||||
$document->setAttribute('smtpPassword', $smtp['password'] ?? '');
|
||||
$document->setAttribute('smtpSecure', $smtp['secure'] ?? '');
|
||||
}
|
||||
|
||||
private function expandServiceFields(Document $document): void
|
||||
{
|
||||
if (!$document->isSet('services')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Services
|
||||
$values = $document->getAttribute('services', []);
|
||||
$services = Config::getParam('services', []);
|
||||
|
||||
@@ -365,8 +385,14 @@ class Project extends Model
|
||||
$value = $values[$key] ?? true;
|
||||
$document->setAttribute('serviceStatusFor' . ucfirst($key), $value);
|
||||
}
|
||||
}
|
||||
|
||||
private function expandAuthFields(Document $document): void
|
||||
{
|
||||
if (!$document->isSet('auths')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Auth
|
||||
$authValues = $document->getAttribute('auths', []);
|
||||
$auth = Config::getParam('auth', []);
|
||||
|
||||
@@ -383,13 +409,19 @@ class Project extends Model
|
||||
$document->setAttribute('authMembershipsMfa', $authValues['membershipsMfa'] ?? true);
|
||||
$document->setAttribute('authInvalidateSessions', $authValues['invalidateSessions'] ?? false);
|
||||
|
||||
foreach ($auth as $index => $method) {
|
||||
foreach ($auth as $method) {
|
||||
$key = $method['key'];
|
||||
$value = $authValues[$key] ?? true;
|
||||
$document->setAttribute('auth' . ucfirst($key), $value);
|
||||
}
|
||||
}
|
||||
|
||||
private function expandOAuthProviders(Document $document): void
|
||||
{
|
||||
if (!$document->isSet('oAuthProviders')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// OAuth Providers
|
||||
$providers = Config::getParam('oAuthProviders', []);
|
||||
$providerValues = $document->getAttribute('oAuthProviders', []);
|
||||
$projectProviders = [];
|
||||
@@ -410,7 +442,5 @@ class Project extends Model
|
||||
}
|
||||
|
||||
$document->setAttribute('oAuthProviders', $projectProviders);
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class HTTPTest extends Scope
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
$this->assertEquals('Appwrite', $response['headers']['server']);
|
||||
$this->assertEquals('GET, POST, PUT, PATCH, DELETE', $response['headers']['access-control-allow-methods']);
|
||||
$this->assertEquals('Accept, Origin, Cookie, Set-Cookie, Content-Type, Content-Range, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-ID, X-Appwrite-Timestamp, X-Appwrite-Session, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-SDK-Profile, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies, X-Requested-With, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']);
|
||||
$this->assertEquals('Accept, Origin, Cookie, Set-Cookie, Content-Type, Content-Range, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-ID, X-Appwrite-Timestamp, X-Appwrite-Session, X-Appwrite-Platform, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-SDK-Profile, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies, X-Requested-With, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']);
|
||||
$this->assertEquals('X-Appwrite-Session, X-Fallback-Cookies', $response['headers']['access-control-expose-headers']);
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
$this->assertEquals('true', $response['headers']['access-control-allow-credentials']);
|
||||
|
||||
@@ -161,9 +161,9 @@ trait ProjectCustom
|
||||
'senderEmail' => 'mailer@appwrite.io',
|
||||
'senderName' => 'Mailer',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'port' => intval(System::getEnv('_APP_SMTP_PORT', "1025")),
|
||||
'username' => System::getEnv('_APP_SMTP_USERNAME', 'user'),
|
||||
'password' => System::getEnv('_APP_SMTP_PASSWORD', 'password'),
|
||||
]);
|
||||
|
||||
$project = [
|
||||
|
||||
@@ -1845,7 +1845,7 @@ class AccountCustomClientTest extends Scope
|
||||
]));
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals(99, $response['headers']['x-ratelimit-remaining']);
|
||||
$this->assertEquals(119, $response['headers']['x-ratelimit-remaining']);
|
||||
$this->assertNotEmpty($response['body']['jwt']);
|
||||
$this->assertIsString($response['body']['jwt']);
|
||||
|
||||
@@ -1887,6 +1887,57 @@ class AccountCustomClientTest extends Scope
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
// Test JWT with custom duration
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/jwt', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]), [
|
||||
'duration' => 5
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['jwt']);
|
||||
|
||||
$jwt = $response['body']['jwt'];
|
||||
|
||||
// Ensure JWT works before expiration
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-jwt' => $jwt,
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
// Wait for JWT to expire
|
||||
\sleep(6);
|
||||
|
||||
// Ensure JWT no longer works after expiration
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-jwt' => $jwt,
|
||||
]));
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -2183,6 +2234,157 @@ class AccountCustomClientTest extends Scope
|
||||
$this->assertEquals('tuvwxyz', $response['body']['providerRefreshToken']);
|
||||
$this->assertNotEquals($initialExpiry, $response['body']['providerAccessTokenExpiry']);
|
||||
|
||||
// Clean up - delete the user
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function testOAuthUnverifiedEmailCannotLinkToExistingAccount()
|
||||
{
|
||||
$provider = 'mock-unverified';
|
||||
$appId = '1';
|
||||
$secret = '123456';
|
||||
|
||||
// First, create a user with the same email that the unverified OAuth will try to use
|
||||
$email = 'useroauthunverified@localhost.test';
|
||||
$password = 'password';
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$existingUserId = $response['body']['$id'];
|
||||
|
||||
// Enable the mock-unverified provider
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'provider' => $provider,
|
||||
'appId' => $appId,
|
||||
'secret' => $secret,
|
||||
'enabled' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
// Attempt OAuth login with unverified email - should fail because existing user has same email
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
||||
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('failure', $response['body']['result']);
|
||||
|
||||
// Clean up - delete the user
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $existingUserId, array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function testOAuthVerifiedEmailCanLinkToExistingAccount()
|
||||
{
|
||||
$provider = 'mock';
|
||||
$appId = '1';
|
||||
$secret = '123456';
|
||||
$email = 'useroauth@localhost.test';
|
||||
|
||||
// Create a user with the same email that the verified OAuth will try to use
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$existingUserId = $response['body']['$id'];
|
||||
|
||||
// Enable the mock provider
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/oauth2', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'provider' => $provider,
|
||||
'appId' => $appId,
|
||||
'secret' => $secret,
|
||||
'enabled' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
// Attempt OAuth login with verified email - should succeed and link to existing account
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider, array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success',
|
||||
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('success', $response['body']['result']);
|
||||
|
||||
// Verify the OAuth identity was linked to the existing user
|
||||
$sessionCookieKey = 'a_session_' . $this->getProject()['$id'];
|
||||
$session = $response['cookies'][$sessionCookieKey];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($existingUserId, $response['body']['$id']);
|
||||
$this->assertEquals($email, $response['body']['email']);
|
||||
|
||||
// Clean up - delete the user
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $existingUserId, array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]));
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ class ProjectsConsoleClientTest extends Scope
|
||||
use Async;
|
||||
|
||||
/**
|
||||
* @group devKeys
|
||||
* @group smtpAndTemplates
|
||||
* @group projectsCRUD */
|
||||
* @group projectsCRUD
|
||||
*/
|
||||
public function testCreateProject(): array
|
||||
{
|
||||
/**
|
||||
@@ -257,11 +257,11 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'search' => $id
|
||||
]));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertEquals($response['body']['total'], 3);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(4, $response['body']['total']);
|
||||
$this->assertIsArray($response['body']['projects']);
|
||||
$this->assertCount(3, $response['body']['projects']);
|
||||
$this->assertEquals($response['body']['projects'][0]['name'], 'Project Test');
|
||||
$this->assertCount(4, $response['body']['projects']);
|
||||
$this->assertEquals('Project Test', $response['body']['projects'][0]['name']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -271,9 +271,9 @@ class ProjectsConsoleClientTest extends Scope
|
||||
]));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
$this->assertEquals(3, $response['body']['total']);
|
||||
$this->assertEquals(4, $response['body']['total']);
|
||||
$this->assertIsArray($response['body']['projects']);
|
||||
$this->assertCount(3, $response['body']['projects']);
|
||||
$this->assertCount(4, $response['body']['projects']);
|
||||
$this->assertEquals($response['body']['projects'][0]['$id'], $data['projectId']);
|
||||
|
||||
/**
|
||||
@@ -348,8 +348,8 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertCount(1, $response['body']['projects']);
|
||||
$this->assertEquals('Project Test 2', $response['body']['projects'][0]['name']);
|
||||
$this->assertCount(2, $response['body']['projects']);
|
||||
$this->assertEquals('Team 1 Project', $response['body']['projects'][0]['name']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -376,7 +376,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertCount(4, $response['body']['projects']);
|
||||
$this->assertCount(5, $response['body']['projects']);
|
||||
$this->assertEquals('Project Test 2', $response['body']['projects'][0]['name']);
|
||||
$this->assertEquals('Team 1 Project', $response['body']['projects'][1]['name']);
|
||||
|
||||
@@ -387,9 +387,9 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertCount(4, $response['body']['projects']);
|
||||
$this->assertCount(5, $response['body']['projects']);
|
||||
$this->assertEquals('Project Test', $response['body']['projects'][0]['name']);
|
||||
$this->assertEquals('Team 1 Project', $response['body']['projects'][2]['name']);
|
||||
$this->assertEquals('Original Project', $response['body']['projects'][2]['name']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -402,8 +402,8 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertCount(3, $response['body']['projects']);
|
||||
$this->assertEquals('Team 1 Project', $response['body']['projects'][1]['name']);
|
||||
$this->assertCount(4, $response['body']['projects']);
|
||||
$this->assertEquals('Original Project', $response['body']['projects'][1]['name']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -436,6 +436,267 @@ class ProjectsConsoleClientTest extends Scope
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @group projectsCRUD
|
||||
*/
|
||||
public function testListProjectsQuerySelect(): void
|
||||
{
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Query Select Test Team',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $team['headers']['status-code']);
|
||||
$teamId = $team['body']['$id'];
|
||||
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Query Select Test Project',
|
||||
'teamId' => $teamId,
|
||||
'region' => System::getEnv('_APP_REGION', 'default')
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $project['headers']['status-code']);
|
||||
$projectId = $project['body']['$id'];
|
||||
|
||||
/**
|
||||
* Test Query.select - basic fields
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayNotHasKey('platforms', $project);
|
||||
$this->assertArrayNotHasKey('webhooks', $project);
|
||||
$this->assertArrayNotHasKey('keys', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('smtpHost', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
$this->assertArrayNotHasKey('authDuration', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select - multiple fields
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name', 'teamId', 'description', '$createdAt', '$updatedAt'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('teamId', $project);
|
||||
$this->assertArrayHasKey('description', $project);
|
||||
$this->assertArrayHasKey('$createdAt', $project);
|
||||
$this->assertArrayHasKey('$updatedAt', $project);
|
||||
$this->assertArrayNotHasKey('platforms', $project);
|
||||
$this->assertArrayNotHasKey('webhooks', $project);
|
||||
$this->assertArrayNotHasKey('keys', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select combined with filters
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name', 'teamId'])->toString(),
|
||||
Query::equal('name', ['Query Select Test Project'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertCount(1, $response['body']['projects']);
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('teamId', $project);
|
||||
$this->assertEquals('Query Select Test Project', $project['name']);
|
||||
$this->assertEquals($teamId, $project['teamId']);
|
||||
$this->assertArrayNotHasKey('platforms', $project);
|
||||
$this->assertArrayNotHasKey('webhooks', $project);
|
||||
$this->assertArrayNotHasKey('keys', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select combined with limit
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name'])->toString(),
|
||||
Query::limit(2)->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertLessThanOrEqual(2, count($response['body']['projects']));
|
||||
|
||||
foreach ($response['body']['projects'] as $p) {
|
||||
$this->assertArrayHasKey('$id', $p);
|
||||
$this->assertArrayHasKey('name', $p);
|
||||
$this->assertArrayNotHasKey('platforms', $p);
|
||||
$this->assertArrayNotHasKey('webhooks', $p);
|
||||
$this->assertArrayNotHasKey('keys', $p);
|
||||
$this->assertArrayNotHasKey('devKeys', $p);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $p);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $p);
|
||||
$this->assertArrayNotHasKey('authLimit', $p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Query.select with subquery attributes (platforms, webhooks, etc.)
|
||||
* When explicitly selected, subqueries should still run
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name', 'platforms'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('platforms', $project);
|
||||
$this->assertIsArray($project['platforms']);
|
||||
$this->assertArrayNotHasKey('webhooks', $project);
|
||||
$this->assertArrayNotHasKey('keys', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select with expanded attributes
|
||||
* webhooks and keys should load their subquery data when selected
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'name', 'webhooks', 'keys'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('webhooks', $project);
|
||||
$this->assertArrayHasKey('keys', $project);
|
||||
$this->assertIsArray($project['webhooks']);
|
||||
$this->assertIsArray($project['keys']);
|
||||
$this->assertArrayNotHasKey('platforms', $project);
|
||||
$this->assertArrayNotHasKey('devKeys', $project);
|
||||
$this->assertArrayNotHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayNotHasKey('authLimit', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select with wildcard '*'
|
||||
* Should return all fields like no select query
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['*'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertGreaterThan(0, count($response['body']['projects']));
|
||||
|
||||
$project = $response['body']['projects'][0];
|
||||
$this->assertArrayHasKey('$id', $project);
|
||||
$this->assertArrayHasKey('name', $project);
|
||||
$this->assertArrayHasKey('teamId', $project);
|
||||
$this->assertArrayHasKey('platforms', $project);
|
||||
$this->assertArrayHasKey('webhooks', $project);
|
||||
$this->assertArrayHasKey('keys', $project);
|
||||
$this->assertArrayHasKey('devKeys', $project);
|
||||
$this->assertArrayHasKey('oAuthProviders', $project);
|
||||
$this->assertArrayHasKey('smtpEnabled', $project);
|
||||
$this->assertArrayHasKey('smtpHost', $project);
|
||||
$this->assertArrayHasKey('authLimit', $project);
|
||||
$this->assertArrayHasKey('authDuration', $project);
|
||||
|
||||
/**
|
||||
* Test Query.select with invalid attribute
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::select(['$id', 'invalidAttribute'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('Invalid `queries` param: Invalid query: Attribute not found in schema: invalidAttribute', $response['body']['message']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testGetProject(): void
|
||||
{
|
||||
// Create a team
|
||||
@@ -618,6 +879,14 @@ class ProjectsConsoleClientTest extends Scope
|
||||
public function testUpdateProjectSMTP($data): array
|
||||
{
|
||||
$id = $data['projectId'];
|
||||
$smtpHost = System::getEnv('_APP_SMTP_HOST', "maildev");
|
||||
$smtpPort = intval(System::getEnv('_APP_SMTP_PORT', "1025"));
|
||||
$smtpUsername = System::getEnv('_APP_SMTP_USERNAME', 'user');
|
||||
$smtpPassword = System::getEnv('_APP_SMTP_PASSWORD', 'password');
|
||||
|
||||
/**
|
||||
* Test for SUCCESS: Valid Credentials
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/smtp', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
@@ -625,23 +894,23 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'enabled' => true,
|
||||
'senderEmail' => 'mailer@appwrite.io',
|
||||
'senderName' => 'Mailer',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => 'user',
|
||||
'password' => 'password',
|
||||
'host' => $smtpHost,
|
||||
'port' => $smtpPort,
|
||||
'username' => $smtpUsername,
|
||||
'password' => $smtpPassword,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertTrue($response['body']['smtpEnabled']);
|
||||
$this->assertEquals('mailer@appwrite.io', $response['body']['smtpSenderEmail']);
|
||||
$this->assertEquals('Mailer', $response['body']['smtpSenderName']);
|
||||
$this->assertEquals('maildev', $response['body']['smtpHost']);
|
||||
$this->assertEquals(1025, $response['body']['smtpPort']);
|
||||
$this->assertEquals('user', $response['body']['smtpUsername']);
|
||||
$this->assertEquals('password', $response['body']['smtpPassword']);
|
||||
$this->assertEquals($smtpHost, $response['body']['smtpHost']);
|
||||
$this->assertEquals($smtpPort, $response['body']['smtpPort']);
|
||||
$this->assertEquals($smtpUsername, $response['body']['smtpUsername']);
|
||||
$this->assertEquals($smtpPassword, $response['body']['smtpPassword']);
|
||||
$this->assertEquals('', $response['body']['smtpSecure']);
|
||||
|
||||
/** Test Reading Project */
|
||||
// Check the project
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
@@ -651,12 +920,32 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertTrue($response['body']['smtpEnabled']);
|
||||
$this->assertEquals('mailer@appwrite.io', $response['body']['smtpSenderEmail']);
|
||||
$this->assertEquals('Mailer', $response['body']['smtpSenderName']);
|
||||
$this->assertEquals('maildev', $response['body']['smtpHost']);
|
||||
$this->assertEquals(1025, $response['body']['smtpPort']);
|
||||
$this->assertEquals('user', $response['body']['smtpUsername']);
|
||||
$this->assertEquals('password', $response['body']['smtpPassword']);
|
||||
$this->assertEquals($smtpHost, $response['body']['smtpHost']);
|
||||
$this->assertEquals($smtpPort, $response['body']['smtpPort']);
|
||||
$this->assertEquals($smtpUsername, $response['body']['smtpUsername']);
|
||||
$this->assertEquals($smtpPassword, $response['body']['smtpPassword']);
|
||||
$this->assertEquals('', $response['body']['smtpSecure']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE: Invalid Credentials
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/smtp', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'enabled' => true,
|
||||
'senderEmail' => 'fail@appwrite.io',
|
||||
'senderName' => 'Failing Mailer',
|
||||
'host' => $smtpHost,
|
||||
'port' => $smtpPort,
|
||||
'username' => 'invalid-user',
|
||||
'password' => 'bad-password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals(Exception::PROJECT_SMTP_CONFIG_INVALID, $response['body']['type']);
|
||||
$this->assertStringContainsStringIgnoringCase('Could not authenticate', $response['body']['message']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -665,6 +954,11 @@ class ProjectsConsoleClientTest extends Scope
|
||||
*/
|
||||
public function testCreateProjectSMTPTests(): void
|
||||
{
|
||||
$smtpHost = System::getEnv('_APP_SMTP_HOST', "maildev");
|
||||
$smtpPort = intval(System::getEnv('_APP_SMTP_PORT', "1025"));
|
||||
$smtpUsername = System::getEnv('_APP_SMTP_USERNAME', 'user');
|
||||
$smtpPassword = System::getEnv('_APP_SMTP_PASSWORD', 'password');
|
||||
|
||||
// Create a team
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -699,10 +993,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'senderEmail' => 'custommailer@appwrite.io',
|
||||
'senderName' => 'Custom Mailer',
|
||||
'replyTo' => 'reply@appwrite.io',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'host' => $smtpHost,
|
||||
'port' => $smtpPort,
|
||||
'username' => $smtpUsername,
|
||||
'password' => $smtpPassword,
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -736,10 +1030,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'senderEmail' => 'custommailer@appwrite.io',
|
||||
'senderName' => 'Custom Mailer',
|
||||
'replyTo' => 'reply@appwrite.io',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'host' => $smtpHost,
|
||||
'port' => $smtpPort,
|
||||
'username' => $smtpUsername,
|
||||
'password' => $smtpPassword,
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -752,10 +1046,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'senderEmail' => 'custommailer@appwrite.io',
|
||||
'senderName' => 'Custom Mailer',
|
||||
'replyTo' => 'reply@appwrite.io',
|
||||
'host' => 'maildev',
|
||||
'port' => 1025,
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'host' => $smtpHost,
|
||||
'port' => $smtpPort,
|
||||
'username' => $smtpUsername,
|
||||
'password' => $smtpPassword,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
@@ -776,7 +1070,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('Account Verification', $response['body']['subject']);
|
||||
$this->assertEquals('Account Verification for {{project}}', $response['body']['subject']);
|
||||
$this->assertEquals('', $response['body']['senderEmail']);
|
||||
$this->assertEquals('verification', $response['body']['type']);
|
||||
$this->assertEquals('en-us', $response['body']['locale']);
|
||||
@@ -879,7 +1173,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals('Project Test 2', $response['body']['name']);
|
||||
$this->assertEquals('Project Test', $response['body']['name']);
|
||||
$this->assertArrayHasKey('platforms', $response['body']);
|
||||
$this->assertArrayHasKey('webhooks', $response['body']);
|
||||
$this->assertArrayHasKey('keys', $response['body']);
|
||||
@@ -959,39 +1253,6 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(15, $response['body']['authDuration']);
|
||||
|
||||
// Create session
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
]), [
|
||||
'email' => $userEmail,
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$sessionCookie = $response['headers']['set-cookie'];
|
||||
|
||||
// Wait 10 seconds, ensure valid session, extend session
|
||||
\sleep(10);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'Cookie' => $sessionCookie,
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/account/sessions/current', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => $sessionCookie,
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
// Wait 20 seconds, ensure non-valid session
|
||||
\sleep(20);
|
||||
|
||||
@@ -3132,7 +3393,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertContains('users.write', $response['body']['scopes']);
|
||||
$this->assertContains('collections.read', $response['body']['scopes']);
|
||||
$this->assertContains('tables.read', $response['body']['scopes']);
|
||||
$this->assertCount(3, $response['body']['scopes']);
|
||||
$this->assertCount(4, $response['body']['scopes']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
@@ -3151,7 +3412,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertContains('users.write', $response['body']['scopes']);
|
||||
$this->assertContains('collections.read', $response['body']['scopes']);
|
||||
$this->assertContains('tables.read', $response['body']['scopes']);
|
||||
$this->assertCount(3, $response['body']['scopes']);
|
||||
$this->assertCount(4, $response['body']['scopes']);
|
||||
$this->assertArrayHasKey('sdks', $response['body']);
|
||||
$this->assertEmpty($response['body']['sdks']);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
@@ -5086,8 +5347,8 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEmpty($response['body']);
|
||||
|
||||
/**
|
||||
* Get rate limit trying to use the deleted key
|
||||
*/
|
||||
* Get rate limit trying to use the deleted key
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
|
||||
@@ -160,4 +160,40 @@ class StorageConsoleClientTest extends Scope
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testFilePermissionNotAutoSetInConsole(): void
|
||||
{
|
||||
// Create a bucket
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'Test Bucket Permissions',
|
||||
'fileSecurity' => true,
|
||||
]);
|
||||
$this->assertEquals(201, $bucket['headers']['status-code']);
|
||||
$bucketId = $bucket['body']['$id'];
|
||||
|
||||
// Create a file without providing permissions (console client should not auto-set permissions)
|
||||
$file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'fileId' => ID::unique(),
|
||||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'test.png'),
|
||||
]);
|
||||
$this->assertEquals(201, $file['headers']['status-code']);
|
||||
|
||||
// Verify file permissions are empty (not auto-set for privileged console user)
|
||||
$this->assertIsArray($file['body']['$permissions']);
|
||||
$this->assertEmpty($file['body']['$permissions']);
|
||||
|
||||
// Clean up: delete the bucket
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user