mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
85
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d0950b8bc | ||
|
|
647b4bc632 | ||
|
|
d38c921781 | ||
|
|
44610462b3 | ||
|
|
28ece7de02 | ||
|
|
8c6d4d8b36 | ||
|
|
2ebc6f70ef | ||
|
|
2b7690d6db | ||
|
|
d9af799cc7 | ||
|
|
3ed1ca736d | ||
|
|
9fa35db838 | ||
|
|
b6e020389b | ||
|
|
ccc0cfbfdc | ||
|
|
b91506fc2d | ||
|
|
9ffc23946c | ||
|
|
8bb6b5cd2a | ||
|
|
afea4ca57b | ||
|
|
b30b89ade9 | ||
|
|
829cf887dc | ||
|
|
32728ae4a0 | ||
|
|
d862a64874 | ||
|
|
168166b9c1 | ||
|
|
142e671c7d | ||
|
|
b4085d1083 | ||
|
|
18ed6a9c59 | ||
|
|
3d64ccd056 | ||
|
|
4f73eb021f | ||
|
|
7dcfd3ff3d | ||
|
|
4dfdfb5e59 | ||
|
|
5d1009b324 | ||
|
|
4568316345 | ||
|
|
c444bccacc | ||
|
|
f9aee4de5d | ||
|
|
037878cc75 | ||
|
|
416b26ac7d | ||
|
|
b47ac00ca8 | ||
|
|
2f53d09c5b | ||
|
|
1fa1aa8621 | ||
|
|
ec20fb59e2 | ||
|
|
7e49cf0bed | ||
|
|
4e32497be1 | ||
|
|
fe6f79c9c6 | ||
|
|
5c84efdd7c | ||
|
|
02d234ad3a | ||
|
|
8b7459f634 | ||
|
|
100cbf50f0 | ||
|
|
c7c59f4e7b | ||
|
|
a955dff55a | ||
|
|
7bcd5b4ebc | ||
|
|
dfb23612b0 | ||
|
|
4e71a54fae | ||
|
|
ea032c2804 | ||
|
|
a80ecd0cb6 | ||
|
|
8be7c6182e | ||
|
|
3bb6a8bcc8 | ||
|
|
c72d438a10 | ||
|
|
0085d93aeb | ||
|
|
3b9b0c96c4 | ||
|
|
2611bf4af1 | ||
|
|
de219de31d | ||
|
|
d8bbd82556 | ||
|
|
5d962e21b0 | ||
|
|
551c83dd2c | ||
|
|
d964c5a439 | ||
|
|
4850fb54ed | ||
|
|
796bc13c19 | ||
|
|
30e080c4d3 | ||
|
|
aaebeec61e | ||
|
|
1789d2ce89 | ||
|
|
239a9c5457 | ||
|
|
53d0e14f97 | ||
|
|
741267c6c5 | ||
|
|
e85080499a | ||
|
|
ee1ca5ace6 | ||
|
|
b36472f0da | ||
|
|
52ae8b3880 | ||
|
|
8f09e74462 | ||
|
|
30907d716f | ||
|
|
eb46855e72 | ||
|
|
45557e5929 | ||
|
|
6ad6a5dea3 | ||
|
|
098f7aa3e3 | ||
|
|
f8c8c17757 | ||
|
|
5b1ee93927 | ||
|
|
dc6b2ce3aa |
+16
-31
@@ -180,38 +180,11 @@ jobs:
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const allDatabases = ['MariaDB', 'PostgreSQL', 'MongoDB'];
|
||||
const allModes = ['dedicated', 'shared_v1', 'shared_v2'];
|
||||
const allDatabases = ['MongoDB'];
|
||||
const allModes = ['shared_v1', 'shared_v2'];
|
||||
|
||||
const defaultDatabases = ['MongoDB'];
|
||||
const defaultModes = ['dedicated'];
|
||||
|
||||
const pr = context.payload.pull_request;
|
||||
if (!pr) {
|
||||
core.setOutput('databases', JSON.stringify(allDatabases));
|
||||
core.setOutput('modes', JSON.stringify(allModes));
|
||||
return;
|
||||
}
|
||||
|
||||
const getContent = (ref) => github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: 'composer.lock',
|
||||
ref,
|
||||
});
|
||||
|
||||
const getDbVersion = (lock) => lock.packages?.find(p => p.name === 'utopia-php/database')?.version;
|
||||
|
||||
const [{ data: base }, { data: head }] = await Promise.all([
|
||||
getContent(pr.base.sha),
|
||||
getContent(pr.head.sha),
|
||||
]);
|
||||
|
||||
const decode = (content) => JSON.parse(Buffer.from(content, 'base64').toString());
|
||||
const databaseChanged = getDbVersion(decode(base.content)) !== getDbVersion(decode(head.content));
|
||||
|
||||
core.setOutput('databases', JSON.stringify(databaseChanged ? allDatabases : defaultDatabases));
|
||||
core.setOutput('modes', JSON.stringify(databaseChanged ? allModes : defaultModes));
|
||||
core.setOutput('databases', JSON.stringify(allDatabases));
|
||||
core.setOutput('modes', JSON.stringify(allModes));
|
||||
|
||||
build:
|
||||
name: Build
|
||||
@@ -451,6 +424,10 @@ jobs:
|
||||
_APP_BROWSER_HOST: http://invalid-browser/v1
|
||||
_APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }}
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }}
|
||||
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'documentsdb_db_main' || '' }}
|
||||
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'documentsdb_db_main' || '' }}
|
||||
_APP_DATABASE_VECTORSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'vectorsdb_db_main' || '' }}
|
||||
_APP_DATABASE_VECTORSDB_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'vectorsdb_db_main' || '' }}
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
@@ -525,6 +502,10 @@ jobs:
|
||||
_APP_OPTIONS_ABUSE: enabled
|
||||
_APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }}
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }}
|
||||
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'documentsdb_db_main' || '' }}
|
||||
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'documentsdb_db_main' || '' }}
|
||||
_APP_DATABASE_VECTORSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'vectorsdb_db_main' || '' }}
|
||||
_APP_DATABASE_VECTORSDB_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'vectorsdb_db_main' || '' }}
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
@@ -582,6 +563,10 @@ jobs:
|
||||
env:
|
||||
_APP_DATABASE_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'database_db_main' || '' }}
|
||||
_APP_DATABASE_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'database_db_main' || '' }}
|
||||
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'documentsdb_db_main' || '' }}
|
||||
_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'documentsdb_db_main' || '' }}
|
||||
_APP_DATABASE_VECTORSDB_SHARED_TABLES: ${{ matrix.mode != 'dedicated' && 'vectorsdb_db_main' || '' }}
|
||||
_APP_DATABASE_VECTORSDB_SHARED_TABLES_V1: ${{ matrix.mode == 'shared_v1' && 'vectorsdb_db_main' || '' }}
|
||||
run: |
|
||||
docker load --input /tmp/${{ env.IMAGE }}.tar
|
||||
docker compose pull --quiet --ignore-buildable
|
||||
|
||||
@@ -1,107 +1,120 @@
|
||||
# AGENTS.md
|
||||
# Appwrite
|
||||
|
||||
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.
|
||||
Self-hosted Backend-as-a-Service platform. Hybrid monolithic-microservice architecture built with PHP 8.3+ on Swoole, delivered as Docker containers.
|
||||
|
||||
## Project Overview
|
||||
## Commands
|
||||
|
||||
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.
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `docker compose up -d --force-recreate --build` | Build and start all services |
|
||||
| `docker compose exec appwrite test tests/e2e/Services/[Service]` | Run E2E tests for a service |
|
||||
| `docker compose exec appwrite test tests/e2e/Services/[Service] --filter=[Method]` | Run a single test method |
|
||||
| `docker compose exec appwrite test tests/unit/` | Run unit tests |
|
||||
| `composer format` | Auto-format code (Pint, PSR-12) |
|
||||
| `composer format <file>` | Format a specific file |
|
||||
| `composer lint <file>` | Check formatting of a file |
|
||||
| `composer analyze` | Static analysis (PHPStan level 3) |
|
||||
| `composer check` | Same as `analyze` |
|
||||
|
||||
**Key Technologies:**
|
||||
- **Backend:** PHP 8.3+, Swoole
|
||||
- **Libraries:** Utopia PHP
|
||||
- **Database:** MariaDB, Redis
|
||||
- **Cache:** Redis
|
||||
- **Queue:** Redis
|
||||
- **Containers:** Docker
|
||||
## Stack
|
||||
|
||||
## Development Commands
|
||||
- PHP 8.3+, Swoole 6.x (async runtime, replaces PHP-FPM)
|
||||
- Utopia PHP framework (HTTP routing, CLI, DI, queue)
|
||||
- MongoDB (default), MariaDB, MySQL, PostgreSQL (adapters via utopia-php/database)
|
||||
- Redis (cache, queue, pub/sub)
|
||||
- Docker + Traefik (reverse proxy)
|
||||
- PHPUnit 12, Pint (PSR-12), PHPStan level 3
|
||||
|
||||
```bash
|
||||
# Run Appwrite
|
||||
docker compose up -d --force-recreate --build
|
||||
## Project layout
|
||||
|
||||
# Run specific test
|
||||
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] --filter=[FunctionName]
|
||||
- **src/Appwrite/Platform/Modules/** -- feature modules (Account, Avatars, Compute, Console, Databases, Functions, Health, Project, Projects, Proxy, Sites, Storage, Teams, Tokens, VCS, Webhooks)
|
||||
- **src/Appwrite/Platform/Workers/** -- background job workers
|
||||
- **src/Appwrite/Platform/Tasks/** -- CLI tasks
|
||||
- **app/init.php** -- bootstrap (registers services, resources, listeners)
|
||||
- **app/init/** -- configs, constants, locales, models, registers, resources, span, database filters/formats
|
||||
- **bin/** -- CLI entry points: `worker-*` (14 workers), `schedule-*`, `queue-*`, plus `doctor`, `install`, `migrate`, `realtime`, `upgrade`, `ssl`, `vars`, `maintenance`, `interval`, `specs`, `sdks`, etc.
|
||||
- **tests/e2e/** -- end-to-end tests per service
|
||||
- **tests/unit/** -- unit tests
|
||||
- **public/** -- static assets and generated SDKs
|
||||
|
||||
# Format code
|
||||
composer format
|
||||
## Module structure
|
||||
|
||||
Each module under `src/Appwrite/Platform/Modules/{Name}/` contains:
|
||||
|
||||
```
|
||||
Module.php -- registers all services for the module
|
||||
Services/Http.php -- registers HTTP endpoints
|
||||
Services/Workers.php -- registers background workers
|
||||
Services/Tasks.php -- registers CLI tasks
|
||||
Http/{Service}/ -- endpoint actions (Create.php, Get.php, Update.php, Delete.php, XList.php)
|
||||
Workers/ -- worker implementations
|
||||
Tasks/ -- CLI task implementations
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
HTTP endpoint nesting reflects the URL path. Sub-resources get subdirectories. For example, within the Functions module:
|
||||
`Http/Deployments/Template/Create.php` -> `POST /v1/functions/:functionId/deployments/template`
|
||||
|
||||
- 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
|
||||
File names in Http directories must only be `Get.php`, `Create.php`, `Update.php`, `Delete.php`, or `XList.php`. For non-CRUD operations, model the endpoint as a property update. For example, updating a team membership status lives at `Teams/Http/Memberships/Status/Update.php` (`PATCH /v1/teams/:teamId/memberships/:membershipId/status`).
|
||||
|
||||
### Naming Conventions
|
||||
Register new modules in `src/Appwrite/Platform/Appwrite.php`. Detailed module guide: `src/Appwrite/Platform/AGENTS.md`.
|
||||
|
||||
#### `resourceType` Naming Rule
|
||||
## Action pattern (HTTP endpoints)
|
||||
|
||||
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'
|
||||
class Create extends Action
|
||||
{
|
||||
public static function getName(): string { return 'createTeam'; }
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/teams')
|
||||
->desc('Create team')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].create')
|
||||
->label('scope', 'teams.write')
|
||||
->param('teamId', '', new CustomId(), 'Team ID.')
|
||||
->param('name', null, new Text(128), 'Team name.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $teamId,
|
||||
string $name,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Event $queueForEvents,
|
||||
): void {
|
||||
// implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Patterns
|
||||
Common injections: `$response`, `$request`, `$dbForProject`, `$dbForPlatform`, `$user`, `$project`, `$queueForEvents`, `$queueForMails`, `$queueForDeletes`.
|
||||
|
||||
### Document Update Optimization
|
||||
## Conventions
|
||||
|
||||
When updating documents, always pass only the changed attributes as a sparse `Document` rather than the full document. This is more efficient because `updateDocument()` internally performs `array_merge($old, $new)`.
|
||||
- PSR-12 formatting enforced by Pint. PSR-4 autoloading.
|
||||
- `resourceType` values are always **plural**: `'functions'`, `'sites'`, `'deployments'`.
|
||||
- When updating documents, pass only changed attributes as a sparse Document:
|
||||
```php
|
||||
// correct
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'name' => $name,
|
||||
]));
|
||||
// incorrect -- passing full document is inefficient
|
||||
$user->setAttribute('name', $name);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
```
|
||||
Exceptions: migrations, `array_merge()` with `getArrayCopy()`, updates where nearly all attributes change, complex nested relationship logic requiring full document state.
|
||||
- Avoid introducing dependencies outside the `utopia-php` ecosystem.
|
||||
- Never hardcode credentials -- use environment variables.
|
||||
- Code changes may require container restart. No central log location -- check relevant containers.
|
||||
|
||||
**Correct Pattern:**
|
||||
```php
|
||||
// Good: Pass only changed attributes directly
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
]));
|
||||
```
|
||||
## Cross-repo context
|
||||
|
||||
**Incorrect Pattern:**
|
||||
```php
|
||||
$user->setAttribute('name', $name);
|
||||
$user->setAttribute('email', $email);
|
||||
|
||||
// Bad: Passing full document is inefficient
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
```
|
||||
|
||||
**Exceptions:**
|
||||
- Migration files (need full document updates by design)
|
||||
- Cases already using `array_merge()` with `getArrayCopy()`
|
||||
- Updates where almost all attributes of the document change at once (sparse update provides little benefit compared to passing the full document)
|
||||
- Complex nested relationship logic where full document state is required
|
||||
|
||||
## 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.
|
||||
|
||||
## Adding new endpoints
|
||||
|
||||
When adding new endpoints, make sure to use modules and follow its patterns. Find instruction in [Modules AGENTS.md](src/Appwrite/Platform/AGENTS.md) file.
|
||||
|
||||
## 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
|
||||
Appwrite is the base server for `appwrite/cloud`. Changes to the Action pattern, module structure, DI system, or response models affect cloud. The `feat-dedicated-db` feature spans cloud, edge, and console.
|
||||
|
||||
@@ -57,21 +57,21 @@
|
||||
"emails.recovery.thanks": "Thanks,",
|
||||
"emails.recovery.buttonText": "Reset password",
|
||||
"emails.recovery.signature": "{{project}} team",
|
||||
"emails.csvExport.success.subject": "Your CSV export is ready",
|
||||
"emails.csvExport.success.preview": "Your data export has been completed successfully.",
|
||||
"emails.csvExport.success.hello": "Hello {{user}},",
|
||||
"emails.csvExport.success.body": "Your CSV export is ready to download. Click the button below to download your data export.",
|
||||
"emails.csvExport.success.footer": "This download link will expire in 1 hour.",
|
||||
"emails.csvExport.success.thanks": "Thanks,",
|
||||
"emails.csvExport.success.buttonText": "Download CSV",
|
||||
"emails.csvExport.success.signature": "Appwrite team",
|
||||
"emails.csvExport.failure.subject": "Your CSV export failed - file too large",
|
||||
"emails.csvExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
|
||||
"emails.csvExport.failure.hello": "Hello {{user}},",
|
||||
"emails.csvExport.failure.body": "Your CSV export could not be completed because the export file size ({{size}}MB) exceeds your plan limit. Please consider upgrading your plan or exporting a smaller dataset.",
|
||||
"emails.csvExport.failure.footer": "If you have any questions, please contact our support team.",
|
||||
"emails.csvExport.failure.thanks": "Thanks,",
|
||||
"emails.csvExport.failure.signature": "{{project}} team",
|
||||
"emails.dataExport.success.subject": "Your {{type}} export is ready",
|
||||
"emails.dataExport.success.preview": "Your data export has been completed successfully.",
|
||||
"emails.dataExport.success.hello": "Hello {{user}},",
|
||||
"emails.dataExport.success.body": "Your {{type}} export is ready to download. Click the button below to download your data export.",
|
||||
"emails.dataExport.success.footer": "This download link will expire in 1 hour.",
|
||||
"emails.dataExport.success.thanks": "Thanks,",
|
||||
"emails.dataExport.success.buttonText": "Download {{type}}",
|
||||
"emails.dataExport.success.signature": "Appwrite team",
|
||||
"emails.dataExport.failure.subject": "Your {{type}} export failed - file too large",
|
||||
"emails.dataExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
|
||||
"emails.dataExport.failure.hello": "Hello {{user}},",
|
||||
"emails.dataExport.failure.body": "Your {{type}} export could not be completed because the export file size ({{size}}MB) exceeds your plan limit. Please consider upgrading your plan or exporting a smaller dataset.",
|
||||
"emails.dataExport.failure.footer": "If you have any questions, please contact our support team.",
|
||||
"emails.dataExport.failure.thanks": "Thanks,",
|
||||
"emails.dataExport.failure.signature": "{{project}} team",
|
||||
"emails.invitation.subject": "Invitation to {{team}} Team at {{project}}",
|
||||
"emails.invitation.preview": "{{owner}} invited you to join {{team}} at {{project}}",
|
||||
"emails.invitation.hello": "Hello {{user}},",
|
||||
|
||||
@@ -29,6 +29,7 @@ use Utopia\Migration\Resource;
|
||||
use Utopia\Migration\Sources\Appwrite;
|
||||
use Utopia\Migration\Sources\CSV;
|
||||
use Utopia\Migration\Sources\Firebase;
|
||||
use Utopia\Migration\Sources\JSON;
|
||||
use Utopia\Migration\Sources\NHost;
|
||||
use Utopia\Migration\Sources\Supabase;
|
||||
use Utopia\Migration\Transfer;
|
||||
@@ -53,6 +54,15 @@ function getDatabaseTransferResourceServices(string $databaseType)
|
||||
};
|
||||
}
|
||||
|
||||
function getDatabaseResourceType(string $databaseType): string
|
||||
{
|
||||
return match($databaseType) {
|
||||
DATABASE_TYPE_VECTORSDB => Resource::TYPE_DATABASE_VECTORSDB,
|
||||
DATABASE_TYPE_DOCUMENTSDB => Resource::TYPE_DATABASE_DOCUMENTSDB,
|
||||
default => Resource::TYPE_DATABASE,
|
||||
};
|
||||
}
|
||||
|
||||
Http::post('/v1/migrations/appwrite')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Create Appwrite migration')
|
||||
@@ -447,6 +457,7 @@ Http::post('/v1/migrations/csv/imports')
|
||||
}
|
||||
$fileSize = $deviceForMigrations->getFileSize($newPath);
|
||||
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
|
||||
$resourceType = getDatabaseResourceType($databaseType);
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => $migrationId,
|
||||
@@ -456,7 +467,7 @@ Http::post('/v1/migrations/csv/imports')
|
||||
'destination' => Appwrite::getName(),
|
||||
'resources' => $resources,
|
||||
'resourceId' => $resourceId,
|
||||
'resourceType' => Resource::TYPE_DATABASE,
|
||||
'resourceType' => $resourceType,
|
||||
'statusCounters' => '{}',
|
||||
'resourceData' => '{}',
|
||||
'errors' => [],
|
||||
@@ -565,16 +576,6 @@ Http::post('/v1/migrations/csv/exports')
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$validator = new Documents(
|
||||
attributes: $collection->getAttribute('attributes', []),
|
||||
indexes: $collection->getAttribute('indexes', []),
|
||||
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
|
||||
);
|
||||
|
||||
if (!$validator->isValid($parsedQueries)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
// getting databasetype
|
||||
$resources = explode(':', $resourceId);
|
||||
$databaseId = $resources[0];
|
||||
@@ -583,7 +584,23 @@ Http::post('/v1/migrations/csv/exports')
|
||||
if (!in_array($databaseType, CSV_ALLOWED_DATABASE_TYPES)) {
|
||||
throw new Exception(Exception::MIGRATION_DATABASE_TYPE_UNSUPPORTED, 'Database type not supported for csv');
|
||||
}
|
||||
|
||||
// Schemaless databases (DocumentsDB, VectorsDB) allow queries on dynamic fields
|
||||
$isSchemaless = in_array($databaseType, [DATABASE_TYPE_DOCUMENTSDB, DATABASE_TYPE_VECTORSDB]);
|
||||
|
||||
$validator = new Documents(
|
||||
attributes: $collection->getAttribute('attributes', []),
|
||||
indexes: $collection->getAttribute('indexes', []),
|
||||
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
|
||||
supportForAttributes: !$isSchemaless,
|
||||
);
|
||||
|
||||
if (!$validator->isValid($parsedQueries)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
|
||||
$resourceType = getDatabaseResourceType($databaseType);
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
@@ -593,7 +610,7 @@ Http::post('/v1/migrations/csv/exports')
|
||||
'destination' => CSV::getName(),
|
||||
'resources' => $resources,
|
||||
'resourceId' => $resourceId,
|
||||
'resourceType' => Resource::TYPE_DATABASE,
|
||||
'resourceType' => $resourceType,
|
||||
'statusCounters' => '{}',
|
||||
'resourceData' => '{}',
|
||||
'errors' => [],
|
||||
@@ -624,6 +641,291 @@ Http::post('/v1/migrations/csv/exports')
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::post('/v1/migrations/json/imports')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Import documents from a JSON')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'migrations',
|
||||
group: null,
|
||||
name: 'createJSONImport',
|
||||
description: '/docs/references/migrations/migration-json-import.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_ACCEPTED,
|
||||
model: Response::MODEL_MIGRATION,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File ID.')
|
||||
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
|
||||
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (
|
||||
string $bucketId,
|
||||
string $fileId,
|
||||
string $resourceId,
|
||||
bool $internalFile,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
array $platform,
|
||||
Device $deviceForFiles,
|
||||
Device $deviceForMigrations,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
if ($internalFile) {
|
||||
return $dbForPlatform->getDocument('buckets', 'default');
|
||||
}
|
||||
return $dbForProject->getDocument('buckets', $bucketId);
|
||||
});
|
||||
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $file->getAttribute('path', '');
|
||||
if (!$deviceForFiles->exists($path)) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
||||
}
|
||||
|
||||
// No encryption or compression on files above 20MB.
|
||||
$hasEncryption = !empty($file->getAttribute('openSSLCipher'));
|
||||
$compression = $file->getAttribute('algorithm', Compression::NONE);
|
||||
$hasCompression = $compression !== Compression::NONE;
|
||||
|
||||
$migrationId = ID::unique();
|
||||
$newPath = $deviceForMigrations->getPath($migrationId . '_' . $fileId . '.json');
|
||||
|
||||
if ($hasEncryption || $hasCompression) {
|
||||
$source = $deviceForFiles->read($path);
|
||||
|
||||
if ($hasEncryption) {
|
||||
$source = OpenSSL::decrypt(
|
||||
$source,
|
||||
$file->getAttribute('openSSLCipher'),
|
||||
System::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
|
||||
0,
|
||||
hex2bin($file->getAttribute('openSSLIV')),
|
||||
hex2bin($file->getAttribute('openSSLTag'))
|
||||
);
|
||||
}
|
||||
|
||||
if ($hasCompression) {
|
||||
switch ($compression) {
|
||||
case Compression::ZSTD:
|
||||
$source = (new Zstd())->decompress($source);
|
||||
break;
|
||||
case Compression::GZIP:
|
||||
$source = (new GZIP())->decompress($source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Manual write after decryption and/or decompression
|
||||
if (!$deviceForMigrations->write($newPath, $source, 'application/json')) {
|
||||
throw new \Exception('Unable to copy file');
|
||||
}
|
||||
} elseif (!$deviceForFiles->transfer($path, $newPath, $deviceForMigrations)) {
|
||||
throw new \Exception('Unable to copy file');
|
||||
}
|
||||
|
||||
$fileSize = $deviceForMigrations->getFileSize($newPath);
|
||||
|
||||
[$databaseId] = \explode(':', $resourceId, 2);
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
$databaseType = $database->getAttribute('type');
|
||||
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
|
||||
$resourceType = getDatabaseResourceType($databaseType);
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => $migrationId,
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => JSON::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'resources' => $resources,
|
||||
'resourceId' => $resourceId,
|
||||
'resourceType' => $resourceType,
|
||||
'statusCounters' => '{}',
|
||||
'resourceData' => '{}',
|
||||
'errors' => [],
|
||||
'options' => [
|
||||
'path' => $newPath,
|
||||
'size' => $fileSize,
|
||||
],
|
||||
]));
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->trigger();
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::post('/v1/migrations/json/exports')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Export documents to JSON')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'migrations',
|
||||
group: null,
|
||||
name: 'createJSONExport',
|
||||
description: '/docs/references/migrations/migration-json-export.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_ACCEPTED,
|
||||
model: Response::MODEL_MIGRATION,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database to export.')
|
||||
->param('filename', '', new Text(255), 'The name of the file to be created for the export, excluding the .json extension.')
|
||||
->param('columns', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of attributes to export. If empty, all attributes will be exported. You can use the `*` wildcard to export all attributes from the collection.', true)
|
||||
->param('queries', [], new ArrayList(new Text(0)), 'Array of query strings generated using the Query class provided by the SDK to filter documents to export. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->param('notify', true, new Boolean(), 'Set to true to receive an email when the export is complete. Default is true.', true)
|
||||
->inject('user')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (
|
||||
string $resourceId,
|
||||
string $filename,
|
||||
array $columns,
|
||||
array $queries,
|
||||
bool $notify,
|
||||
Document $user,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
array $platform,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
try {
|
||||
$parsedQueries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
[$databaseId, $collectionId] = \explode(':', $resourceId, 2);
|
||||
if (empty($databaseId)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
if (empty($collectionId)) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$databaseType = $database->getAttribute('type');
|
||||
|
||||
// Schemaless databases (DocumentsDB, VectorsDB) allow queries on dynamic fields
|
||||
$isSchemaless = in_array($databaseType, [DATABASE_TYPE_DOCUMENTSDB, DATABASE_TYPE_VECTORSDB]);
|
||||
|
||||
$validator = new Documents(
|
||||
attributes: $collection->getAttribute('attributes', []),
|
||||
indexes: $collection->getAttribute('indexes', []),
|
||||
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
|
||||
supportForAttributes: !$isSchemaless,
|
||||
);
|
||||
|
||||
if (!$validator->isValid($parsedQueries)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$resources = Transfer::extractServices([getDatabaseTransferResourceServices($databaseType)]);
|
||||
$resourceType = getDatabaseResourceType($databaseType);
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Appwrite::getName(),
|
||||
'destination' => JSON::getName(),
|
||||
'resources' => $resources,
|
||||
'resourceId' => $resourceId,
|
||||
'resourceType' => $resourceType,
|
||||
'statusCounters' => '{}',
|
||||
'resourceData' => '{}',
|
||||
'errors' => [],
|
||||
'options' => [
|
||||
'bucketId' => 'default', // Always use internal bucket
|
||||
'filename' => $filename,
|
||||
'columns' => $columns,
|
||||
'queries' => $queries,
|
||||
'notify' => $notify,
|
||||
'userInternalId' => $user->getSequence(),
|
||||
],
|
||||
]));
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->trigger();
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
Http::get('/v1/migrations')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('List migrations')
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
|
||||
$lines = explode("\n", $data, 3);
|
||||
$request = $lines[0];
|
||||
if (count($lines) > 1) {
|
||||
$domain = trim(explode('Host: ', $lines[1])[1]);
|
||||
$domain = trim(explode('Host: ', $lines[1])[1] ?? '');
|
||||
}
|
||||
|
||||
// Sync executions are considered risky
|
||||
|
||||
+27
-5
@@ -888,8 +888,9 @@ Http::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authori
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) {
|
||||
$initializedPools = [];
|
||||
|
||||
return function (Document $database) use ($pools, $cache, $project, $request, $usage, $authorization): Database {
|
||||
return function (Document $database) use ($pools, $cache, $project, $request, $usage, $authorization, &$initializedPools): Database {
|
||||
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
|
||||
$databaseType = $database->getAttribute('type', '');
|
||||
|
||||
@@ -907,11 +908,12 @@ Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Docume
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
$pool = $pools->get($databaseDSN->getHost());
|
||||
$databaseHost = $databaseDSN->getHost();
|
||||
$pool = $pools->get($databaseHost);
|
||||
|
||||
$adapter = new DatabasePool($pool);
|
||||
$database = new Database($adapter, $cache);
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
$sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')));
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
@@ -922,10 +924,30 @@ Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Docume
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
// inside pools authorization needs to be set first
|
||||
$database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB);
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
|
||||
// When the database uses a separate pool (e.g. vectorsdb on PostgreSQL),
|
||||
// always use dedicated mode with namespace isolation. Shared tables mode
|
||||
// can't be used across different engines (e.g. MongoDB UUID tenants are
|
||||
// incompatible with PostgreSQL's integer _tenant column).
|
||||
if ($databaseHost !== $dsn->getHost()) {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
|
||||
$poolKey = $databaseHost . ':' . $database->getNamespace();
|
||||
if (!isset($initializedPools[$poolKey])) {
|
||||
try {
|
||||
$database->create();
|
||||
} catch (\Utopia\Database\Exception\Duplicate) {
|
||||
// Schema already exists
|
||||
}
|
||||
$initializedPools[$poolKey] = true;
|
||||
}
|
||||
} elseif (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int)$project->getSequence())
|
||||
->setTenant($project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
|
||||
@@ -13,7 +13,7 @@ $enabledDatabases = $enabledDatabases ?? ['mongodb', 'mariadb', 'postgresql'];
|
||||
$isLocalInstall = $isLocalInstall ?? false;
|
||||
|
||||
|
||||
$cardStep = min(4, $step);
|
||||
$cardStep = ($step === 5) ? 4 : $step;
|
||||
$stepFile = __DIR__ . "/installer/templates/steps/step-{$cardStep}.phtml";
|
||||
if (!is_file($stepFile)) {
|
||||
$stepFile = __DIR__ . "/installer/templates/steps/step-1.phtml";
|
||||
|
||||
@@ -478,6 +478,10 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.installer-page[data-upgrade='true'] .installer-step {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.action-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1808,3 +1812,92 @@ body {
|
||||
gap: var(--gap-s);
|
||||
}
|
||||
}
|
||||
|
||||
.migration-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--gap-l);
|
||||
padding: var(--space-6);
|
||||
background: var(--bgcolor-neutral-default);
|
||||
border-radius: var(--border-radius-m);
|
||||
outline: var(--border-width-s) solid var(--border-neutral);
|
||||
outline-offset: calc(var(--border-width-s) * -1);
|
||||
cursor: pointer;
|
||||
transition: outline-color 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.migration-option:hover {
|
||||
outline-color: var(--border-neutral-stronger);
|
||||
}
|
||||
|
||||
.migration-option-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.migration-switch {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.migration-switch-track {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 32px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
background: var(--bgcolor-neutral-invert-weaker);
|
||||
transition: background 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.migration-switch-thumb {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--bgcolor-neutral-primary);
|
||||
transition: transform 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
#run-migration:checked ~ .migration-switch-track {
|
||||
background: var(--bgcolor-neutral-invert-weak);
|
||||
}
|
||||
|
||||
#run-migration:checked ~ .migration-switch-track .migration-switch-thumb {
|
||||
transform: translateX(12px);
|
||||
}
|
||||
|
||||
#run-migration:focus-visible ~ .migration-switch-track {
|
||||
box-shadow: 0 0 0 var(--border-width-l) var(--border-focus);
|
||||
}
|
||||
|
||||
.migration-hint {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--gap-s);
|
||||
padding: 0 var(--space-2);
|
||||
}
|
||||
|
||||
.migration-hint-icon {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--fgcolor-neutral-tertiary);
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.migration-hint-icon svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.migration-code {
|
||||
padding: 1px 4px;
|
||||
border-radius: var(--border-radius-xs, 4px);
|
||||
background: var(--bgcolor-neutral-secondary);
|
||||
font-family: monospace;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
const { validateInstallRequest } = window.InstallerStepsProgress || {};
|
||||
|
||||
const isUpgrade = document.body?.dataset.upgrade === 'true';
|
||||
const stepFlow = isUpgrade ? [1, 4, 5] : [1, 2, 3, 4, 5];
|
||||
const stepFlow = isUpgrade ? [1, 6, 4, 5] : [1, 2, 3, 4, 5];
|
||||
const cardSteps = stepFlow.filter((step) => step !== 5);
|
||||
|
||||
const normalizeStep = (step) => {
|
||||
@@ -53,7 +53,7 @@
|
||||
let pendingStep = null;
|
||||
let pendingPushState = false;
|
||||
|
||||
const clampStep = (step) => Math.max(1, Math.min(5, step));
|
||||
const clampStep = (step) => Math.max(1, Math.min(6, step));
|
||||
const isInstallLocked = () => Boolean(window.InstallerSteps?.isInstallLocked?.());
|
||||
|
||||
const scrollToFirstError = (panel) => {
|
||||
@@ -399,11 +399,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action === 'next' && String(target) === '5' && typeof validateInstallRequest === 'function') {
|
||||
const isValid = await validateInstallRequest();
|
||||
if (!isValid) {
|
||||
return;
|
||||
if (action === 'next' && String(target) === '5') {
|
||||
if (typeof validateInstallRequest === 'function') {
|
||||
const isValid = await validateInstallRequest();
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Clear stale install data from previous runs so initStep5
|
||||
// starts a fresh install instead of trying to resume.
|
||||
const { clearInstallLock, clearInstallId } = window.InstallerStepsState || {};
|
||||
clearInstallLock?.();
|
||||
clearInstallId?.();
|
||||
}
|
||||
if (isInstallLocked() && Number(target) !== 5) {
|
||||
requestStep(5, true);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
ENV_VARS: 'env-vars',
|
||||
DOCKER_CONTAINERS: 'docker-containers',
|
||||
ACCOUNT_SETUP: 'account-setup',
|
||||
MIGRATION: 'migration',
|
||||
SSL_CERTIFICATE: 'ssl-certificate',
|
||||
REDIRECT: 'redirect'
|
||||
});
|
||||
@@ -52,6 +53,11 @@
|
||||
id: STEP_IDS.DOCKER_CONTAINERS,
|
||||
inProgress: 'Restarting Docker containers...',
|
||||
done: 'Docker containers restarted'
|
||||
},
|
||||
{
|
||||
id: STEP_IDS.MIGRATION,
|
||||
inProgress: 'Running database migration...',
|
||||
done: 'Database migration completed'
|
||||
}
|
||||
] : [
|
||||
{
|
||||
@@ -95,7 +101,7 @@
|
||||
const clampStep = (step) => {
|
||||
const numeric = Number(step);
|
||||
if (Number.isNaN(numeric)) return 1;
|
||||
return Math.max(1, Math.min(5, numeric));
|
||||
return Math.max(1, Math.min(6, numeric));
|
||||
};
|
||||
|
||||
window.InstallerStepsContext = Object.freeze({
|
||||
|
||||
@@ -373,7 +373,8 @@
|
||||
opensslKey: (formState?.opensslKey || '').trim(),
|
||||
assistantOpenAIKey: normalizedAssistantKey,
|
||||
accountEmail: normalizedAccountEmail,
|
||||
accountPassword: normalizedAccountPassword
|
||||
accountPassword: normalizedAccountPassword,
|
||||
migrate: formState?.migrate ?? false
|
||||
};
|
||||
};
|
||||
|
||||
@@ -721,7 +722,8 @@
|
||||
});
|
||||
startSyncedSpinnerRotation(list);
|
||||
|
||||
notifyInstallComplete(activeInstall?.installId, sessionDetails).finally(() => {
|
||||
const completeId = activeInstall?.installId || getStoredInstallId?.();
|
||||
notifyInstallComplete(completeId, sessionDetails).finally(() => {
|
||||
setTimeout(() => redirectToApp(protocol), TIMINGS?.redirectDelay ?? 0);
|
||||
});
|
||||
};
|
||||
@@ -911,21 +913,28 @@
|
||||
};
|
||||
|
||||
const isSnapshotTerminal = (snapshot) => {
|
||||
if (!snapshot?.steps) return true;
|
||||
if (!snapshot?.steps) return 'empty';
|
||||
const stepEntries = Object.values(snapshot.steps);
|
||||
if (stepEntries.length === 0) return true;
|
||||
if (stepEntries.length === 0) return 'empty';
|
||||
const hasError = stepEntries.some((s) => s.status === STATUS.ERROR);
|
||||
if (hasError) return true;
|
||||
if (hasError) return 'error';
|
||||
const allCompleted = INSTALLATION_STEPS.every((step) => {
|
||||
const detail = snapshot.steps[step.id];
|
||||
return detail && detail.status === STATUS.COMPLETED;
|
||||
});
|
||||
return allCompleted;
|
||||
if (allCompleted) return 'completed';
|
||||
return false;
|
||||
};
|
||||
|
||||
const resumeInstall = async (installId) => {
|
||||
const snapshot = await fetchInstallStatus(installId);
|
||||
if (!snapshot || isSnapshotTerminal(snapshot)) return false;
|
||||
const terminal = isSnapshotTerminal(snapshot);
|
||||
if (!snapshot || terminal) {
|
||||
if (terminal === 'completed') {
|
||||
return 'completed';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
activeInstall = {
|
||||
installId,
|
||||
controller: new AbortController(),
|
||||
@@ -1069,14 +1078,33 @@
|
||||
startInstallStream(newInstallId);
|
||||
};
|
||||
|
||||
const recoverToLastStep = () => {
|
||||
clearInstallId?.();
|
||||
clearInstallLock?.();
|
||||
const url = new URL(window.location.href);
|
||||
const lastStep = url.searchParams.get('step');
|
||||
// Stay on the current URL so the user keeps their place;
|
||||
// only navigate away if we're already on step 5 (the
|
||||
// progress screen) since there's nothing to show.
|
||||
if (!lastStep || String(lastStep) === '5') {
|
||||
window.location.href = '/?step=1';
|
||||
}
|
||||
};
|
||||
|
||||
const lock = getInstallLock?.();
|
||||
const existingInstallId = lock?.installId || getStoredInstallId?.();
|
||||
if (existingInstallId) {
|
||||
resumeInstall(existingInstallId).then((resumed) => {
|
||||
if (!resumed) {
|
||||
clearInstallId?.();
|
||||
resumeInstall(existingInstallId).then((result) => {
|
||||
if (result === 'completed') {
|
||||
// Install already finished — redirect to console
|
||||
// instead of bouncing back to step 1.
|
||||
stopSyncedSpinnerRotation();
|
||||
setUnloadGuard(false);
|
||||
clearInstallLock?.();
|
||||
window.location.href = '/?step=1';
|
||||
clearInstallId?.();
|
||||
startSslCheck(null);
|
||||
} else if (!result) {
|
||||
recoverToLastStep();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -329,6 +329,30 @@
|
||||
}
|
||||
};
|
||||
|
||||
const initStep6 = (root) => {
|
||||
if (!root) return;
|
||||
syncInstallLockFlag?.();
|
||||
applyLockPayload?.();
|
||||
applyBodyDefaults?.();
|
||||
|
||||
const checkbox = root.querySelector('#run-migration');
|
||||
if (checkbox) {
|
||||
if (formState.migrate !== undefined) {
|
||||
checkbox.checked = formState.migrate;
|
||||
} else {
|
||||
formState.migrate = checkbox.checked;
|
||||
}
|
||||
checkbox.addEventListener('change', () => {
|
||||
formState.migrate = checkbox.checked;
|
||||
dispatchStateChange?.('migrate');
|
||||
});
|
||||
}
|
||||
|
||||
if (isInstallLocked?.()) {
|
||||
disableControls?.(root);
|
||||
}
|
||||
};
|
||||
|
||||
const initStep = (step, container) => {
|
||||
if (!container) return;
|
||||
const root = container.querySelector('.step-layout') || container;
|
||||
@@ -346,6 +370,7 @@
|
||||
if (normalized === 3) initStep3(root);
|
||||
if (normalized === 4) initStep4(root);
|
||||
if (normalized === 5) Progress.initStep5?.(root);
|
||||
if (normalized === 6) initStep6(root);
|
||||
};
|
||||
|
||||
window.InstallerSteps = {
|
||||
|
||||
@@ -62,12 +62,14 @@ $badgeClass = $defaultSecretKey !== '' ? 'badge-success' : 'badge-warning';
|
||||
<span class="badge badge-neutral typography-text-xs-400" data-review-assistant-badge>Disabled</span>
|
||||
<div class="review-label typography-text-xs-400 text-neutral-tertiary">Appwrite Assistant</div>
|
||||
</div>
|
||||
<?php if (!$isUpgrade) { ?>
|
||||
<div class="review-row">
|
||||
<span class="badge <?php echo $badgeClass; ?> typography-text-xs-400" data-review-badge>
|
||||
<?php echo htmlspecialchars((string) $badgeLabel, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</span>
|
||||
<div class="review-label typography-text-xs-400 text-neutral-tertiary">Secret API key</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ $isUpgrade = $isUpgrade ?? false;
|
||||
<div class="install-panel">
|
||||
<div class="install-header">
|
||||
<div class="typography-text-m-400 text-neutral-primary">
|
||||
<?php echo $isUpgrade ? 'Updating your app…' : 'Installing your app…'; ?>
|
||||
<?php echo $isUpgrade ? 'Updating Appwrite…' : 'Installing Appwrite…'; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="install-list" data-install-list></div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
$isUpgrade = $isUpgrade ?? false;
|
||||
?>
|
||||
<div class="step-layout" data-step="6">
|
||||
<div class="stack-xl">
|
||||
<div class="stack-xxxs">
|
||||
<h1 class="typography-title-s text-neutral-primary">Database migration</h1>
|
||||
<p class="typography-text-m-400 text-neutral-secondary">
|
||||
Run database migration after the update to apply schema changes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="stack-xl">
|
||||
<label class="migration-option" for="run-migration">
|
||||
<span class="migration-option-content">
|
||||
<span class="typography-text-m-500 text-neutral-primary">Run migration automatically</span>
|
||||
<span class="typography-text-xs-400 text-neutral-tertiary">Recommended when upgrading to a new version</span>
|
||||
</span>
|
||||
<span class="migration-switch">
|
||||
<input type="checkbox" id="run-migration" name="migrate" class="sr-only" checked>
|
||||
<span class="migration-switch-track" aria-hidden="true">
|
||||
<span class="migration-switch-thumb"></span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div class="migration-hint">
|
||||
<span class="migration-hint-icon">
|
||||
<?php include __DIR__ . '/../../icons/info.svg'; ?>
|
||||
</span>
|
||||
<span class="typography-text-xs-400 text-neutral-tertiary">
|
||||
To run manually later: <code class="migration-code">docker compose exec appwrite migrate</code>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+26
-5
@@ -221,7 +221,9 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register, Document $project, Authorization $authorization) {
|
||||
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization): Database {
|
||||
$initializedPools = [];
|
||||
|
||||
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization, &$initializedPools): Database {
|
||||
$projectDocument ??= $project;
|
||||
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
|
||||
$databaseType = $database->getAttribute('type', '');
|
||||
@@ -246,7 +248,8 @@ Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register
|
||||
}
|
||||
|
||||
$pools = $register->get('pools');
|
||||
$pool = $pools->get($databaseDSN->getHost());
|
||||
$databaseHost = $databaseDSN->getHost();
|
||||
$pool = $pools->get($databaseHost);
|
||||
|
||||
$adapter = new DatabasePool($pool);
|
||||
$database = new Database($adapter, $cache);
|
||||
@@ -255,12 +258,30 @@ Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register
|
||||
->setAuthorization($authorization);
|
||||
$database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB);
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
$sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables, true)) {
|
||||
// When using a separate pool, always use dedicated mode with namespace isolation.
|
||||
// Shared tables mode can't be used across different engines (e.g. MongoDB UUID
|
||||
// tenants are incompatible with PostgreSQL's integer _tenant column).
|
||||
if ($databaseHost !== $dsn->getHost()) {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $projectDocument->getSequence());
|
||||
|
||||
$poolKey = $databaseHost . ':' . $database->getNamespace();
|
||||
if (!isset($initializedPools[$poolKey])) {
|
||||
try {
|
||||
$database->create();
|
||||
} catch (\Utopia\Database\Exception\Duplicate) {
|
||||
// Schema already exists
|
||||
}
|
||||
$initializedPools[$poolKey] = true;
|
||||
}
|
||||
} elseif (\in_array($dsn->getHost(), $sharedTables, true)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int) $projectDocument->getSequence())
|
||||
->setTenant($projectDocument->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@
|
||||
"utopia-php/locale": "0.8.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/messaging": "0.20.*",
|
||||
"utopia-php/migration": "1.8.*",
|
||||
"utopia-php/migration": "1.9.*",
|
||||
"utopia-php/platform": "0.7.*",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
|
||||
Generated
+19
-19
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f9225f2b580de0ccb796b2fb8c881384",
|
||||
"content-hash": "b5261855586680e467168f527e0634ae",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -161,16 +161,16 @@
|
||||
},
|
||||
{
|
||||
"name": "appwrite/php-runtimes",
|
||||
"version": "0.19.4",
|
||||
"version": "0.19.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/runtimes.git",
|
||||
"reference": "eea9d1b3ca2540eab623b419c8afde09ef406c0b"
|
||||
"reference": "aa2f7760cd0493c0880209b92df812c9386b3546"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/eea9d1b3ca2540eab623b419c8afde09ef406c0b",
|
||||
"reference": "eea9d1b3ca2540eab623b419c8afde09ef406c0b",
|
||||
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/aa2f7760cd0493c0880209b92df812c9386b3546",
|
||||
"reference": "aa2f7760cd0493c0880209b92df812c9386b3546",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -210,9 +210,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/runtimes/issues",
|
||||
"source": "https://github.com/appwrite/runtimes/tree/0.19.4"
|
||||
"source": "https://github.com/appwrite/runtimes/tree/0.19.5"
|
||||
},
|
||||
"time": "2026-02-17T10:04:39+00:00"
|
||||
"time": "2026-04-01T01:39:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -3850,16 +3850,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "5.3.17",
|
||||
"version": "5.3.19",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "cff2b6ed63d3291b74110d086e16ff089fe05993"
|
||||
"reference": "72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/cff2b6ed63d3291b74110d086e16ff089fe05993",
|
||||
"reference": "cff2b6ed63d3291b74110d086e16ff089fe05993",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691",
|
||||
"reference": "72ee1614c37e37c7fdd9d4dc87f1f7cdfa1ca691",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3903,9 +3903,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.17"
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.19"
|
||||
},
|
||||
"time": "2026-03-20T01:18:52+00:00"
|
||||
"time": "2026-03-31T15:52:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -4518,16 +4518,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.8.3",
|
||||
"version": "1.9.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "8633523b3343d492427331b6eec53f020f6ab7a7"
|
||||
"reference": "7a86aeadf182b63a9f4ceba7e137588b31c5d2e2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/8633523b3343d492427331b6eec53f020f6ab7a7",
|
||||
"reference": "8633523b3343d492427331b6eec53f020f6ab7a7",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/7a86aeadf182b63a9f4ceba7e137588b31c5d2e2",
|
||||
"reference": "7a86aeadf182b63a9f4ceba7e137588b31c5d2e2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4567,9 +4567,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.8.3"
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.9.1"
|
||||
},
|
||||
"time": "2026-03-19T09:18:47+00:00"
|
||||
"time": "2026-03-25T07:05:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
|
||||
@@ -1 +1 @@
|
||||
Get the number of audit logs that are waiting to be processed in the Appwrite internal queue server.
|
||||
Get the number of audit logs that are waiting to be processed in the Appwrite internal queue server.
|
||||
|
||||
+2
-523
@@ -186,108 +186,6 @@ parameters:
|
||||
count: 3
|
||||
path: app/worker.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @return with type string is incompatible with native type int\.$#'
|
||||
identifier: return.phpDocType
|
||||
count: 1
|
||||
path: src/Appwrite/Auth/OAuth2.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param references unknown parameter\: \$token$#'
|
||||
identifier: parameter.notFound
|
||||
count: 1
|
||||
path: src/Appwrite/Auth/OAuth2/Disqus.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param references unknown parameter\: \$value$#'
|
||||
identifier: parameter.notFound
|
||||
count: 1
|
||||
path: src/Appwrite/Auth/Validator/PersonalData.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param has invalid value \(DeviceDetector\)\: Unexpected token "\\n ", expected variable at offset 32 on line 2$#'
|
||||
identifier: phpDoc.parseError
|
||||
count: 1
|
||||
path: src/Appwrite/Detector/Detector.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param has invalid value \(string\)\: Unexpected token "\\n ", expected variable at offset 24 on line 2$#'
|
||||
identifier: phpDoc.parseError
|
||||
count: 1
|
||||
path: src/Appwrite/Detector/Detector.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @var above a method has no effect\.$#'
|
||||
identifier: varTag.misplaced
|
||||
count: 1
|
||||
path: src/Appwrite/Docker/Compose.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @var above a method has no effect\.$#'
|
||||
identifier: varTag.misplaced
|
||||
count: 1
|
||||
path: src/Appwrite/Docker/Compose/Service.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @var above a method has no effect\.$#'
|
||||
identifier: varTag.misplaced
|
||||
count: 1
|
||||
path: src/Appwrite/Docker/Env.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param has invalid value \(int port\)\: Unexpected token "port", expected variable at offset 50 on line 4$#'
|
||||
identifier: phpDoc.parseError
|
||||
count: 1
|
||||
path: src/Appwrite/Event/Mail.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param references unknown parameter\: \$password$#'
|
||||
identifier: parameter.notFound
|
||||
count: 1
|
||||
path: src/Appwrite/Event/Mail.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @return with type string is incompatible with native type Appwrite\\Event\\Mail\.$#'
|
||||
identifier: return.phpDocType
|
||||
count: 1
|
||||
path: src/Appwrite/Event/Mail.php
|
||||
|
||||
-
|
||||
message: '#^Method Appwrite\\Event\\Message\\Usage\:\:fromArray\(\) should return static\(Appwrite\\Event\\Message\\Usage\) but returns Appwrite\\Event\\Message\\Usage\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Appwrite/Event/Message/Usage.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param references unknown parameter\: \$message$#'
|
||||
identifier: parameter.notFound
|
||||
count: 1
|
||||
path: src/Appwrite/Event/Messaging.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @return with type string is incompatible with native type Utopia\\Database\\Document\.$#'
|
||||
identifier: return.phpDocType
|
||||
count: 1
|
||||
path: src/Appwrite/Event/Messaging.php
|
||||
|
||||
-
|
||||
message: '#^Method Appwrite\\Functions\\EventProcessor\:\:getFunctionsEvents\(\) should return array\<string, bool\> but returns array\<int\<0, max\>\>\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Appwrite/Functions/EventProcessor.php
|
||||
|
||||
-
|
||||
message: '#^Method Appwrite\\Functions\\EventProcessor\:\:getWebhooksEvents\(\) should return array\<string, bool\> but returns array\<int\<0, max\>\>\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Appwrite/Functions/EventProcessor.php
|
||||
|
||||
-
|
||||
message: '#^Variable \$hostname on left side of \?\? always exists and is not nullable\.$#'
|
||||
identifier: nullCoalesce.variable
|
||||
count: 1
|
||||
path: src/Appwrite/Functions/EventProcessor.php
|
||||
|
||||
-
|
||||
message: '#^Anonymous function has an unused use \$context\.$#'
|
||||
identifier: closure.unusedUse
|
||||
@@ -360,30 +258,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Appwrite/GraphQL/Types/Mapper.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Appwrite\\GraphQL\\Types\\Mapper\:\:\$models through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 3
|
||||
path: src/Appwrite/GraphQL/Types/Mapper.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe call to private method Appwrite\\GraphQL\\Types\\Mapper\:\:getColumnImplementation\(\) through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateMethod
|
||||
count: 2
|
||||
path: src/Appwrite/GraphQL/Types/Mapper.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe call to private method Appwrite\\GraphQL\\Types\\Mapper\:\:getHashOptionsImplementation\(\) through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateMethod
|
||||
count: 1
|
||||
path: src/Appwrite/GraphQL/Types/Mapper.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe call to private method Appwrite\\GraphQL\\Types\\Mapper\:\:getUnionImplementation\(\) through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateMethod
|
||||
count: 1
|
||||
path: src/Appwrite/GraphQL/Types/Mapper.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Appwrite\\Migration\\Version\\V15\:\:documentsIterator\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
@@ -450,12 +324,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Appwrite/OpenSSL/OpenSSL.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param references unknown parameter\: \$projectId$#'
|
||||
identifier: parameter.notFound
|
||||
count: 1
|
||||
path: src/Appwrite/Platform/Action.php
|
||||
|
||||
-
|
||||
message: '#^Variable \$output in empty\(\) always exists and is not falsy\.$#'
|
||||
identifier: empty.variable
|
||||
@@ -1152,12 +1020,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Appwrite/SDK/Specification/Format/Swagger2.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @var above a method has no effect\.$#'
|
||||
identifier: varTag.misplaced
|
||||
count: 2
|
||||
path: src/Appwrite/Template/Template.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param has invalid value \(Document \$this\)\: Unexpected token "\$this", expected variable at offset 69 on line 4$#'
|
||||
identifier: phpDoc.parseError
|
||||
@@ -1170,35 +1032,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Appwrite/Utopia/Database/Documents/User.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe call to private method Appwrite\\Utopia\\Request\\Filters\\V17\:\:appendSymbol\(\) through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateMethod
|
||||
count: 4
|
||||
path: src/Appwrite/Utopia/Request/Filters/V17.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe call to private method Appwrite\\Utopia\\Request\\Filters\\V17\:\:isSpecialChar\(\) through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateMethod
|
||||
count: 1
|
||||
path: src/Appwrite/Utopia/Request/Filters/V17.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @param has invalid value \(callable The callback to show sensitive information for\)\: Unexpected token "The", expected variable at offset 91 on line 4$#'
|
||||
identifier: phpDoc.parseError
|
||||
count: 1
|
||||
path: src/Appwrite/Utopia/Response.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @return with type Appwrite\\Utopia\\Response\\Filter is incompatible with native type array\.$#'
|
||||
identifier: return.phpDocType
|
||||
count: 1
|
||||
path: src/Appwrite/Utopia/Response.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @return with type string is incompatible with native type Utopia\\Database\\Document\.$#'
|
||||
identifier: return.phpDocType
|
||||
count: 1
|
||||
path: src/Appwrite/Utopia/Response/Model/User.php
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Databases\\Legacy\\DatabasesStringTypesTest\:\:\$setupCache through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
@@ -1234,108 +1067,12 @@ parameters:
|
||||
count: 8
|
||||
path: tests/e2e/Services/Functions/FunctionsCustomServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsClientTest\:\:\$cachedDeployment through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 3
|
||||
path: tests/e2e/Services/GraphQL/FunctionsClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsClientTest\:\:\$cachedExecution through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/FunctionsClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsClientTest\:\:\$cachedFunction through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 3
|
||||
path: tests/e2e/Services/GraphQL/FunctionsClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsServerTest\:\:\$cachedDeployment through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/FunctionsServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsServerTest\:\:\$cachedExecution through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/FunctionsServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\FunctionsServerTest\:\:\$cachedFunction through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/FunctionsServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Binary operation "\+" between string and 1 results in an error\.$#'
|
||||
identifier: binaryOp.invalid
|
||||
count: 1
|
||||
path: tests/e2e/Services/GraphQL/Legacy/AbuseTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\Legacy\\DatabaseClientTest\:\:\$bulkData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\Legacy\\DatabaseClientTest\:\:\$collection through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\Legacy\\DatabaseClientTest\:\:\$database through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\Legacy\\DatabaseClientTest\:\:\$document through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/Legacy/DatabaseClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedEmail through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 2
|
||||
path: tests/e2e/Services/GraphQL/MessagingTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedProviders through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 8
|
||||
path: tests/e2e/Services/GraphQL/MessagingTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedPush through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/MessagingTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedSms through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/MessagingTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedSubscriber through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/MessagingTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\MessagingTest\:\:\$cachedTopic through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 9
|
||||
path: tests/e2e/Services/GraphQL/MessagingTest.php
|
||||
|
||||
-
|
||||
message: '#^Variable \$from in empty\(\) is never defined\.$#'
|
||||
identifier: empty.variable
|
||||
@@ -1348,174 +1085,18 @@ parameters:
|
||||
count: 1
|
||||
path: tests/e2e/Services/GraphQL/StorageClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\StorageClientTest\:\:\$cachedBucket through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 3
|
||||
path: tests/e2e/Services/GraphQL/StorageClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\StorageClientTest\:\:\$cachedFile through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/StorageClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Method Tests\\E2E\\Services\\GraphQL\\StorageServerTest\:\:testGetFileDownload\(\) should return array but return statement is missing\.$#'
|
||||
identifier: return.missing
|
||||
count: 1
|
||||
path: tests/e2e/Services/GraphQL/StorageServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\StorageServerTest\:\:\$cachedBucket through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/StorageServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\StorageServerTest\:\:\$cachedFile through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 6
|
||||
path: tests/e2e/Services/GraphQL/StorageServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Binary operation "\+" between string and 1 results in an error\.$#'
|
||||
identifier: binaryOp.invalid
|
||||
count: 1
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/AbuseTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedBooleanColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedBulkData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedDatabase through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedDatetimeColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedEmailColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedEnumColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedFloatColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedIPColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedIndexData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 7
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedIntegerColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedRelationshipColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedRowData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedStringColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedTableData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TablesDB\\DatabaseServerTest\:\:\$cachedURLColumnData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/TablesDB/DatabaseServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsClientTest\:\:\$cachedMembership through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/TeamsClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsClientTest\:\:\$cachedTeam through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 3
|
||||
path: tests/e2e/Services/GraphQL/TeamsClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsServerTest\:\:\$cachedMembership through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/GraphQL/TeamsServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsServerTest\:\:\$cachedTeam through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 3
|
||||
path: tests/e2e/Services/GraphQL/TeamsServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\TeamsServerTest\:\:\$cachedTeamWithPrefs through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 3
|
||||
path: tests/e2e/Services/GraphQL/TeamsServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\UsersTest\:\:\$cachedUser through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 3
|
||||
path: tests/e2e/Services/GraphQL/UsersTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\GraphQL\\UsersTest\:\:\$cachedUserTarget through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 5
|
||||
path: tests/e2e/Services/GraphQL/UsersTest.php
|
||||
|
||||
-
|
||||
message: '#^Variable \$from in empty\(\) is never defined\.$#'
|
||||
identifier: empty.variable
|
||||
@@ -1537,13 +1118,13 @@ parameters:
|
||||
-
|
||||
message: '#^Anonymous function has an unused use \$databaseId\.$#'
|
||||
identifier: closure.unusedUse
|
||||
count: 5
|
||||
count: 6
|
||||
path: tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Anonymous function has an unused use \$tableId\.$#'
|
||||
identifier: closure.unusedUse
|
||||
count: 5
|
||||
count: 6
|
||||
path: tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php
|
||||
|
||||
-
|
||||
@@ -1587,36 +1168,6 @@ parameters:
|
||||
identifier: method.notFound
|
||||
count: 1
|
||||
path: tests/e2e/Services/TablesDB/Permissions/TablesDBPermissionsTeamTest.php
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensConsoleClientTest\:\:\$bucketAndFileData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/Tokens/TokensConsoleClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensConsoleClientTest\:\:\$tokenData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/Tokens/TokensConsoleClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensCustomClientTest\:\:\$bucketAndFileData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/Tokens/TokensCustomClientTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensCustomServerTest\:\:\$bucketAndFileData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/Tokens/TokensCustomServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Tokens\\TokensCustomServerTest\:\:\$tokenData through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
count: 4
|
||||
path: tests/e2e/Services/Tokens/TokensCustomServerTest.php
|
||||
|
||||
-
|
||||
message: '#^Unsafe access to private property Tests\\E2E\\Services\\Users\\UsersCustomServerTest\:\:\$cachedHashedPasswordUsers through static\:\:\.$#'
|
||||
identifier: staticClassAccess.privateProperty
|
||||
@@ -1676,75 +1227,3 @@ parameters:
|
||||
identifier: method.notFound
|
||||
count: 1
|
||||
path: tests/unit/Event/EventTest.php
|
||||
|
||||
-
|
||||
message: '#^Call to method parse\(\) on an unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter\.$#'
|
||||
identifier: class.notFound
|
||||
count: 6
|
||||
path: tests/unit/Utopia/Response/Filters/V16Test.php
|
||||
|
||||
-
|
||||
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V16Test\:\:\$filter \(Tests\\Unit\\Utopia\\Response\\Filters\\Filter\) does not accept Appwrite\\Utopia\\Response\\Filters\\V16\.$#'
|
||||
identifier: assign.propertyType
|
||||
count: 1
|
||||
path: tests/unit/Utopia/Response/Filters/V16Test.php
|
||||
|
||||
-
|
||||
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V16Test\:\:\$filter has unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter as its type\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: tests/unit/Utopia/Response/Filters/V16Test.php
|
||||
|
||||
-
|
||||
message: '#^Call to method parse\(\) on an unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter\.$#'
|
||||
identifier: class.notFound
|
||||
count: 5
|
||||
path: tests/unit/Utopia/Response/Filters/V17Test.php
|
||||
|
||||
-
|
||||
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V17Test\:\:\$filter \(Tests\\Unit\\Utopia\\Response\\Filters\\Filter\) does not accept Appwrite\\Utopia\\Response\\Filters\\V17\.$#'
|
||||
identifier: assign.propertyType
|
||||
count: 1
|
||||
path: tests/unit/Utopia/Response/Filters/V17Test.php
|
||||
|
||||
-
|
||||
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V17Test\:\:\$filter has unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter as its type\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: tests/unit/Utopia/Response/Filters/V17Test.php
|
||||
|
||||
-
|
||||
message: '#^Call to method parse\(\) on an unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter\.$#'
|
||||
identifier: class.notFound
|
||||
count: 4
|
||||
path: tests/unit/Utopia/Response/Filters/V18Test.php
|
||||
|
||||
-
|
||||
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V18Test\:\:\$filter \(Tests\\Unit\\Utopia\\Response\\Filters\\Filter\) does not accept Appwrite\\Utopia\\Response\\Filters\\V18\.$#'
|
||||
identifier: assign.propertyType
|
||||
count: 1
|
||||
path: tests/unit/Utopia/Response/Filters/V18Test.php
|
||||
|
||||
-
|
||||
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V18Test\:\:\$filter has unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter as its type\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: tests/unit/Utopia/Response/Filters/V18Test.php
|
||||
|
||||
-
|
||||
message: '#^Call to method parse\(\) on an unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter\.$#'
|
||||
identifier: class.notFound
|
||||
count: 11
|
||||
path: tests/unit/Utopia/Response/Filters/V19Test.php
|
||||
|
||||
-
|
||||
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V19Test\:\:\$filter \(Tests\\Unit\\Utopia\\Response\\Filters\\Filter\) does not accept Appwrite\\Utopia\\Response\\Filters\\V19\.$#'
|
||||
identifier: assign.propertyType
|
||||
count: 1
|
||||
path: tests/unit/Utopia/Response/Filters/V19Test.php
|
||||
|
||||
-
|
||||
message: '#^Property Tests\\Unit\\Utopia\\Response\\Filters\\V19Test\:\:\$filter has unknown class Tests\\Unit\\Utopia\\Response\\Filters\\Filter as its type\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: tests/unit/Utopia/Response/Filters/V19Test.php
|
||||
|
||||
@@ -155,7 +155,7 @@ abstract class OAuth2
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return string
|
||||
* @return int
|
||||
*/
|
||||
public function getAccessTokenExpiry(string $code): int
|
||||
{
|
||||
|
||||
@@ -108,7 +108,7 @@ class Disqus extends OAuth2
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $token
|
||||
* @param string $accessToken
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -33,7 +33,7 @@ class PersonalData extends Password
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $password
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -6,14 +6,8 @@ use DeviceDetector\DeviceDetector;
|
||||
|
||||
class Detector
|
||||
{
|
||||
/**
|
||||
* @param string
|
||||
*/
|
||||
protected $userAgent = '';
|
||||
|
||||
/**
|
||||
* @param DeviceDetector
|
||||
*/
|
||||
protected $detctor;
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,9 +12,6 @@ class Compose
|
||||
*/
|
||||
protected $compose = [];
|
||||
|
||||
/**
|
||||
* @var string $data
|
||||
*/
|
||||
public function __construct(string $data)
|
||||
{
|
||||
$this->compose = yaml_parse($data);
|
||||
|
||||
@@ -11,9 +11,6 @@ class Service
|
||||
*/
|
||||
protected $service = [];
|
||||
|
||||
/**
|
||||
* @var string $path
|
||||
*/
|
||||
public function __construct(array $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
|
||||
@@ -9,9 +9,6 @@ class Env
|
||||
*/
|
||||
protected $vars = [];
|
||||
|
||||
/**
|
||||
* @var string $data
|
||||
*/
|
||||
public function __construct(string $data)
|
||||
{
|
||||
$data = explode("\n", $data);
|
||||
|
||||
@@ -101,7 +101,8 @@ class Mail extends Event
|
||||
/**
|
||||
* Sets preview for the mail event.
|
||||
*
|
||||
* @return string
|
||||
* @param string $preview
|
||||
* @return self
|
||||
*/
|
||||
public function setPreview(string $preview): self
|
||||
{
|
||||
@@ -115,7 +116,7 @@ class Mail extends Event
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPreview(string $preview): string
|
||||
public function getPreview(): string
|
||||
{
|
||||
return $this->preview;
|
||||
}
|
||||
@@ -181,7 +182,7 @@ class Mail extends Event
|
||||
/**
|
||||
* Set SMTP port
|
||||
*
|
||||
* @param int port
|
||||
* @param int $port
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpPort(int $port): self
|
||||
@@ -217,7 +218,7 @@ class Mail extends Event
|
||||
/**
|
||||
* Set SMTP secure
|
||||
*
|
||||
* @param string $password
|
||||
* @param string $secure
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpSecure(string $secure): self
|
||||
|
||||
@@ -40,7 +40,7 @@ class Usage extends Base
|
||||
*/
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
return new static(
|
||||
project: new Document($data['project'] ?? []),
|
||||
metrics: $data['metrics'] ?? [],
|
||||
reduce: array_map(fn (array $doc) => new Document($doc), $data['reduce'] ?? []),
|
||||
|
||||
@@ -86,7 +86,7 @@ class Messaging extends Event
|
||||
/**
|
||||
* Returns message document for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
* @return Document
|
||||
*/
|
||||
public function getMessage(): Document
|
||||
{
|
||||
@@ -96,7 +96,7 @@ class Messaging extends Event
|
||||
/**
|
||||
* Sets message ID for the messaging event.
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $messageId
|
||||
* @return self
|
||||
*/
|
||||
public function setMessageId(string $messageId): self
|
||||
|
||||
@@ -8,6 +8,18 @@ use Utopia\Database\Query;
|
||||
|
||||
class EventProcessor
|
||||
{
|
||||
/**
|
||||
* @param array<mixed> $events
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
private function getEventMap(array $events): array
|
||||
{
|
||||
return \array_fill_keys(
|
||||
\array_map('strval', \array_unique($events)),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get function events for a project, using Redis cache
|
||||
* @param Document|null $project
|
||||
@@ -26,7 +38,7 @@ class EventProcessor
|
||||
$cacheKey = \sprintf(
|
||||
'%s-cache-%s:%s:%s:project:%s:functions:events',
|
||||
$dbForProject->getCacheName(),
|
||||
$hostname ?? '',
|
||||
$hostname,
|
||||
$dbForProject->getNamespace(),
|
||||
$dbForProject->getTenant(),
|
||||
$project->getId()
|
||||
@@ -36,7 +48,9 @@ class EventProcessor
|
||||
$cachedFunctionEvents = $dbForProject->getCache()->load($cacheKey, $ttl);
|
||||
|
||||
if ($cachedFunctionEvents !== false) {
|
||||
return \json_decode($cachedFunctionEvents, true) ?? [];
|
||||
$decoded = \json_decode($cachedFunctionEvents, true);
|
||||
|
||||
return \is_array($decoded) ? $this->getEventMap(\array_keys($decoded)) : [];
|
||||
}
|
||||
|
||||
$events = [];
|
||||
@@ -63,7 +77,7 @@ class EventProcessor
|
||||
}
|
||||
}
|
||||
|
||||
$uniqueEvents = \array_flip(\array_unique($events));
|
||||
$uniqueEvents = $this->getEventMap($events);
|
||||
$dbForProject->getCache()->save($cacheKey, \json_encode($uniqueEvents));
|
||||
|
||||
return $uniqueEvents;
|
||||
@@ -97,6 +111,6 @@ class EventProcessor
|
||||
}
|
||||
}
|
||||
|
||||
return \array_flip(\array_unique($events));
|
||||
return $this->getEventMap($events);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,16 +101,16 @@ class Mapper
|
||||
|
||||
if (\is_array($modelName)) {
|
||||
foreach ($modelName as $name) {
|
||||
$models[] = static::$models[$name];
|
||||
$models[] = self::$models[$name];
|
||||
}
|
||||
} else {
|
||||
$models[] = static::$models[$modelName];
|
||||
$models[] = self::$models[$modelName];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If single response, get its model and wrap in array
|
||||
$modelName = $responses->getModel();
|
||||
$models = [static::$models[$modelName]];
|
||||
$models = [self::$models[$modelName]];
|
||||
}
|
||||
|
||||
foreach ($models as $model) {
|
||||
@@ -425,7 +425,7 @@ class Mapper
|
||||
'name' => $unionName,
|
||||
'types' => $types,
|
||||
'resolveType' => static function ($object) use ($unionName) {
|
||||
return static::getUnionImplementation($unionName, $object);
|
||||
return self::getUnionImplementation($unionName, $object);
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -440,11 +440,11 @@ class Mapper
|
||||
|
||||
switch ($name) {
|
||||
case 'Attributes':
|
||||
return static::getColumnImplementation($object);
|
||||
return self::getColumnImplementation($object);
|
||||
case 'Columns':
|
||||
return static::getColumnImplementation($object, true);
|
||||
return self::getColumnImplementation($object, true);
|
||||
case 'HashOptions':
|
||||
return static::getHashOptionsImplementation($object);
|
||||
return self::getHashOptionsImplementation($object);
|
||||
}
|
||||
|
||||
throw new Exception('Unknown union type: ' . $name);
|
||||
|
||||
@@ -187,6 +187,20 @@ class V24 extends Migration
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
case 'users':
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->dbForProject, $id, 'impersonator');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create attribute \"impersonator\" in collection {$id}: {$th->getMessage()}");
|
||||
}
|
||||
try {
|
||||
$this->createIndexFromCollection($this->dbForProject, $id, 'impersonator');
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create index \"impersonator\" from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
$this->dbForProject->purgeCachedCollection($id);
|
||||
break;
|
||||
|
||||
case 'teams':
|
||||
try {
|
||||
$this->createAttributeFromCollection($this->dbForProject, $id, 'labels');
|
||||
|
||||
@@ -37,7 +37,7 @@ class Action extends UtopiaAction
|
||||
* Foreach Document
|
||||
* Call provided callback for each document in the collection
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param Database $database
|
||||
* @param string $collection
|
||||
* @param array $queries
|
||||
* @param callable $callback
|
||||
|
||||
@@ -43,6 +43,7 @@ class Install extends Action
|
||||
->param('database', '', new WhiteList(['mongodb', 'mariadb', 'postgresql']), 'Database adapter', true)
|
||||
->param('installId', '', new Text(64, 0), 'Installation ID', true)
|
||||
->param('retryStep', null, new Nullable(new WhiteList([Server::STEP_DOCKER_COMPOSE, Server::STEP_ENV_VARS, Server::STEP_DOCKER_CONTAINERS], true)), 'Retry from step', true)
|
||||
->param('migrate', false, new \Utopia\Validator\Boolean(true), 'Run database migration after upgrade', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('swooleResponse')
|
||||
@@ -64,6 +65,7 @@ class Install extends Action
|
||||
string $database,
|
||||
string $installId,
|
||||
?string $retryStep,
|
||||
bool $migrate,
|
||||
Request $request,
|
||||
Response $response,
|
||||
SwooleResponse $swooleResponse,
|
||||
@@ -321,6 +323,28 @@ class Install extends Action
|
||||
}
|
||||
};
|
||||
|
||||
$responseSent = false;
|
||||
$onComplete = function () use ($wantsStream, $swooleResponse, $response, $installId, $state, &$responseSent) {
|
||||
if ($responseSent) {
|
||||
return;
|
||||
}
|
||||
$responseSent = true;
|
||||
$state->updateGlobalLock($installId, Server::STATUS_COMPLETED);
|
||||
if ($wantsStream) {
|
||||
$this->writeSseEvent($swooleResponse, 'done', ['installId' => $installId, 'success' => true]);
|
||||
usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS);
|
||||
$swooleResponse->write(": keepalive\n\n");
|
||||
usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS);
|
||||
$swooleResponse->end();
|
||||
} else {
|
||||
$response->json([
|
||||
'success' => true,
|
||||
'installId' => $installId,
|
||||
'message' => 'Installation completed successfully',
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
$installer->performInstallation(
|
||||
$httpPort ?: $config->getDefaultHttpPort(),
|
||||
$httpsPort ?: $config->getDefaultHttpsPort(),
|
||||
@@ -331,23 +355,12 @@ class Install extends Action
|
||||
$progress,
|
||||
$retryStep,
|
||||
$config->isUpgrade(),
|
||||
$account
|
||||
$account,
|
||||
$onComplete,
|
||||
$migrate,
|
||||
);
|
||||
|
||||
if ($wantsStream) {
|
||||
$this->writeSseEvent($swooleResponse, 'done', ['installId' => $installId, 'success' => true]);
|
||||
usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS);
|
||||
$swooleResponse->write(": keepalive\n\n");
|
||||
usleep(self::SSE_KEEPALIVE_DELAY_MICROSECONDS);
|
||||
$swooleResponse->end();
|
||||
} else {
|
||||
$response->json([
|
||||
'success' => true,
|
||||
'installId' => $installId,
|
||||
'message' => 'Installation completed successfully',
|
||||
]);
|
||||
}
|
||||
$state->updateGlobalLock($installId, Server::STATUS_COMPLETED);
|
||||
$onComplete();
|
||||
} catch (\Throwable $e) {
|
||||
$this->handleInstallationError($e, $installId, $wantsStream, $response, $swooleResponse, $state);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class View extends Action
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/')
|
||||
->desc('Serve installer UI')
|
||||
->param('step', 1, new Integer(true), 'Step number (1-5)', true)
|
||||
->param('step', 1, new Integer(true), 'Step number (1-6)', true)
|
||||
->param('partial', null, new Nullable(new Text(1, 0)), 'Render partial step only', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -52,10 +52,13 @@ class View extends Action
|
||||
$defaultEmailCertificates = 'walterobrien@example.com';
|
||||
}
|
||||
|
||||
$step = max(1, min(5, $step));
|
||||
$step = max(1, min(6, $step));
|
||||
if ($isUpgrade && ($step === 2 || $step === 3)) {
|
||||
$step = 4;
|
||||
}
|
||||
if (!$isUpgrade && $step === 6) {
|
||||
$step = 4;
|
||||
}
|
||||
|
||||
$partialFile = $paths['views'] . "/installer/templates/steps/step-{$step}.phtml";
|
||||
if (!is_file($partialFile)) {
|
||||
|
||||
@@ -6,6 +6,7 @@ use Appwrite\Platform\Installer\Http\Installer\Error;
|
||||
use Appwrite\Platform\Installer\Runtime\Config;
|
||||
use Appwrite\Platform\Installer\Runtime\State;
|
||||
use Swoole\Http\Server as SwooleServer;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\Http\Adapter\Swoole\Response;
|
||||
use Utopia\Http\Adapter\Swoole\Server as SwooleAdapter;
|
||||
@@ -28,6 +29,7 @@ class Server
|
||||
public const string STEP_DOCKER_COMPOSE = 'docker-compose';
|
||||
public const string STEP_DOCKER_CONTAINERS = 'docker-containers';
|
||||
public const string STEP_ACCOUNT_SETUP = 'account-setup';
|
||||
public const string STEP_MIGRATION = 'migration';
|
||||
public const string STEP_SSL_CERTIFICATE = 'ssl-certificate';
|
||||
|
||||
public const string STATUS_IN_PROGRESS = 'in-progress';
|
||||
@@ -129,6 +131,8 @@ class Server
|
||||
|
||||
private function startSwooleServer(string $host, int $port, ?string $readyFile = null): void
|
||||
{
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
$this->state->clearStaleLock();
|
||||
|
||||
// Preload static files into memory
|
||||
|
||||
@@ -61,16 +61,16 @@ class Create extends Action
|
||||
$databaseKeys = System::getEnv('_APP_DATABASE_DOCUMENTSDB_KEYS', '');
|
||||
$databaseOverride = System::getEnv('_APP_DATABASE_DOCUMENTSDB_OVERRIDE');
|
||||
$dbScheme = System::getEnv('_APP_DB_HOST_DOCUMENTSDB', 'mongodb');
|
||||
$databaseSharedTables = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', ''));
|
||||
$databaseSharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1', ''));
|
||||
$databaseSharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', '')));
|
||||
$databaseSharedTablesV1 = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES_V1', '')));
|
||||
break;
|
||||
case VECTORSDB:
|
||||
$databases = Config::getParam('pools-vectorsdb', []);
|
||||
$databaseKeys = System::getEnv('_APP_DATABASE_VECTORSDB_KEYS', '');
|
||||
$databaseOverride = System::getEnv('_APP_DATABASE_VECTORSDB_OVERRIDE');
|
||||
$dbScheme = System::getEnv('_APP_DB_HOST_VECTORSDB', 'postgresql');
|
||||
$databaseSharedTables = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', ''));
|
||||
$databaseSharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES_V1', ''));
|
||||
$databaseSharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', '')));
|
||||
$databaseSharedTablesV1 = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES_V1', '')));
|
||||
break;
|
||||
default:
|
||||
// legacy/tablesdb
|
||||
@@ -108,7 +108,7 @@ class Create extends Action
|
||||
if ($index !== false) {
|
||||
$selectedDsn = $databases[$index];
|
||||
} else {
|
||||
if (!empty($dsn)) {
|
||||
if (!empty($dsn) && !empty($databaseSharedTables)) {
|
||||
$beforeFilter = \array_values($databases);
|
||||
if ($isSharedTablesV1) {
|
||||
$databases = array_filter($databases, fn ($value) => \in_array($value, $databaseSharedTablesV1));
|
||||
@@ -118,7 +118,10 @@ class Create extends Action
|
||||
$databases = array_filter($databases, fn ($value) => !\in_array($value, $databaseSharedTables));
|
||||
}
|
||||
}
|
||||
$selectedDsn = !empty($databases) ? $databases[array_rand($databases)] : '';
|
||||
if (empty($databases)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "No {$databasetype} database pool available for the current shared-tables mode");
|
||||
}
|
||||
$selectedDsn = $databases[array_rand($databases)];
|
||||
}
|
||||
|
||||
if (\in_array($selectedDsn, $databaseSharedTables)) {
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ class Create extends IndexCreate
|
||||
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
|
||||
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject'])
|
||||
->param('key', null, fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Index Key.', false, ['dbForProject'])
|
||||
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.')
|
||||
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE]), 'Index type.')
|
||||
->param('attributes', null, fn (Database $dbForProject) => new ArrayList(new Key(true, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.', false, ['dbForProject'])
|
||||
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
|
||||
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
|
||||
|
||||
@@ -70,6 +70,8 @@ trait Deployment
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND, 'Repository references non-existent project');
|
||||
}
|
||||
|
||||
$this->beforeCreateGitDeployment($project, $repository, $dbForPlatform, $authorization);
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
$databaseName = $dsn->getHost();
|
||||
@@ -561,6 +563,10 @@ trait Deployment
|
||||
}
|
||||
}
|
||||
|
||||
protected function beforeCreateGitDeployment(Document $project, Document $repository, Database $dbForPlatform, Authorization $authorization): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function getBuildQueueName(Document $project, Database $dbForPlatform, Authorization $authorization): string
|
||||
{
|
||||
return System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME);
|
||||
|
||||
+4
-5
@@ -82,12 +82,11 @@ class Create extends Action
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_DETECTION_RUNTIME,
|
||||
model: [
|
||||
Response::MODEL_DETECTION_RUNTIME,
|
||||
Response::MODEL_DETECTION_FRAMEWORK,
|
||||
],
|
||||
),
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_DETECTION_FRAMEWORK,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
|
||||
@@ -86,12 +86,11 @@ class XList extends Action
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST,
|
||||
model: [
|
||||
Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST,
|
||||
Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST,
|
||||
],
|
||||
),
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
|
||||
@@ -7,6 +7,7 @@ use Appwrite\Docker\Env;
|
||||
use Appwrite\Platform\Installer\Runtime\State;
|
||||
use Appwrite\Platform\Installer\Server as InstallerServer;
|
||||
use Appwrite\Utopia\View;
|
||||
use Swoole\Coroutine;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Config\Config;
|
||||
@@ -35,6 +36,7 @@ class Install extends Action
|
||||
private const string GROWTH_API_URL = 'https://growth.appwrite.io/v1';
|
||||
|
||||
protected bool $isUpgrade = false;
|
||||
protected bool $migrate = false;
|
||||
protected string $hostPath = '';
|
||||
protected ?bool $isLocalInstall = null;
|
||||
protected ?array $installerConfig = null;
|
||||
@@ -211,13 +213,14 @@ class Install extends Action
|
||||
}
|
||||
|
||||
// If interactive and web mode enabled, start web server
|
||||
if ($interactive === 'Y' && Console::isInteractive()) {
|
||||
// Skip the web installer when explicit CLI params are provided
|
||||
if ($interactive === 'Y' && Console::isInteractive() && !$this->hasExplicitCliParams()) {
|
||||
Console::success('Starting web installer...');
|
||||
Console::info('Open your browser at: http://localhost:' . InstallerServer::INSTALLER_WEB_PORT);
|
||||
Console::info('Press Ctrl+C to cancel installation');
|
||||
|
||||
$detectedDb = ($existingInstallation && isset($existingDatabase)) ? $existingDatabase : null;
|
||||
$this->startWebServer($defaultHttpPort, $defaultHttpsPort, $organization, $image, $noStart, $vars, $isUpgrade, $detectedDb);
|
||||
$this->startWebServer($defaultHttpPort, $defaultHttpsPort, $organization, $image, $noStart, $vars, $isUpgrade || $existingInstallation, $detectedDb);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -321,7 +324,7 @@ class Install extends Action
|
||||
|
||||
$shouldGenerateSecrets = !$existingInstallation && !$isUpgrade;
|
||||
$input = $this->prepareEnvironmentVariables($userInput, $vars, $shouldGenerateSecrets);
|
||||
$this->performInstallation($httpPort, $httpsPort, $organization, $image, $input, $noStart, null, null, $isUpgrade);
|
||||
$this->performInstallation($httpPort, $httpsPort, $organization, $image, $input, $noStart, null, null, $isUpgrade, migrate: $this->migrate);
|
||||
}
|
||||
|
||||
|
||||
@@ -510,7 +513,9 @@ class Install extends Action
|
||||
?callable $progress = null,
|
||||
?string $resumeFromStep = null,
|
||||
bool $isUpgrade = false,
|
||||
array $account = []
|
||||
array $account = [],
|
||||
?callable $onComplete = null,
|
||||
bool $migrate = false,
|
||||
): void {
|
||||
$isLocalInstall = $this->isLocalInstall();
|
||||
$this->applyLocalPaths($isLocalInstall, false);
|
||||
@@ -633,8 +638,34 @@ class Install extends Action
|
||||
$this->createInitialAdminAccount($account, $progress, $apiUrl, $domain);
|
||||
}
|
||||
|
||||
// Track installs
|
||||
$this->trackSelfHostedInstall($input, $isUpgrade, $version, $account);
|
||||
if ($isUpgrade && $migrate) {
|
||||
// Allow the containers-completed SSE event to flush
|
||||
// before blocking on migration exec
|
||||
usleep(200_000);
|
||||
$currentStep = InstallerServer::STEP_MIGRATION;
|
||||
$this->runDatabaseMigration($progress, $isLocalInstall);
|
||||
} elseif ($isUpgrade) {
|
||||
$this->updateProgress($progress, InstallerServer::STEP_MIGRATION, InstallerServer::STATUS_COMPLETED, messageOverride: 'Migration skipped');
|
||||
}
|
||||
|
||||
// Signal completion before tracking so the SSE stream
|
||||
// finishes and the frontend can redirect immediately.
|
||||
if ($onComplete) {
|
||||
try {
|
||||
$onComplete();
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
// Run tracking in a coroutine when inside a Swoole
|
||||
// request so it doesn't block the worker.
|
||||
if (Coroutine::getCid() !== -1) {
|
||||
go(function () use ($input, $isUpgrade, $version, $account) {
|
||||
$this->trackSelfHostedInstall($input, $isUpgrade, $version, $account);
|
||||
});
|
||||
} else {
|
||||
$this->trackSelfHostedInstall($input, $isUpgrade, $version, $account);
|
||||
}
|
||||
|
||||
if ($isCLI) {
|
||||
Console::success('Appwrite installed successfully');
|
||||
@@ -726,6 +757,46 @@ class Install extends Action
|
||||
}
|
||||
}
|
||||
|
||||
private function runDatabaseMigration(?callable $progress, bool $isLocalInstall): void
|
||||
{
|
||||
$this->updateProgress(
|
||||
$progress,
|
||||
InstallerServer::STEP_MIGRATION,
|
||||
InstallerServer::STATUS_IN_PROGRESS,
|
||||
messageOverride: 'Running database migration...'
|
||||
);
|
||||
|
||||
// Allow the SSE chunk to flush before the blocking exec
|
||||
usleep(100_000);
|
||||
|
||||
// Static command — no user input involved
|
||||
$command = $isLocalInstall
|
||||
? 'docker compose exec appwrite migrate 2>&1'
|
||||
: 'docker exec appwrite migrate 2>&1';
|
||||
|
||||
$output = [];
|
||||
\exec($command, $output, $exit);
|
||||
|
||||
if ($exit !== 0) {
|
||||
$message = trim(implode("\n", $output));
|
||||
$this->updateProgress(
|
||||
$progress,
|
||||
InstallerServer::STEP_MIGRATION,
|
||||
InstallerServer::STATUS_ERROR,
|
||||
details: ['output' => $message],
|
||||
messageOverride: 'Migration failed: ' . ($message ?: 'exit code ' . $exit)
|
||||
);
|
||||
throw new \RuntimeException('Database migration failed', 0, $message !== '' ? new \RuntimeException($message) : null);
|
||||
}
|
||||
|
||||
$this->updateProgress(
|
||||
$progress,
|
||||
InstallerServer::STEP_MIGRATION,
|
||||
InstallerServer::STATUS_COMPLETED,
|
||||
messageOverride: 'Database migration completed'
|
||||
);
|
||||
}
|
||||
|
||||
private function trackSelfHostedInstall(array $input, bool $isUpgrade, string $version, array $account): void
|
||||
{
|
||||
if ($this->isLocalInstall()) {
|
||||
@@ -753,7 +824,7 @@ class Install extends Action
|
||||
$name = $account['name'] ?? 'Admin';
|
||||
$email = $account['email'] ?? 'admin@selfhosted.local';
|
||||
|
||||
$hostIp = gethostbyname($domain);
|
||||
$hostIp = @gethostbyname($domain);
|
||||
|
||||
$payload = [
|
||||
'action' => $type,
|
||||
@@ -767,7 +838,7 @@ class Install extends Action
|
||||
'email' => $email,
|
||||
'domain' => $domain,
|
||||
'database' => $database,
|
||||
'hostIp' => $hostIp !== $domain ? $hostIp : null,
|
||||
'ip' => ($hostIp !== false && $hostIp !== $domain) ? $hostIp : null,
|
||||
'os' => php_uname('s') . ' ' . php_uname('r'),
|
||||
'arch' => php_uname('m'),
|
||||
'cpus' => ((int) trim((string) \shell_exec('nproc'))) ?: null,
|
||||
@@ -778,6 +849,8 @@ class Install extends Action
|
||||
try {
|
||||
$client = new Client();
|
||||
$client
|
||||
->setConnectTimeout(5000)
|
||||
->setTimeout(5000)
|
||||
->addHeader('Content-Type', 'application/json')
|
||||
->fetch(self::GROWTH_API_URL . '/analytics', Client::METHOD_POST, $payload);
|
||||
} catch (\Throwable) {
|
||||
@@ -1261,6 +1334,22 @@ class Install extends Action
|
||||
$this->hostPath = $this->getInstallerHostPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any installer-specific CLI params were explicitly passed.
|
||||
* When params like --database or --http-port are provided, the user
|
||||
* intends to run in CLI mode rather than launching the web installer.
|
||||
*/
|
||||
private function hasExplicitCliParams(): bool
|
||||
{
|
||||
$argv = $_SERVER['argv'] ?? [];
|
||||
foreach ($argv as $arg) {
|
||||
if (\str_starts_with($arg, '--')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the database adapter from a pre-1.9.0 compose file by
|
||||
* checking which DB service exists or reading _APP_DB_HOST.
|
||||
|
||||
@@ -102,11 +102,14 @@ class SDKs extends Action
|
||||
} else {
|
||||
$sdks = explode(',', $sdks);
|
||||
}
|
||||
$version ??= Console::confirm('Choose an Appwrite version');
|
||||
|
||||
$createRelease = ($release === 'yes');
|
||||
$commitRelease = ($commit === 'yes');
|
||||
|
||||
if ($createRelease && $examplesOnly) {
|
||||
throw new \Exception('Cannot use --release=yes with --mode=examples');
|
||||
}
|
||||
|
||||
if (! $createRelease && ! $examplesOnly) {
|
||||
$git ??= Console::confirm('Should we use git push? (yes/no)');
|
||||
$git = ($git === 'yes');
|
||||
@@ -118,30 +121,34 @@ class SDKs extends Action
|
||||
$prUrls = [];
|
||||
}
|
||||
|
||||
if (! \in_array($version, [
|
||||
'0.6.x',
|
||||
'0.7.x',
|
||||
'0.8.x',
|
||||
'0.9.x',
|
||||
'0.10.x',
|
||||
'0.11.x',
|
||||
'0.12.x',
|
||||
'0.13.x',
|
||||
'0.14.x',
|
||||
'0.15.x',
|
||||
'1.0.x',
|
||||
'1.1.x',
|
||||
'1.2.x',
|
||||
'1.3.x',
|
||||
'1.4.x',
|
||||
'1.5.x',
|
||||
'1.6.x',
|
||||
'1.7.x',
|
||||
'1.8.x',
|
||||
'1.9.x',
|
||||
'latest',
|
||||
])) {
|
||||
throw new \Exception('Unknown version given');
|
||||
if (! $createRelease) {
|
||||
$version ??= Console::confirm('Choose an Appwrite version');
|
||||
|
||||
if (! \in_array($version, [
|
||||
'0.6.x',
|
||||
'0.7.x',
|
||||
'0.8.x',
|
||||
'0.9.x',
|
||||
'0.10.x',
|
||||
'0.11.x',
|
||||
'0.12.x',
|
||||
'0.13.x',
|
||||
'0.14.x',
|
||||
'0.15.x',
|
||||
'1.0.x',
|
||||
'1.1.x',
|
||||
'1.2.x',
|
||||
'1.3.x',
|
||||
'1.4.x',
|
||||
'1.5.x',
|
||||
'1.6.x',
|
||||
'1.7.x',
|
||||
'1.8.x',
|
||||
'1.9.x',
|
||||
'latest',
|
||||
])) {
|
||||
throw new \Exception('Unknown version given');
|
||||
}
|
||||
}
|
||||
|
||||
$selectedPlatforms = ($selectedPlatform === '*' || $selectedPlatform === null) ? null : \array_map('trim', \explode(',', $selectedPlatform));
|
||||
@@ -173,6 +180,124 @@ class SDKs extends Action
|
||||
}
|
||||
|
||||
Console::log('');
|
||||
|
||||
if ($createRelease && ! $examplesOnly) {
|
||||
Console::info("━━━ {$language['name']} SDK ({$platform['name']}, {$language['version']}) ━━━");
|
||||
$changelog = $language['changelog'] ?? '';
|
||||
$changelog = ($changelog) ? \file_get_contents($changelog) : '# Change Log';
|
||||
|
||||
$repoName = $language['gitUserName'] . '/' . $language['gitRepoName'];
|
||||
$releaseVersion = $language['version'];
|
||||
$releaseNotes = $this->extractReleaseNotes($changelog, $releaseVersion);
|
||||
|
||||
if (empty($releaseNotes)) {
|
||||
$releaseNotes = "Release version {$releaseVersion}";
|
||||
}
|
||||
|
||||
$releaseTitle = $releaseVersion;
|
||||
$releaseTarget = $language['repoBranch'] ?? 'main';
|
||||
|
||||
if ($repoName === '/') {
|
||||
Console::warning(' Not a releasable SDK, skipping');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if release already exists
|
||||
$checkReleaseCommand = 'gh release view ' . \escapeshellarg($releaseVersion) . ' --repo ' . \escapeshellarg($repoName) . ' --json url --jq ".url" 2>/dev/null';
|
||||
$existingReleaseUrl = trim(\shell_exec($checkReleaseCommand) ?? '');
|
||||
|
||||
if (! empty($existingReleaseUrl)) {
|
||||
Console::warning(" Release {$releaseVersion} already exists, skipping");
|
||||
Console::log(" {$existingReleaseUrl}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the latest commit on the target branch already has a release
|
||||
$latestCommitCommand = 'gh api repos/' . $repoName . '/commits/' . $releaseTarget . ' --jq ".sha" 2>/dev/null';
|
||||
$latestCommitSha = trim(\shell_exec($latestCommitCommand) ?? '');
|
||||
|
||||
if (! empty($latestCommitSha)) {
|
||||
$latestReleaseTagCommand = 'gh api repos/' . $repoName . '/releases --jq ".[0] | .tag_name" 2>/dev/null';
|
||||
$latestReleaseTag = trim(\shell_exec($latestReleaseTagCommand) ?? '');
|
||||
|
||||
if (! empty($latestReleaseTag)) {
|
||||
$tagCommitCommand = 'gh api repos/' . $repoName . '/git/ref/tags/' . $latestReleaseTag . ' --jq ".object.sha" 2>/dev/null';
|
||||
$tagCommitSha = trim(\shell_exec($tagCommitCommand) ?? '');
|
||||
|
||||
if (! empty($tagCommitSha) && $latestCommitSha === $tagCommitSha) {
|
||||
Console::warning(" Latest commit already released ({$latestReleaseTag}), skipping");
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$previousVersion = '';
|
||||
$tagListCommand = 'gh release list --repo ' . \escapeshellarg($repoName) . ' --limit 1 --json tagName --jq ".[0].tagName" 2>&1';
|
||||
$previousVersion = trim(\shell_exec($tagListCommand) ?? '');
|
||||
|
||||
$formattedNotes = "## What's Changed\n\n";
|
||||
$formattedNotes .= $releaseNotes . "\n\n";
|
||||
|
||||
if (! empty($previousVersion)) {
|
||||
$formattedNotes .= '**Full Changelog**: https://github.com/' . $repoName . '/compare/' . $previousVersion . '...' . $releaseVersion;
|
||||
} else {
|
||||
$formattedNotes .= '**Full Changelog**: https://github.com/' . $repoName . '/releases/tag/' . $releaseVersion;
|
||||
}
|
||||
|
||||
if (! $commitRelease) {
|
||||
Console::info(' [DRY RUN] Would create release:');
|
||||
Console::log(" Repository: {$repoName}");
|
||||
Console::log(" Version: {$releaseVersion}");
|
||||
Console::log(" Title: {$releaseTitle}");
|
||||
Console::log(" Target Branch: {$releaseTarget}");
|
||||
Console::log(' Previous Version: ' . ($previousVersion ?: 'N/A'));
|
||||
Console::log(' Release Notes:');
|
||||
Console::log(' ' . str_replace("\n", "\n ", $formattedNotes));
|
||||
} else {
|
||||
Console::log(" Creating release {$releaseVersion}...");
|
||||
|
||||
$tempNotesFile = \tempnam(\sys_get_temp_dir(), 'release_notes_');
|
||||
\file_put_contents($tempNotesFile, $formattedNotes);
|
||||
|
||||
$releaseCommand = 'gh release create ' . \escapeshellarg($releaseVersion) . ' \
|
||||
--repo ' . \escapeshellarg($repoName) . ' \
|
||||
--title ' . \escapeshellarg($releaseTitle) . ' \
|
||||
--notes-file ' . \escapeshellarg($tempNotesFile) . ' \
|
||||
--target ' . \escapeshellarg($releaseTarget) . ' \
|
||||
2>&1';
|
||||
|
||||
$releaseOutput = [];
|
||||
$releaseReturnCode = 0;
|
||||
\exec($releaseCommand, $releaseOutput, $releaseReturnCode);
|
||||
|
||||
\unlink($tempNotesFile);
|
||||
|
||||
if ($releaseReturnCode === 0) {
|
||||
// Extract release URL from output
|
||||
$releaseUrl = '';
|
||||
foreach ($releaseOutput as $line) {
|
||||
if (strpos($line, 'https://github.com/') !== false) {
|
||||
$releaseUrl = trim($line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Console::success(" Release {$releaseVersion} created");
|
||||
if (! empty($releaseUrl)) {
|
||||
Console::log(" {$releaseUrl}");
|
||||
}
|
||||
} else {
|
||||
$errorMessage = implode("\n", $releaseOutput);
|
||||
Console::error(" Failed to create release: " . $errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::info("━━━ {$language['name']} SDK ({$platform['name']}, {$version}) ━━━");
|
||||
$specFormat = $language['spec'] ?? 'swagger2';
|
||||
$spec = null;
|
||||
@@ -330,119 +455,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
throw new \Exception('Language "' . $language['key'] . '" not supported');
|
||||
}
|
||||
|
||||
if ($createRelease && ! $examplesOnly) {
|
||||
$repoName = $language['gitUserName'] . '/' . $language['gitRepoName'];
|
||||
$releaseVersion = $language['version'];
|
||||
$releaseNotes = $this->extractReleaseNotes($changelog, $releaseVersion);
|
||||
|
||||
if (empty($releaseNotes)) {
|
||||
$releaseNotes = "Release version {$releaseVersion}";
|
||||
}
|
||||
|
||||
$releaseTitle = $releaseVersion;
|
||||
$releaseTarget = $language['repoBranch'] ?? 'main';
|
||||
|
||||
if ($repoName === '/') {
|
||||
Console::warning(' Not a releasable SDK, skipping');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if release already exists
|
||||
$checkReleaseCommand = 'gh release view ' . \escapeshellarg($releaseVersion) . ' --repo ' . \escapeshellarg($repoName) . ' --json url --jq ".url" 2>/dev/null';
|
||||
$existingReleaseUrl = trim(\shell_exec($checkReleaseCommand) ?? '');
|
||||
|
||||
if (! empty($existingReleaseUrl)) {
|
||||
Console::warning(" Release {$releaseVersion} already exists, skipping");
|
||||
Console::log(" {$existingReleaseUrl}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the latest commit on the target branch already has a release
|
||||
$latestCommitCommand = 'gh api repos/' . $repoName . '/commits/' . $releaseTarget . ' --jq ".sha" 2>/dev/null';
|
||||
$latestCommitSha = trim(\shell_exec($latestCommitCommand) ?? '');
|
||||
|
||||
if (! empty($latestCommitSha)) {
|
||||
$latestReleaseTagCommand = 'gh api repos/' . $repoName . '/releases --jq ".[0] | .tag_name" 2>/dev/null';
|
||||
$latestReleaseTag = trim(\shell_exec($latestReleaseTagCommand) ?? '');
|
||||
|
||||
if (! empty($latestReleaseTag)) {
|
||||
$tagCommitCommand = 'gh api repos/' . $repoName . '/git/ref/tags/' . $latestReleaseTag . ' --jq ".object.sha" 2>/dev/null';
|
||||
$tagCommitSha = trim(\shell_exec($tagCommitCommand) ?? '');
|
||||
|
||||
if (! empty($tagCommitSha) && $latestCommitSha === $tagCommitSha) {
|
||||
Console::warning(" Latest commit already released ({$latestReleaseTag}), skipping");
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$previousVersion = '';
|
||||
$tagListCommand = 'gh release list --repo ' . \escapeshellarg($repoName) . ' --limit 1 --json tagName --jq ".[0].tagName" 2>&1';
|
||||
$previousVersion = trim(\shell_exec($tagListCommand) ?? '');
|
||||
|
||||
$formattedNotes = "## What's Changed\n\n";
|
||||
$formattedNotes .= $releaseNotes . "\n\n";
|
||||
|
||||
if (! empty($previousVersion)) {
|
||||
$formattedNotes .= '**Full Changelog**: https://github.com/' . $repoName . '/compare/' . $previousVersion . '...' . $releaseVersion;
|
||||
} else {
|
||||
$formattedNotes .= '**Full Changelog**: https://github.com/' . $repoName . '/releases/tag/' . $releaseVersion;
|
||||
}
|
||||
|
||||
if (! $commitRelease) {
|
||||
Console::info(' [DRY RUN] Would create release:');
|
||||
Console::log(" Repository: {$repoName}");
|
||||
Console::log(" Version: {$releaseVersion}");
|
||||
Console::log(" Title: {$releaseTitle}");
|
||||
Console::log(" Target Branch: {$releaseTarget}");
|
||||
Console::log(' Previous Version: ' . ($previousVersion ?: 'N/A'));
|
||||
Console::log(' Release Notes:');
|
||||
Console::log(' ' . str_replace("\n", "\n ", $formattedNotes));
|
||||
} else {
|
||||
Console::log(" Creating release {$releaseVersion}...");
|
||||
|
||||
$tempNotesFile = \tempnam(\sys_get_temp_dir(), 'release_notes_');
|
||||
\file_put_contents($tempNotesFile, $formattedNotes);
|
||||
|
||||
$releaseCommand = 'gh release create ' . \escapeshellarg($releaseVersion) . ' \
|
||||
--repo ' . \escapeshellarg($repoName) . ' \
|
||||
--title ' . \escapeshellarg($releaseTitle) . ' \
|
||||
--notes-file ' . \escapeshellarg($tempNotesFile) . ' \
|
||||
--target ' . \escapeshellarg($releaseTarget) . ' \
|
||||
2>&1';
|
||||
|
||||
$releaseOutput = [];
|
||||
$releaseReturnCode = 0;
|
||||
\exec($releaseCommand, $releaseOutput, $releaseReturnCode);
|
||||
|
||||
\unlink($tempNotesFile);
|
||||
|
||||
if ($releaseReturnCode === 0) {
|
||||
// Extract release URL from output
|
||||
$releaseUrl = '';
|
||||
foreach ($releaseOutput as $line) {
|
||||
if (strpos($line, 'https://github.com/') !== false) {
|
||||
$releaseUrl = trim($line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Console::success(" Release {$releaseVersion} created");
|
||||
if (! empty($releaseUrl)) {
|
||||
Console::log(" {$releaseUrl}");
|
||||
}
|
||||
} else {
|
||||
$errorMessage = implode("\n", $releaseOutput);
|
||||
Console::error(" Failed to create release: " . $errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::log($examplesOnly
|
||||
? ' Generating examples...'
|
||||
: ' Generating SDK...');
|
||||
|
||||
@@ -347,6 +347,13 @@ class Specs extends Action
|
||||
$keys = $this->getKeys();
|
||||
|
||||
$generatedFiles = [];
|
||||
$endpoint = System::getEnv('_APP_HOME', 'https://appwrite.io');
|
||||
$email = System::getEnv('_APP_SYSTEM_TEAM_EMAIL', 'team@appwrite.io');
|
||||
$specsDir = __DIR__ . '/../../../../app/config/specs';
|
||||
|
||||
if (!is_dir($specsDir) && !@mkdir($specsDir, 0755, true) && !is_dir($specsDir)) {
|
||||
throw new Exception('Failed to create specs directory: ' . $specsDir);
|
||||
}
|
||||
|
||||
foreach ($platforms as $platform) {
|
||||
$routes = [];
|
||||
@@ -443,8 +450,6 @@ class Specs extends Action
|
||||
foreach (['swagger2', 'open-api3'] as $format) {
|
||||
$formatInstance = $this->getFormatInstance($format, $arguments);
|
||||
$specs = new Specification($formatInstance);
|
||||
$endpoint = System::getEnv('_APP_HOME', '[HOSTNAME]');
|
||||
$email = System::getEnv('_APP_SYSTEM_TEAM_EMAIL', APP_EMAIL_TEAM);
|
||||
|
||||
$formatInstance
|
||||
->setParam('name', APP_NAME)
|
||||
@@ -463,36 +468,30 @@ class Specs extends Action
|
||||
->setParam('docs.description', 'Full API docs, specs and tutorials')
|
||||
->setParam('docs.url', $endpoint . '/docs');
|
||||
|
||||
$specsDir = __DIR__ . '/../../../../app/config/specs';
|
||||
$path = $mocks
|
||||
? $specsDir . '/' . $format . '-mocks-' . $platform . '.json'
|
||||
: $specsDir . '/' . $format . '-' . $version . '-' . $platform . '.json';
|
||||
|
||||
if (!is_dir($specsDir)) {
|
||||
if (!mkdir($specsDir, 0755, true)) {
|
||||
throw new Exception('Failed to create specs directory: ' . $specsDir);
|
||||
}
|
||||
$parsedSpecs = $specs->parse();
|
||||
$encodedSpecs = \json_encode($parsedSpecs, JSON_PRETTY_PRINT);
|
||||
|
||||
unset($parsedSpecs);
|
||||
|
||||
if ($encodedSpecs === false) {
|
||||
throw new Exception('Failed to encode ' . ($mocks ? 'mocks ' : '') . 'spec file: ' . \json_last_error_msg());
|
||||
}
|
||||
|
||||
if ($mocks) {
|
||||
$path = $specsDir . '/' . $format . '-mocks-' . $platform . '.json';
|
||||
|
||||
if (!file_put_contents($path, json_encode($specs->parse(), JSON_PRETTY_PRINT))) {
|
||||
throw new Exception('Failed to save mocks spec file: ' . $path);
|
||||
}
|
||||
|
||||
$generatedFiles[] = realpath($path);
|
||||
Console::success('Saved mocks spec file: ' . realpath($path));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $specsDir . '/' . $format . '-' . $version . '-' . $platform . '.json';
|
||||
|
||||
if (!file_put_contents($path, json_encode($specs->parse(), JSON_PRETTY_PRINT))) {
|
||||
throw new Exception('Failed to save spec file: ' . $path);
|
||||
if (\file_put_contents($path, $encodedSpecs) === false) {
|
||||
throw new Exception('Failed to save ' . ($mocks ? 'mocks ' : '') . 'spec file: ' . $path);
|
||||
}
|
||||
|
||||
$generatedFiles[] = realpath($path);
|
||||
Console::success('Saved spec file: ' . realpath($path));
|
||||
Console::success('Saved ' . ($mocks ? 'mocks ' : '') . 'spec file: ' . realpath($path));
|
||||
|
||||
unset($encodedSpecs, $specs, $formatInstance);
|
||||
}
|
||||
|
||||
unset($arguments, $models, $routes, $services);
|
||||
}
|
||||
|
||||
if ($git === 'yes') {
|
||||
|
||||
@@ -30,6 +30,7 @@ class Upgrade extends Install
|
||||
->param('interactive', 'Y', new Text(1), 'Run an interactive session', true)
|
||||
->param('no-start', false, new Boolean(true), 'Run an interactive session', true)
|
||||
->param('database', 'mongodb', new Text(length: 0), 'Database to use (mongodb|mariadb|postgresql)', true)
|
||||
->param('migrate', false, new Boolean(true), 'Run database migration after upgrade', true)
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -40,9 +41,11 @@ class Upgrade extends Install
|
||||
string $image,
|
||||
string $interactive,
|
||||
bool $noStart,
|
||||
string $database
|
||||
string $database,
|
||||
bool $migrate = false,
|
||||
): void {
|
||||
$this->isUpgrade = true;
|
||||
$this->migrate = $migrate;
|
||||
$isLocalInstall = $this->isLocalInstall();
|
||||
$this->applyLocalPaths($isLocalInstall, true);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ use Utopia\Locale\Locale;
|
||||
use Utopia\Migration\Destination;
|
||||
use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
|
||||
use Utopia\Migration\Destinations\CSV as DestinationCSV;
|
||||
use Utopia\Migration\Destinations\JSON as DestinationJSON;
|
||||
use Utopia\Migration\Exception as MigrationException;
|
||||
use Utopia\Migration\Resource;
|
||||
use Utopia\Migration\Resources\Database\Database as ResourceDatabase;
|
||||
@@ -37,6 +38,7 @@ use Utopia\Migration\Source;
|
||||
use Utopia\Migration\Sources\Appwrite as SourceAppwrite;
|
||||
use Utopia\Migration\Sources\CSV;
|
||||
use Utopia\Migration\Sources\Firebase;
|
||||
use Utopia\Migration\Sources\JSON;
|
||||
use Utopia\Migration\Sources\NHost;
|
||||
use Utopia\Migration\Sources\Supabase;
|
||||
use Utopia\Migration\Transfer;
|
||||
@@ -208,8 +210,8 @@ class Migrations extends Action
|
||||
$getDatabasesDB = fn (Document $database): Database =>
|
||||
$this->getDatabasesDBForProject($database);
|
||||
$queries = [];
|
||||
if ($source === SourceAppwrite::getName() && $destination === DestinationCSV::getName()) {
|
||||
$queries = Query::parseQueries($migrationOptions['queries']);
|
||||
if ($source === SourceAppwrite::getName() && in_array($destination, [DestinationCSV::getName(), DestinationJSON::getName()])) {
|
||||
$queries = Query::parseQueries($migrationOptions['queries'] ?? []);
|
||||
}
|
||||
|
||||
$migrationSource = match ($source) {
|
||||
@@ -250,6 +252,12 @@ class Migrations extends Action
|
||||
$this->dbForProject,
|
||||
$getDatabasesDB
|
||||
),
|
||||
JSON::getName() => new JSON(
|
||||
$resourceId,
|
||||
$migrationOptions['path'],
|
||||
$this->deviceForMigrations,
|
||||
$this->dbForProject,
|
||||
),
|
||||
default => throw new \Exception('Invalid source type'),
|
||||
};
|
||||
|
||||
@@ -288,6 +296,13 @@ class Migrations extends Action
|
||||
$options['escape'],
|
||||
$options['header'],
|
||||
),
|
||||
DestinationJSON::getName() => new DestinationJSON(
|
||||
$this->deviceForFiles,
|
||||
$migration->getAttribute('resourceId'),
|
||||
$options['bucketId'] ?? 'default',
|
||||
$options['filename'],
|
||||
$options['columns'] ?? [],
|
||||
),
|
||||
default => throw new \Exception('Invalid destination type'),
|
||||
};
|
||||
}
|
||||
@@ -550,8 +565,9 @@ class Migrations extends Action
|
||||
$destination?->success();
|
||||
$source?->success();
|
||||
}
|
||||
if ($migration->getAttribute('destination') === DestinationCSV::getName()) {
|
||||
$this->handleCSVExportComplete($project, $migration, $queueForMails, $queueForRealtime, $platform, $authorization);
|
||||
$destination_type = $migration->getAttribute('destination');
|
||||
if ($destination_type === DestinationCSV::getName() || $destination_type === DestinationJSON::getName()) {
|
||||
$this->handleDataExportComplete($project, $migration, $queueForMails, $queueForRealtime, $platform, $authorization);
|
||||
}
|
||||
} finally {
|
||||
$source?->cleanup();
|
||||
@@ -583,7 +599,7 @@ class Migrations extends Action
|
||||
* @param Authorization $authorization
|
||||
* @return void
|
||||
*/
|
||||
protected function handleCSVExportComplete(
|
||||
protected function handleDataExportComplete(
|
||||
Document $project,
|
||||
Document $migration,
|
||||
Mail $queueForMails,
|
||||
@@ -608,7 +624,8 @@ class Migrations extends Action
|
||||
throw new \Exception('Bucket not found');
|
||||
}
|
||||
|
||||
$path = $this->deviceForFiles->getPath($bucketId . '/' . $this->sanitizeFilename($filename) . '.csv');
|
||||
$extension = $migration->getAttribute('destination') === DestinationJSON::getName() ? '.json' : '.csv';
|
||||
$path = $this->deviceForFiles->getPath($bucketId . '/' . $this->sanitizeFilename($filename) . $extension);
|
||||
$size = $this->deviceForFiles->getFileSize($path);
|
||||
$mime = $this->deviceForFiles->getFileMimeType($path);
|
||||
$hash = $this->deviceForFiles->getFileHash($path);
|
||||
@@ -632,13 +649,14 @@ class Migrations extends Action
|
||||
$migration->setAttribute('errors', $errors);
|
||||
$migration = $this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
|
||||
$this->sendCSVEmail(
|
||||
$this->sendExportEmail(
|
||||
success: false,
|
||||
project: $project,
|
||||
user: $user,
|
||||
options: $options,
|
||||
queueForMails: $queueForMails,
|
||||
platform: $platform,
|
||||
exportType: $migration->getAttribute('destination') === DestinationJSON::getName() ? 'JSON' : 'CSV',
|
||||
sizeMB: $sizeMB
|
||||
);
|
||||
|
||||
@@ -694,13 +712,14 @@ class Migrations extends Action
|
||||
$migration->setAttribute('options', $options);
|
||||
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
|
||||
$this->sendCSVEmail(
|
||||
$this->sendExportEmail(
|
||||
success: true,
|
||||
project: $project,
|
||||
user: $user,
|
||||
options: $options,
|
||||
queueForMails: $queueForMails,
|
||||
platform: $platform,
|
||||
exportType: $migration->getAttribute('destination') === DestinationJSON::getName() ? 'JSON' : 'CSV',
|
||||
downloadUrl: $downloadUrl
|
||||
);
|
||||
}
|
||||
@@ -719,13 +738,14 @@ class Migrations extends Action
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function sendCSVEmail(
|
||||
protected function sendExportEmail(
|
||||
bool $success,
|
||||
Document $project,
|
||||
Document $user,
|
||||
array $options,
|
||||
Mail $queueForMails,
|
||||
array $platform,
|
||||
string $exportType = 'CSV',
|
||||
string $downloadUrl = '',
|
||||
float $sizeMB = 0.0,
|
||||
): void {
|
||||
@@ -745,15 +765,15 @@ class Migrations extends Action
|
||||
? 'success'
|
||||
: 'failure';
|
||||
|
||||
// Get localized email content
|
||||
$subject = $locale->getText("emails.csvExport.{$emailType}.subject");
|
||||
$preview = $locale->getText("emails.csvExport.{$emailType}.preview");
|
||||
$hello = $locale->getText("emails.csvExport.{$emailType}.hello");
|
||||
$body = $locale->getText("emails.csvExport.{$emailType}.body");
|
||||
$footer = $locale->getText("emails.csvExport.{$emailType}.footer");
|
||||
$thanks = $locale->getText("emails.csvExport.{$emailType}.thanks");
|
||||
$signature = $locale->getText("emails.csvExport.{$emailType}.signature");
|
||||
$buttonText = $success ? $locale->getText("emails.csvExport.{$emailType}.buttonText") : '';
|
||||
// Get localized email content — replace {{type}} with export format (CSV/JSON)
|
||||
$subject = \str_replace('{{type}}', $exportType, $locale->getText("emails.dataExport.{$emailType}.subject"));
|
||||
$preview = \str_replace('{{type}}', $exportType, $locale->getText("emails.dataExport.{$emailType}.preview"));
|
||||
$hello = $locale->getText("emails.dataExport.{$emailType}.hello");
|
||||
$body = $locale->getText("emails.dataExport.{$emailType}.body");
|
||||
$footer = $locale->getText("emails.dataExport.{$emailType}.footer");
|
||||
$thanks = $locale->getText("emails.dataExport.{$emailType}.thanks");
|
||||
$signature = $locale->getText("emails.dataExport.{$emailType}.signature");
|
||||
$buttonText = $success ? $locale->getText("emails.dataExport.{$emailType}.buttonText") : '';
|
||||
|
||||
// Build email body using appropriate template
|
||||
$templatePath = $success
|
||||
@@ -769,6 +789,7 @@ class Migrations extends Action
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{project}}', $project->getAttribute('name'))
|
||||
->setParam('{{user}}', $user->getAttribute('name', $user->getAttribute('email')))
|
||||
->setParam('{{type}}', $exportType)
|
||||
->setParam('{{size}}', $success ? '' : (string)$sizeMB);
|
||||
|
||||
if ($success) {
|
||||
@@ -789,6 +810,7 @@ class Migrations extends Action
|
||||
'terms' => $platform['termsUrl'],
|
||||
'privacy' => $platform['privacyUrl'],
|
||||
'platform' => $platform['platformName'],
|
||||
'type' => $exportType,
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
|
||||
@@ -149,7 +149,7 @@ class Template extends View
|
||||
/**
|
||||
* From Camel Case
|
||||
*
|
||||
* @var string $input
|
||||
* @param string $input
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -167,7 +167,7 @@ class Template extends View
|
||||
/**
|
||||
* From Camel Case to Dash Case
|
||||
*
|
||||
* @var string $input
|
||||
* @param string $input
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -120,11 +120,11 @@ class V17 extends Filter
|
||||
$isArrayStack = !$isStringStack && $stackCount > 0;
|
||||
|
||||
if ($char === static::CHAR_BACKSLASH) {
|
||||
if (!(static::isSpecialChar($filter[$i + 1]))) {
|
||||
static::appendSymbol($isStringStack, $filter[$i], $i, $filter, $currentParam);
|
||||
if (!(self::isSpecialChar($filter[$i + 1]))) {
|
||||
self::appendSymbol($isStringStack, $filter[$i], $i, $filter, $currentParam);
|
||||
}
|
||||
|
||||
static::appendSymbol($isStringStack, $filter[$i + 1], $i, $filter, $currentParam);
|
||||
self::appendSymbol($isStringStack, $filter[$i + 1], $i, $filter, $currentParam);
|
||||
$i++;
|
||||
|
||||
continue;
|
||||
@@ -147,7 +147,7 @@ class V17 extends Filter
|
||||
}
|
||||
|
||||
// Either way, add symbol to builder
|
||||
static::appendSymbol(
|
||||
self::appendSymbol(
|
||||
$isStringStack,
|
||||
$char,
|
||||
$i,
|
||||
@@ -199,7 +199,7 @@ class V17 extends Filter
|
||||
}
|
||||
|
||||
// Value, not relevant to syntax
|
||||
static::appendSymbol(
|
||||
self::appendSymbol(
|
||||
$isStringStack,
|
||||
$char,
|
||||
$i,
|
||||
|
||||
@@ -629,9 +629,9 @@ class Response extends SwooleResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the currently set filter
|
||||
* Return the currently set filters
|
||||
*
|
||||
* @return Filter
|
||||
* @return array<Filter>
|
||||
*/
|
||||
public function getFilters(): array
|
||||
{
|
||||
@@ -661,7 +661,7 @@ class Response extends SwooleResponse
|
||||
/**
|
||||
* Static wrapper to show sensitive data in response
|
||||
*
|
||||
* @param callable The callback to show sensitive information for
|
||||
* @param callable(): array $callback The callback to show sensitive information for
|
||||
* @return array
|
||||
*/
|
||||
public static function showSensitive(callable $callback): array
|
||||
|
||||
@@ -11,34 +11,48 @@ class V21 extends Filter
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
return match ($model) {
|
||||
Response::MODEL_USER => $this->parseUser($content),
|
||||
Response::MODEL_USER_LIST => $this->handleList(
|
||||
$content,
|
||||
'users',
|
||||
fn ($item) => $this->parseUser($item),
|
||||
),
|
||||
Response::MODEL_ACCOUNT => $this->parseUser($content),
|
||||
Response::MODEL_SITE => $this->parseSite($content),
|
||||
Response::MODEL_SITE_LIST => $this->handleList(
|
||||
$content,
|
||||
"sites",
|
||||
'sites',
|
||||
fn ($item) => $this->parseSite($item),
|
||||
),
|
||||
Response::MODEL_FUNCTION => $this->parseFunction($content),
|
||||
Response::MODEL_FUNCTION_LIST => $this->handleList(
|
||||
$content,
|
||||
"functions",
|
||||
'functions',
|
||||
fn ($item) => $this->parseFunction($item),
|
||||
),
|
||||
Response::MODEL_DOCUMENT => $this->parseDocument($content),
|
||||
Response::MODEL_DOCUMENT_LIST => $this->handleList(
|
||||
$content,
|
||||
"documents",
|
||||
'documents',
|
||||
fn ($item) => $this->parseDocument($item),
|
||||
),
|
||||
Response::MODEL_ROW => $this->parseRow($content),
|
||||
Response::MODEL_ROW_LIST => $this->handleList(
|
||||
$content,
|
||||
"rows",
|
||||
'rows',
|
||||
fn ($item) => $this->parseRow($item),
|
||||
),
|
||||
default => $content,
|
||||
};
|
||||
}
|
||||
|
||||
protected function parseUser(array $content): array
|
||||
{
|
||||
unset($content['impersonator']);
|
||||
unset($content['impersonatorUserId']);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseSite(array $content): array
|
||||
{
|
||||
$content = $this->parseSpecs($content);
|
||||
|
||||
@@ -157,9 +157,9 @@ class User extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Collection
|
||||
* Filter user document attributes for response output.
|
||||
*
|
||||
* @return string
|
||||
* @return Document
|
||||
*/
|
||||
public function filter(Document $document): Document
|
||||
{
|
||||
|
||||
@@ -24,8 +24,8 @@ class FunctionsClientTest extends Scope
|
||||
protected function setupFunction(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedFunction[$key])) {
|
||||
return static::$cachedFunction[$key];
|
||||
if (!empty(self::$cachedFunction[$key])) {
|
||||
return self::$cachedFunction[$key];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -79,15 +79,15 @@ class FunctionsClientTest extends Scope
|
||||
$this->assertIsArray($variables['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $variables['body']);
|
||||
|
||||
static::$cachedFunction[$key] = $function;
|
||||
self::$cachedFunction[$key] = $function;
|
||||
return $function;
|
||||
}
|
||||
|
||||
protected function setupDeployment(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedDeployment[$key])) {
|
||||
return static::$cachedDeployment[$key];
|
||||
if (!empty(self::$cachedDeployment[$key])) {
|
||||
return self::$cachedDeployment[$key];
|
||||
}
|
||||
|
||||
$function = $this->setupFunction();
|
||||
@@ -146,15 +146,15 @@ class FunctionsClientTest extends Scope
|
||||
$this->assertEquals('ready', $deployment['status']);
|
||||
}, 60000);
|
||||
|
||||
static::$cachedDeployment[$key] = $deployment;
|
||||
self::$cachedDeployment[$key] = $deployment;
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
protected function setupExecution(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedExecution[$key])) {
|
||||
return static::$cachedExecution[$key];
|
||||
if (!empty(self::$cachedExecution[$key])) {
|
||||
return self::$cachedExecution[$key];
|
||||
}
|
||||
|
||||
$function = $this->setupFunction();
|
||||
@@ -177,8 +177,8 @@ class FunctionsClientTest extends Scope
|
||||
$this->assertIsArray($execution['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $execution['body']);
|
||||
|
||||
static::$cachedExecution[$key] = $execution['body']['data']['functionsCreateExecution'];
|
||||
return static::$cachedExecution[$key];
|
||||
self::$cachedExecution[$key] = $execution['body']['data']['functionsCreateExecution'];
|
||||
return self::$cachedExecution[$key];
|
||||
}
|
||||
|
||||
public function testCreateFunction(): void
|
||||
|
||||
@@ -25,8 +25,8 @@ class FunctionsServerTest extends Scope
|
||||
protected function setupFunction(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedFunction[$key])) {
|
||||
return static::$cachedFunction[$key];
|
||||
if (!empty(self::$cachedFunction[$key])) {
|
||||
return self::$cachedFunction[$key];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -79,15 +79,15 @@ class FunctionsServerTest extends Scope
|
||||
$this->assertIsArray($variables['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $variables['body']);
|
||||
|
||||
static::$cachedFunction[$key] = $function;
|
||||
self::$cachedFunction[$key] = $function;
|
||||
return $function;
|
||||
}
|
||||
|
||||
protected function setupDeployment(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedDeployment[$key])) {
|
||||
return static::$cachedDeployment[$key];
|
||||
if (!empty(self::$cachedDeployment[$key])) {
|
||||
return self::$cachedDeployment[$key];
|
||||
}
|
||||
|
||||
$function = $this->setupFunction();
|
||||
@@ -149,15 +149,15 @@ class FunctionsServerTest extends Scope
|
||||
$this->assertEquals('ready', $deployment['status']);
|
||||
}, 120000);
|
||||
|
||||
static::$cachedDeployment[$key] = $deployment;
|
||||
self::$cachedDeployment[$key] = $deployment;
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
protected function setupExecution(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedExecution[$key])) {
|
||||
return static::$cachedExecution[$key];
|
||||
if (!empty(self::$cachedExecution[$key])) {
|
||||
return self::$cachedExecution[$key];
|
||||
}
|
||||
|
||||
$deployment = $this->setupDeployment();
|
||||
@@ -179,8 +179,8 @@ class FunctionsServerTest extends Scope
|
||||
$this->assertIsArray($execution['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $execution['body']);
|
||||
|
||||
static::$cachedExecution[$key] = $execution['body']['data']['functionsCreateExecution'];
|
||||
return static::$cachedExecution[$key];
|
||||
self::$cachedExecution[$key] = $execution['body']['data']['functionsCreateExecution'];
|
||||
return self::$cachedExecution[$key];
|
||||
}
|
||||
|
||||
public function testCreateFunction(): void
|
||||
@@ -496,8 +496,8 @@ class FunctionsServerTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedDeployment[$key] = [];
|
||||
static::$cachedExecution[$key] = [];
|
||||
self::$cachedDeployment[$key] = [];
|
||||
self::$cachedExecution[$key] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,6 +529,6 @@ class FunctionsServerTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedFunction[$key] = [];
|
||||
self::$cachedFunction[$key] = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ class DatabaseClientTest extends Scope
|
||||
protected function setupDatabase(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$database[$cacheKey])) {
|
||||
return static::$database[$cacheKey];
|
||||
if (!empty(self::$database[$cacheKey])) {
|
||||
return self::$database[$cacheKey];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -71,9 +71,9 @@ class DatabaseClientTest extends Scope
|
||||
}
|
||||
|
||||
$this->assertIsArray($database['body']['data']);
|
||||
static::$database[$cacheKey] = $database['body']['data']['databasesCreate'];
|
||||
self::$database[$cacheKey] = $database['body']['data']['databasesCreate'];
|
||||
|
||||
return static::$database[$cacheKey];
|
||||
return self::$database[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,8 +82,8 @@ class DatabaseClientTest extends Scope
|
||||
protected function setupCollection(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$collection[$cacheKey])) {
|
||||
return static::$collection[$cacheKey];
|
||||
if (!empty(self::$collection[$cacheKey])) {
|
||||
return self::$collection[$cacheKey];
|
||||
}
|
||||
|
||||
$database = $this->setupDatabase();
|
||||
@@ -121,12 +121,12 @@ class DatabaseClientTest extends Scope
|
||||
|
||||
$this->assertIsArray($collection['body']['data']);
|
||||
|
||||
static::$collection[$cacheKey] = [
|
||||
self::$collection[$cacheKey] = [
|
||||
'database' => $database,
|
||||
'collection' => $collection['body']['data']['databasesCreateCollection'],
|
||||
];
|
||||
|
||||
return static::$collection[$cacheKey];
|
||||
return self::$collection[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,8 +206,8 @@ class DatabaseClientTest extends Scope
|
||||
protected function setupDocument(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$document[$cacheKey])) {
|
||||
return static::$document[$cacheKey];
|
||||
if (!empty(self::$document[$cacheKey])) {
|
||||
return self::$document[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupAttributes();
|
||||
@@ -256,13 +256,13 @@ class DatabaseClientTest extends Scope
|
||||
$this->assertArrayNotHasKey('errors', $document['body']);
|
||||
$this->assertIsArray($document['body']['data']);
|
||||
|
||||
static::$document[$cacheKey] = [
|
||||
self::$document[$cacheKey] = [
|
||||
'database' => $data['database'],
|
||||
'collection' => $data['collection'],
|
||||
'document' => $document['body']['data']['databasesCreateDocument'],
|
||||
];
|
||||
|
||||
return static::$document[$cacheKey];
|
||||
return self::$document[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,8 +271,8 @@ class DatabaseClientTest extends Scope
|
||||
protected function setupBulkData(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$bulkData[$cacheKey])) {
|
||||
return static::$bulkData[$cacheKey];
|
||||
if (!empty(self::$bulkData[$cacheKey])) {
|
||||
return self::$bulkData[$cacheKey];
|
||||
}
|
||||
|
||||
$project = $this->getProject();
|
||||
@@ -352,13 +352,13 @@ class DatabaseClientTest extends Scope
|
||||
$this->assertArrayNotHasKey('errors', $res['body']);
|
||||
$this->assertCount(10, $res['body']['data']['databasesCreateDocuments']['documents']);
|
||||
|
||||
static::$bulkData[$cacheKey] = [
|
||||
self::$bulkData[$cacheKey] = [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'projectId' => $projectId,
|
||||
];
|
||||
|
||||
return static::$bulkData[$cacheKey];
|
||||
return self::$bulkData[$cacheKey];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,8 +26,8 @@ class MessagingTest extends Scope
|
||||
protected function setupProviders(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedProviders[$key])) {
|
||||
return static::$cachedProviders[$key];
|
||||
if (!empty(self::$cachedProviders[$key])) {
|
||||
return self::$cachedProviders[$key];
|
||||
}
|
||||
|
||||
$providersParams = [
|
||||
@@ -128,15 +128,15 @@ class MessagingTest extends Scope
|
||||
$this->assertEquals($providersParams[$providerKey]['name'], $response['body']['data']['messagingCreate' . $providerKey . 'Provider']['name']);
|
||||
}
|
||||
|
||||
static::$cachedProviders[$key] = $providers;
|
||||
self::$cachedProviders[$key] = $providers;
|
||||
return $providers;
|
||||
}
|
||||
|
||||
protected function setupUpdatedProviders(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'] . '_updated';
|
||||
if (!empty(static::$cachedProviders[$key])) {
|
||||
return static::$cachedProviders[$key];
|
||||
if (!empty(self::$cachedProviders[$key])) {
|
||||
return self::$cachedProviders[$key];
|
||||
}
|
||||
|
||||
$providers = $this->setupProviders();
|
||||
@@ -247,15 +247,15 @@ class MessagingTest extends Scope
|
||||
$this->assertEquals('Mailgun2', $response['body']['data']['messagingUpdateMailgunProvider']['name']);
|
||||
$this->assertEquals(false, $response['body']['data']['messagingUpdateMailgunProvider']['enabled']);
|
||||
|
||||
static::$cachedProviders[$key] = $providers;
|
||||
self::$cachedProviders[$key] = $providers;
|
||||
return $providers;
|
||||
}
|
||||
|
||||
protected function setupTopic(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedTopic[$key])) {
|
||||
return static::$cachedTopic[$key];
|
||||
if (!empty(self::$cachedTopic[$key])) {
|
||||
return self::$cachedTopic[$key];
|
||||
}
|
||||
|
||||
$query = $this->getQuery(self::CREATE_TOPIC);
|
||||
@@ -275,15 +275,15 @@ class MessagingTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('topic1', $response['body']['data']['messagingCreateTopic']['name']);
|
||||
|
||||
static::$cachedTopic[$key] = $response['body']['data']['messagingCreateTopic'];
|
||||
return static::$cachedTopic[$key];
|
||||
self::$cachedTopic[$key] = $response['body']['data']['messagingCreateTopic'];
|
||||
return self::$cachedTopic[$key];
|
||||
}
|
||||
|
||||
protected function setupUpdatedTopic(): string
|
||||
{
|
||||
$key = $this->getProject()['$id'] . '_updated';
|
||||
if (!empty(static::$cachedTopic[$key])) {
|
||||
return static::$cachedTopic[$key];
|
||||
if (!empty(self::$cachedTopic[$key])) {
|
||||
return self::$cachedTopic[$key];
|
||||
}
|
||||
|
||||
$topic = $this->setupTopic();
|
||||
@@ -306,15 +306,15 @@ class MessagingTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('topic2', $response['body']['data']['messagingUpdateTopic']['name']);
|
||||
|
||||
static::$cachedTopic[$key] = $topicId;
|
||||
self::$cachedTopic[$key] = $topicId;
|
||||
return $topicId;
|
||||
}
|
||||
|
||||
protected function setupSubscriber(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedSubscriber[$key])) {
|
||||
return static::$cachedSubscriber[$key];
|
||||
if (!empty(self::$cachedSubscriber[$key])) {
|
||||
return self::$cachedSubscriber[$key];
|
||||
}
|
||||
|
||||
$topic = $this->setupTopic();
|
||||
@@ -386,15 +386,15 @@ class MessagingTest extends Scope
|
||||
$this->assertEquals($response['body']['data']['messagingCreateSubscriber']['targetId'], $targetId);
|
||||
$this->assertEquals($response['body']['data']['messagingCreateSubscriber']['target']['userId'], $userId);
|
||||
|
||||
static::$cachedSubscriber[$key] = $response['body']['data']['messagingCreateSubscriber'];
|
||||
return static::$cachedSubscriber[$key];
|
||||
self::$cachedSubscriber[$key] = $response['body']['data']['messagingCreateSubscriber'];
|
||||
return self::$cachedSubscriber[$key];
|
||||
}
|
||||
|
||||
protected function setupEmail(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedEmail[$key])) {
|
||||
return static::$cachedEmail[$key];
|
||||
if (!empty(self::$cachedEmail[$key])) {
|
||||
return self::$cachedEmail[$key];
|
||||
}
|
||||
|
||||
if (empty(System::getEnv('_APP_MESSAGE_EMAIL_TEST_DSN'))) {
|
||||
@@ -550,15 +550,15 @@ class MessagingTest extends Scope
|
||||
$this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']);
|
||||
$this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors']));
|
||||
|
||||
static::$cachedEmail[$key] = $message['body']['data']['messagingGetMessage'];
|
||||
return static::$cachedEmail[$key];
|
||||
self::$cachedEmail[$key] = $message['body']['data']['messagingGetMessage'];
|
||||
return self::$cachedEmail[$key];
|
||||
}
|
||||
|
||||
protected function setupSms(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedSms[$key])) {
|
||||
return static::$cachedSms[$key];
|
||||
if (!empty(self::$cachedSms[$key])) {
|
||||
return self::$cachedSms[$key];
|
||||
}
|
||||
|
||||
if (empty(System::getEnv('_APP_MESSAGE_SMS_TEST_DSN'))) {
|
||||
@@ -709,15 +709,15 @@ class MessagingTest extends Scope
|
||||
$this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']);
|
||||
$this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors']));
|
||||
|
||||
static::$cachedSms[$key] = $message['body']['data']['messagingGetMessage'];
|
||||
return static::$cachedSms[$key];
|
||||
self::$cachedSms[$key] = $message['body']['data']['messagingGetMessage'];
|
||||
return self::$cachedSms[$key];
|
||||
}
|
||||
|
||||
protected function setupPush(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedPush[$key])) {
|
||||
return static::$cachedPush[$key];
|
||||
if (!empty(self::$cachedPush[$key])) {
|
||||
return self::$cachedPush[$key];
|
||||
}
|
||||
|
||||
if (empty(System::getEnv('_APP_MESSAGE_PUSH_TEST_DSN'))) {
|
||||
@@ -870,8 +870,8 @@ class MessagingTest extends Scope
|
||||
$this->assertEquals(1, $message['body']['data']['messagingGetMessage']['deliveredTotal']);
|
||||
$this->assertEquals(0, \count($message['body']['data']['messagingGetMessage']['deliveryErrors']));
|
||||
|
||||
static::$cachedPush[$key] = $message['body']['data']['messagingGetMessage'];
|
||||
return static::$cachedPush[$key];
|
||||
self::$cachedPush[$key] = $message['body']['data']['messagingGetMessage'];
|
||||
return self::$cachedPush[$key];
|
||||
}
|
||||
|
||||
public function testCreateProviders(): void
|
||||
@@ -945,8 +945,8 @@ class MessagingTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedProviders[$key] = [];
|
||||
static::$cachedProviders[$key . '_updated'] = [];
|
||||
self::$cachedProviders[$key] = [];
|
||||
self::$cachedProviders[$key . '_updated'] = [];
|
||||
}
|
||||
|
||||
public function testCreateTopic(): void
|
||||
@@ -1081,7 +1081,7 @@ class MessagingTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedSubscriber[$key] = [];
|
||||
self::$cachedSubscriber[$key] = [];
|
||||
}
|
||||
|
||||
public function testDeleteTopic()
|
||||
@@ -1105,8 +1105,8 @@ class MessagingTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedTopic[$key] = [];
|
||||
static::$cachedTopic[$key . '_updated'] = [];
|
||||
self::$cachedTopic[$key] = [];
|
||||
self::$cachedTopic[$key . '_updated'] = [];
|
||||
}
|
||||
|
||||
public function testSendEmail(): void
|
||||
|
||||
@@ -23,8 +23,8 @@ class StorageClientTest extends Scope
|
||||
protected function setupBucket(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedBucket[$key])) {
|
||||
return static::$cachedBucket[$key];
|
||||
if (!empty(self::$cachedBucket[$key])) {
|
||||
return self::$cachedBucket[$key];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -55,15 +55,15 @@ class StorageClientTest extends Scope
|
||||
$bucket = $bucket['body']['data']['storageCreateBucket'];
|
||||
$this->assertEquals('Actors', $bucket['name']);
|
||||
|
||||
static::$cachedBucket[$key] = $bucket;
|
||||
self::$cachedBucket[$key] = $bucket;
|
||||
return $bucket;
|
||||
}
|
||||
|
||||
protected function setupFile(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedFile[$key])) {
|
||||
return static::$cachedFile[$key];
|
||||
if (!empty(self::$cachedFile[$key])) {
|
||||
return self::$cachedFile[$key];
|
||||
}
|
||||
|
||||
$bucket = $this->setupBucket();
|
||||
@@ -99,8 +99,8 @@ class StorageClientTest extends Scope
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
|
||||
static::$cachedFile[$key] = $file['body']['data']['storageCreateFile'];
|
||||
return static::$cachedFile[$key];
|
||||
self::$cachedFile[$key] = $file['body']['data']['storageCreateFile'];
|
||||
return self::$cachedFile[$key];
|
||||
}
|
||||
|
||||
public function testCreateBucket(): void
|
||||
@@ -319,6 +319,6 @@ class StorageClientTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedFile[$key] = [];
|
||||
self::$cachedFile[$key] = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ class StorageServerTest extends Scope
|
||||
protected function setupBucket(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedBucket[$key])) {
|
||||
return static::$cachedBucket[$key];
|
||||
if (!empty(self::$cachedBucket[$key])) {
|
||||
return self::$cachedBucket[$key];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -54,15 +54,15 @@ class StorageServerTest extends Scope
|
||||
$bucket = $bucket['body']['data']['storageCreateBucket'];
|
||||
$this->assertEquals('Actors', $bucket['name']);
|
||||
|
||||
static::$cachedBucket[$key] = $bucket;
|
||||
self::$cachedBucket[$key] = $bucket;
|
||||
return $bucket;
|
||||
}
|
||||
|
||||
protected function setupFile(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedFile[$key])) {
|
||||
return static::$cachedFile[$key];
|
||||
if (!empty(self::$cachedFile[$key])) {
|
||||
return self::$cachedFile[$key];
|
||||
}
|
||||
|
||||
$bucket = $this->setupBucket();
|
||||
@@ -98,8 +98,8 @@ class StorageServerTest extends Scope
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
|
||||
static::$cachedFile[$key] = $file['body']['data']['storageCreateFile'];
|
||||
return static::$cachedFile[$key];
|
||||
self::$cachedFile[$key] = $file['body']['data']['storageCreateFile'];
|
||||
return self::$cachedFile[$key];
|
||||
}
|
||||
|
||||
public function testCreateBucket(): void
|
||||
@@ -413,7 +413,7 @@ class StorageServerTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedFile[$key] = [];
|
||||
self::$cachedFile[$key] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,7 +443,7 @@ class StorageServerTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedBucket[$key] = [];
|
||||
static::$cachedFile[$key] = [];
|
||||
self::$cachedBucket[$key] = [];
|
||||
self::$cachedFile[$key] = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupDatabase(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedDatabase[$cacheKey])) {
|
||||
return static::$cachedDatabase[$cacheKey];
|
||||
if (!empty(self::$cachedDatabase[$cacheKey])) {
|
||||
return self::$cachedDatabase[$cacheKey];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -62,15 +62,15 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $database['body']);
|
||||
|
||||
static::$cachedDatabase[$cacheKey] = $database['body']['data']['tablesDBCreate'];
|
||||
return static::$cachedDatabase[$cacheKey];
|
||||
self::$cachedDatabase[$cacheKey] = $database['body']['data']['tablesDBCreate'];
|
||||
return self::$cachedDatabase[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupTable(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedTableData[$cacheKey])) {
|
||||
return static::$cachedTableData[$cacheKey];
|
||||
if (!empty(self::$cachedTableData[$cacheKey])) {
|
||||
return self::$cachedTableData[$cacheKey];
|
||||
}
|
||||
|
||||
$database = $this->setupDatabase();
|
||||
@@ -124,20 +124,20 @@ class DatabaseServerTest extends Scope
|
||||
$this->assertArrayNotHasKey('errors', $table2['body']);
|
||||
$table2 = $table2['body']['data']['tablesDBCreateTable'];
|
||||
|
||||
static::$cachedTableData[$cacheKey] = [
|
||||
self::$cachedTableData[$cacheKey] = [
|
||||
'database' => $database,
|
||||
'table' => $table,
|
||||
'table2' => $table2,
|
||||
];
|
||||
|
||||
return static::$cachedTableData[$cacheKey];
|
||||
return self::$cachedTableData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupStringColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedStringColumnData[$cacheKey])) {
|
||||
return static::$cachedStringColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedStringColumnData[$cacheKey])) {
|
||||
return self::$cachedStringColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -159,8 +159,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedStringColumnData[$cacheKey] = $data;
|
||||
return static::$cachedStringColumnData[$cacheKey];
|
||||
self::$cachedStringColumnData[$cacheKey] = $data;
|
||||
return self::$cachedStringColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedStringColumn(): array
|
||||
@@ -205,8 +205,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupIntegerColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedIntegerColumnData[$cacheKey])) {
|
||||
return static::$cachedIntegerColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedIntegerColumnData[$cacheKey])) {
|
||||
return self::$cachedIntegerColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -229,8 +229,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedIntegerColumnData[$cacheKey] = $data;
|
||||
return static::$cachedIntegerColumnData[$cacheKey];
|
||||
self::$cachedIntegerColumnData[$cacheKey] = $data;
|
||||
return self::$cachedIntegerColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedIntegerColumn(): array
|
||||
@@ -275,8 +275,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupBooleanColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedBooleanColumnData[$cacheKey])) {
|
||||
return static::$cachedBooleanColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedBooleanColumnData[$cacheKey])) {
|
||||
return self::$cachedBooleanColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -297,8 +297,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedBooleanColumnData[$cacheKey] = $data;
|
||||
return static::$cachedBooleanColumnData[$cacheKey];
|
||||
self::$cachedBooleanColumnData[$cacheKey] = $data;
|
||||
return self::$cachedBooleanColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedBooleanColumn(): array
|
||||
@@ -341,8 +341,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupFloatColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedFloatColumnData[$cacheKey])) {
|
||||
return static::$cachedFloatColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedFloatColumnData[$cacheKey])) {
|
||||
return self::$cachedFloatColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -366,8 +366,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedFloatColumnData[$cacheKey] = $data;
|
||||
return static::$cachedFloatColumnData[$cacheKey];
|
||||
self::$cachedFloatColumnData[$cacheKey] = $data;
|
||||
return self::$cachedFloatColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedFloatColumn(): array
|
||||
@@ -412,8 +412,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupEmailColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedEmailColumnData[$cacheKey])) {
|
||||
return static::$cachedEmailColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedEmailColumnData[$cacheKey])) {
|
||||
return self::$cachedEmailColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -434,8 +434,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedEmailColumnData[$cacheKey] = $data;
|
||||
return static::$cachedEmailColumnData[$cacheKey];
|
||||
self::$cachedEmailColumnData[$cacheKey] = $data;
|
||||
return self::$cachedEmailColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedEmailColumn(): array
|
||||
@@ -478,8 +478,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupEnumColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedEnumColumnData[$cacheKey])) {
|
||||
return static::$cachedEnumColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedEnumColumnData[$cacheKey])) {
|
||||
return self::$cachedEnumColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -505,8 +505,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedEnumColumnData[$cacheKey] = $data;
|
||||
return static::$cachedEnumColumnData[$cacheKey];
|
||||
self::$cachedEnumColumnData[$cacheKey] = $data;
|
||||
return self::$cachedEnumColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedEnumColumn(): array
|
||||
@@ -554,8 +554,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupDatetimeColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedDatetimeColumnData[$cacheKey])) {
|
||||
return static::$cachedDatetimeColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedDatetimeColumnData[$cacheKey])) {
|
||||
return self::$cachedDatetimeColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -576,8 +576,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedDatetimeColumnData[$cacheKey] = $data;
|
||||
return static::$cachedDatetimeColumnData[$cacheKey];
|
||||
self::$cachedDatetimeColumnData[$cacheKey] = $data;
|
||||
return self::$cachedDatetimeColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedDatetimeColumn(): array
|
||||
@@ -620,8 +620,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupRelationshipColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedRelationshipColumnData[$cacheKey])) {
|
||||
return static::$cachedRelationshipColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedRelationshipColumnData[$cacheKey])) {
|
||||
return self::$cachedRelationshipColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -645,8 +645,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedRelationshipColumnData[$cacheKey] = $data;
|
||||
return static::$cachedRelationshipColumnData[$cacheKey];
|
||||
self::$cachedRelationshipColumnData[$cacheKey] = $data;
|
||||
return self::$cachedRelationshipColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedRelationshipColumn(): array
|
||||
@@ -688,8 +688,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupIPColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedIPColumnData[$cacheKey])) {
|
||||
return static::$cachedIPColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedIPColumnData[$cacheKey])) {
|
||||
return self::$cachedIPColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -711,8 +711,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedIPColumnData[$cacheKey] = $data;
|
||||
return static::$cachedIPColumnData[$cacheKey];
|
||||
self::$cachedIPColumnData[$cacheKey] = $data;
|
||||
return self::$cachedIPColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedIPColumn(): array
|
||||
@@ -755,8 +755,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupURLColumn(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedURLColumnData[$cacheKey])) {
|
||||
return static::$cachedURLColumnData[$cacheKey];
|
||||
if (!empty(self::$cachedURLColumnData[$cacheKey])) {
|
||||
return self::$cachedURLColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
$data = $this->setupTable();
|
||||
@@ -778,8 +778,8 @@ class DatabaseServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
static::$cachedURLColumnData[$cacheKey] = $data;
|
||||
return static::$cachedURLColumnData[$cacheKey];
|
||||
self::$cachedURLColumnData[$cacheKey] = $data;
|
||||
return self::$cachedURLColumnData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupUpdatedURLColumn(): array
|
||||
@@ -822,8 +822,8 @@ class DatabaseServerTest extends Scope
|
||||
protected function setupIndex(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedIndexData[$cacheKey])) {
|
||||
return static::$cachedIndexData[$cacheKey];
|
||||
if (!empty(self::$cachedIndexData[$cacheKey])) {
|
||||
return self::$cachedIndexData[$cacheKey];
|
||||
}
|
||||
|
||||
// Need updated string and integer columns first
|
||||
@@ -855,31 +855,31 @@ class DatabaseServerTest extends Scope
|
||||
if (isset($index['body']['errors'])) {
|
||||
$errorMessage = $index['body']['errors'][0]['message'] ?? '';
|
||||
if (strpos($errorMessage, 'already exists') !== false || strpos($errorMessage, 'Document with the requested ID already exists') !== false) {
|
||||
static::$cachedIndexData[$cacheKey] = [
|
||||
self::$cachedIndexData[$cacheKey] = [
|
||||
'database' => $data['database'],
|
||||
'table' => $data['table'],
|
||||
'index' => ['key' => 'index'],
|
||||
];
|
||||
return static::$cachedIndexData[$cacheKey];
|
||||
return self::$cachedIndexData[$cacheKey];
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $index['body']);
|
||||
|
||||
static::$cachedIndexData[$cacheKey] = [
|
||||
self::$cachedIndexData[$cacheKey] = [
|
||||
'database' => $data['database'],
|
||||
'table' => $data['table'],
|
||||
'index' => $index['body']['data']['tablesDBCreateIndex'],
|
||||
];
|
||||
|
||||
return static::$cachedIndexData[$cacheKey];
|
||||
return self::$cachedIndexData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupRow(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedRowData[$cacheKey])) {
|
||||
return static::$cachedRowData[$cacheKey];
|
||||
if (!empty(self::$cachedRowData[$cacheKey])) {
|
||||
return self::$cachedRowData[$cacheKey];
|
||||
}
|
||||
|
||||
// Need all columns that the row data references
|
||||
@@ -940,20 +940,20 @@ class DatabaseServerTest extends Scope
|
||||
$this->assertArrayNotHasKey('errors', $row['body']);
|
||||
$row = $row['body']['data']['tablesDBCreateRow'];
|
||||
|
||||
static::$cachedRowData[$cacheKey] = [
|
||||
self::$cachedRowData[$cacheKey] = [
|
||||
'database' => $data['database'],
|
||||
'table' => $data['table'],
|
||||
'row' => $row,
|
||||
];
|
||||
|
||||
return static::$cachedRowData[$cacheKey];
|
||||
return self::$cachedRowData[$cacheKey];
|
||||
}
|
||||
|
||||
protected function setupBulkData(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(static::$cachedBulkData[$cacheKey])) {
|
||||
return static::$cachedBulkData[$cacheKey];
|
||||
if (!empty(self::$cachedBulkData[$cacheKey])) {
|
||||
return self::$cachedBulkData[$cacheKey];
|
||||
}
|
||||
|
||||
$project = $this->getProject();
|
||||
@@ -1034,9 +1034,9 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
$this->client->call(Client::METHOD_POST, '/graphql', $headers, $payload);
|
||||
|
||||
static::$cachedBulkData[$cacheKey] = compact('databaseId', 'tableId', 'projectId');
|
||||
self::$cachedBulkData[$cacheKey] = compact('databaseId', 'tableId', 'projectId');
|
||||
|
||||
return static::$cachedBulkData[$cacheKey];
|
||||
return self::$cachedBulkData[$cacheKey];
|
||||
}
|
||||
|
||||
public function testCreateDatabase(): void
|
||||
@@ -1088,7 +1088,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedStringColumnData[$cacheKey] = $data;
|
||||
self::$cachedStringColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1166,7 +1166,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedIntegerColumnData[$cacheKey] = $data;
|
||||
self::$cachedIntegerColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1246,7 +1246,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedBooleanColumnData[$cacheKey] = $data;
|
||||
self::$cachedBooleanColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1325,7 +1325,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedFloatColumnData[$cacheKey] = $data;
|
||||
self::$cachedFloatColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1405,7 +1405,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedEmailColumnData[$cacheKey] = $data;
|
||||
self::$cachedEmailColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1486,7 +1486,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedEnumColumnData[$cacheKey] = $data;
|
||||
self::$cachedEnumColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
|
||||
@@ -1570,7 +1570,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedDatetimeColumnData[$cacheKey] = $data;
|
||||
self::$cachedDatetimeColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1646,7 +1646,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedRelationshipColumnData[$cacheKey] = $data;
|
||||
self::$cachedRelationshipColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
public function testUpdateRelationshipColumn(): void
|
||||
@@ -1717,7 +1717,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedIPColumnData[$cacheKey] = $data;
|
||||
self::$cachedIPColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1794,7 +1794,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedURLColumnData[$cacheKey] = $data;
|
||||
self::$cachedURLColumnData[$cacheKey] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1877,7 +1877,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedIndexData[$cacheKey] = [
|
||||
self::$cachedIndexData[$cacheKey] = [
|
||||
'database' => $data['database'],
|
||||
'table' => $data['table'],
|
||||
'index' => $index['body']['data']['tablesDBCreateIndex'],
|
||||
@@ -1952,7 +1952,7 @@ class DatabaseServerTest extends Scope
|
||||
|
||||
// Store for caching
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
static::$cachedRowData[$cacheKey] = [
|
||||
self::$cachedRowData[$cacheKey] = [
|
||||
'database' => $data['database'],
|
||||
'table' => $data['table'],
|
||||
'row' => $row,
|
||||
|
||||
@@ -20,8 +20,8 @@ class TeamsClientTest extends Scope
|
||||
protected function setupTeam(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedTeam[$key])) {
|
||||
return static::$cachedTeam[$key];
|
||||
if (!empty(self::$cachedTeam[$key])) {
|
||||
return self::$cachedTeam[$key];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -45,15 +45,15 @@ class TeamsClientTest extends Scope
|
||||
$team = $team['body']['data']['teamsCreate'];
|
||||
$this->assertEquals('Team Name', $team['name']);
|
||||
|
||||
static::$cachedTeam[$key] = $team;
|
||||
self::$cachedTeam[$key] = $team;
|
||||
return $team;
|
||||
}
|
||||
|
||||
protected function setupMembership(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedMembership[$key])) {
|
||||
return static::$cachedMembership[$key];
|
||||
if (!empty(self::$cachedMembership[$key])) {
|
||||
return self::$cachedMembership[$key];
|
||||
}
|
||||
|
||||
$team = $this->setupTeam();
|
||||
@@ -82,7 +82,7 @@ class TeamsClientTest extends Scope
|
||||
$this->assertEquals($team['_id'], $membership['teamId']);
|
||||
$this->assertEquals(['developer'], $membership['roles']);
|
||||
|
||||
static::$cachedMembership[$key] = $membership;
|
||||
self::$cachedMembership[$key] = $membership;
|
||||
return $membership;
|
||||
}
|
||||
|
||||
@@ -211,6 +211,6 @@ class TeamsClientTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedMembership[$key] = [];
|
||||
self::$cachedMembership[$key] = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ class TeamsServerTest extends Scope
|
||||
protected function setupTeam(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedTeam[$key])) {
|
||||
return static::$cachedTeam[$key];
|
||||
if (!empty(self::$cachedTeam[$key])) {
|
||||
return self::$cachedTeam[$key];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -47,15 +47,15 @@ class TeamsServerTest extends Scope
|
||||
$team = $team['body']['data']['teamsCreate'];
|
||||
$this->assertEquals('Team Name', $team['name']);
|
||||
|
||||
static::$cachedTeam[$key] = $team;
|
||||
self::$cachedTeam[$key] = $team;
|
||||
return $team;
|
||||
}
|
||||
|
||||
protected function setupMembership(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedMembership[$key])) {
|
||||
return static::$cachedMembership[$key];
|
||||
if (!empty(self::$cachedMembership[$key])) {
|
||||
return self::$cachedMembership[$key];
|
||||
}
|
||||
|
||||
$team = $this->setupTeam();
|
||||
@@ -83,15 +83,15 @@ class TeamsServerTest extends Scope
|
||||
$this->assertEquals($team['_id'], $membership['teamId']);
|
||||
$this->assertEquals(['developer'], $membership['roles']);
|
||||
|
||||
static::$cachedMembership[$key] = $membership;
|
||||
self::$cachedMembership[$key] = $membership;
|
||||
return $membership;
|
||||
}
|
||||
|
||||
protected function setupTeamWithPrefs(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedTeamWithPrefs[$key])) {
|
||||
return static::$cachedTeamWithPrefs[$key];
|
||||
if (!empty(self::$cachedTeamWithPrefs[$key])) {
|
||||
return self::$cachedTeamWithPrefs[$key];
|
||||
}
|
||||
|
||||
$team = $this->setupTeam();
|
||||
@@ -137,7 +137,7 @@ class TeamsServerTest extends Scope
|
||||
$this->assertIsArray($prefs['body']['data']['teamsUpdatePrefs']);
|
||||
$this->assertEquals('{"key":"value"}', $prefs['body']['data']['teamsUpdatePrefs']['data']);
|
||||
|
||||
static::$cachedTeamWithPrefs[$key] = $fetchedTeam;
|
||||
self::$cachedTeamWithPrefs[$key] = $fetchedTeam;
|
||||
return $fetchedTeam;
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ class TeamsServerTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedMembership[$key] = [];
|
||||
self::$cachedMembership[$key] = [];
|
||||
}
|
||||
|
||||
#[Group('cl-ignore')]
|
||||
|
||||
@@ -21,8 +21,8 @@ class UsersTest extends Scope
|
||||
protected function setupUser(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedUser[$key])) {
|
||||
return static::$cachedUser[$key];
|
||||
if (!empty(self::$cachedUser[$key])) {
|
||||
return self::$cachedUser[$key];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -50,15 +50,15 @@ class UsersTest extends Scope
|
||||
$this->assertEquals('Project User', $user['name']);
|
||||
$this->assertEquals($email, $user['email']);
|
||||
|
||||
static::$cachedUser[$key] = $user;
|
||||
self::$cachedUser[$key] = $user;
|
||||
return $user;
|
||||
}
|
||||
|
||||
protected function setupUserTarget(): array
|
||||
{
|
||||
$key = $this->getProject()['$id'];
|
||||
if (!empty(static::$cachedUserTarget[$key])) {
|
||||
return static::$cachedUserTarget[$key];
|
||||
if (!empty(self::$cachedUserTarget[$key])) {
|
||||
return self::$cachedUserTarget[$key];
|
||||
}
|
||||
|
||||
$user = $this->setupUser();
|
||||
@@ -106,8 +106,8 @@ class UsersTest extends Scope
|
||||
$this->assertEquals(200, $target['headers']['status-code']);
|
||||
$this->assertEquals('random-email@mail.org', $target['body']['data']['usersCreateTarget']['identifier']);
|
||||
|
||||
static::$cachedUserTarget[$key] = $target['body']['data']['usersCreateTarget'];
|
||||
return static::$cachedUserTarget[$key];
|
||||
self::$cachedUserTarget[$key] = $target['body']['data']['usersCreateTarget'];
|
||||
return self::$cachedUserTarget[$key];
|
||||
}
|
||||
|
||||
public function testCreateUser(): void
|
||||
@@ -581,7 +581,7 @@ class UsersTest extends Scope
|
||||
|
||||
// Clear cache after deletion
|
||||
$key = $this->getProject()['$id'];
|
||||
static::$cachedUserTarget[$key] = [];
|
||||
self::$cachedUserTarget[$key] = [];
|
||||
}
|
||||
|
||||
public function testDeleteUser()
|
||||
|
||||
@@ -3825,4 +3825,714 @@ trait MigrationsBase
|
||||
'x-appwrite-key' => $sourceProject['apiKey'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testCreateJSONImport(): void
|
||||
{
|
||||
// Make a database
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Test Database'
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals('Test Database', $response['body']['name']);
|
||||
|
||||
$databaseId = $response['body']['$id'];
|
||||
|
||||
// make a table
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'name' => 'Test table',
|
||||
'tableId' => ID::unique(),
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals($response['body']['name'], 'Test table');
|
||||
|
||||
$tableId = $response['body']['$id'];
|
||||
|
||||
// make columns
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'key' => 'name',
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $response['headers']['status-code']);
|
||||
$this->assertEquals($response['body']['key'], 'name');
|
||||
$this->assertEquals($response['body']['type'], 'string');
|
||||
$this->assertEquals($response['body']['size'], 256);
|
||||
$this->assertEquals($response['body']['required'], true);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/integer', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'key' => 'age',
|
||||
'min' => 18,
|
||||
'max' => 65,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $response['headers']['status-code']);
|
||||
$this->assertEquals($response['body']['key'], 'age');
|
||||
$this->assertEquals($response['body']['type'], 'integer');
|
||||
$this->assertEquals($response['body']['min'], 18);
|
||||
$this->assertEquals($response['body']['max'], 65);
|
||||
$this->assertEquals($response['body']['required'], true);
|
||||
|
||||
// make a bucket, upload a file to it!
|
||||
$bucketOne = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'Test Bucket',
|
||||
'maximumFileSize' => 2000000, //2MB
|
||||
'allowedFileExtensions' => ['json'],
|
||||
'compression' => 'gzip',
|
||||
'encryption' => true
|
||||
]);
|
||||
$this->assertEquals(201, $bucketOne['headers']['status-code']);
|
||||
$this->assertNotEmpty($bucketOne['body']['$id']);
|
||||
|
||||
$bucketOneId = $bucketOne['body']['$id'];
|
||||
|
||||
$bucketIds = [
|
||||
'default' => $bucketOneId,
|
||||
'missing-column' => $bucketOneId,
|
||||
'irrelevant-column' => $bucketOneId,
|
||||
'documents-internals' => $bucketOneId,
|
||||
];
|
||||
|
||||
$fileIds = [];
|
||||
|
||||
foreach ($bucketIds as $label => $bucketId) {
|
||||
$jsonFileName = match ($label) {
|
||||
'missing-column',
|
||||
'irrelevant-column',
|
||||
'documents-internals' => "$label.json",
|
||||
default => 'documents.json',
|
||||
};
|
||||
|
||||
$response = $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/json/'.$jsonFileName), 'application/json', $jsonFileName),
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($jsonFileName, $response['body']['name']);
|
||||
$this->assertEquals('application/json', $response['body']['mimeType']);
|
||||
|
||||
$fileIds[$label] = $response['body']['$id'];
|
||||
}
|
||||
|
||||
// missing column, fail in worker.
|
||||
$missingColumn = $this->performJsonMigration(
|
||||
[
|
||||
'fileId' => $fileIds['missing-column'],
|
||||
'bucketId' => $bucketIds['missing-column'],
|
||||
'resourceId' => $databaseId . ':' . $tableId,
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($missingColumn) {
|
||||
$migrationId = $missingColumn['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $migration['headers']['status-code']);
|
||||
$this->assertEquals('finished', $migration['body']['stage']);
|
||||
$this->assertEquals('failed', $migration['body']['status']);
|
||||
$this->assertEquals('JSON', $migration['body']['source']);
|
||||
$this->assertEquals('Appwrite', $migration['body']['destination']);
|
||||
$this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']);
|
||||
|
||||
/* fails in batch create documents unlike csv which checks headers first! */
|
||||
$this->assertArrayHasKey(Resource::TYPE_ROW, $migration['body']['statusCounters']);
|
||||
$this->assertGreaterThan(0, $migration['body']['statusCounters'][Resource::TYPE_ROW]['error']);
|
||||
|
||||
$this->assertThat(
|
||||
implode("\n", $migration['body']['errors']),
|
||||
$this->stringContains('Missing required attribute')
|
||||
);
|
||||
$this->assertThat(
|
||||
implode("\n", $migration['body']['errors']),
|
||||
$this->stringContains('age')
|
||||
);
|
||||
}, 60_000, 500);
|
||||
|
||||
// irrelevant column - email, success.
|
||||
$irrelevantColumn = $this->performJsonMigration(
|
||||
[
|
||||
'fileId' => $fileIds['irrelevant-column'],
|
||||
'bucketId' => $bucketIds['irrelevant-column'],
|
||||
'resourceId' => $databaseId . ':' . $tableId,
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($irrelevantColumn) {
|
||||
$migrationId = $irrelevantColumn['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $migration['headers']['status-code']);
|
||||
$this->assertEquals('finished', $migration['body']['stage']);
|
||||
$this->assertEquals('completed', $migration['body']['status']);
|
||||
$this->assertEquals('JSON', $migration['body']['source']);
|
||||
$this->assertEquals('Appwrite', $migration['body']['destination']);
|
||||
$this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_ROW, $migration['body']['statusCounters']);
|
||||
$this->assertEquals(100, $migration['body']['statusCounters'][Resource::TYPE_ROW]['success']);
|
||||
}, 10_000, 500);
|
||||
|
||||
// all data exists, pass.
|
||||
$migration = $this->performJsonMigration(
|
||||
[
|
||||
'endpoint' => $this->endpoint,
|
||||
'fileId' => $fileIds['default'],
|
||||
'bucketId' => $bucketIds['default'],
|
||||
'resourceId' => $databaseId . ':' . $tableId,
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($migration, $databaseId, $tableId) {
|
||||
$migrationId = $migration['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $migration['headers']['status-code']);
|
||||
$this->assertEquals('finished', $migration['body']['stage']);
|
||||
$this->assertEquals('completed', $migration['body']['status']);
|
||||
$this->assertEquals('JSON', $migration['body']['source']);
|
||||
$this->assertEquals('Appwrite', $migration['body']['destination']);
|
||||
$this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_ROW, $migration['body']['statusCounters']);
|
||||
$this->assertEquals(100, $migration['body']['statusCounters'][Resource::TYPE_ROW]['success']);
|
||||
}, 10_000, 500);
|
||||
|
||||
// get rows count
|
||||
$rows = $this->client->call(Client::METHOD_GET, '/tablesdb/'.$databaseId.'/tables/'.$tableId.'/rows', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::limit(250)->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $rows['headers']['status-code']);
|
||||
$this->assertIsArray($rows['body']['rows']);
|
||||
$this->assertIsNumeric($rows['body']['total']);
|
||||
$this->assertEquals(200, $rows['body']['total']);
|
||||
|
||||
// all data exists and includes internals, pass.
|
||||
$migration = $this->performJsonMigration(
|
||||
[
|
||||
'endpoint' => $this->endpoint,
|
||||
'fileId' => $fileIds['documents-internals'],
|
||||
'bucketId' => $bucketIds['documents-internals'],
|
||||
'resourceId' => $databaseId . ':' . $tableId,
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($migration) {
|
||||
$migrationId = $migration['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $migration['headers']['status-code']);
|
||||
$this->assertEquals('finished', $migration['body']['stage']);
|
||||
$this->assertEquals('completed', $migration['body']['status']);
|
||||
$this->assertEquals('JSON', $migration['body']['source']);
|
||||
$this->assertEquals('Appwrite', $migration['body']['destination']);
|
||||
$this->assertContains(Resource::TYPE_ROW, $migration['body']['resources']);
|
||||
$this->assertArrayHasKey(Resource::TYPE_ROW, $migration['body']['statusCounters']);
|
||||
$this->assertEquals(25, $migration['body']['statusCounters'][Resource::TYPE_ROW]['success']);
|
||||
}, 10_000, 500);
|
||||
}
|
||||
|
||||
private function performJsonMigration(array $body): array
|
||||
{
|
||||
return $this->client->call(Client::METHOD_POST, '/migrations/json/imports', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test JSON export with email notification
|
||||
*/
|
||||
public function testCreateJSONExport(): void
|
||||
{
|
||||
// Create a database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Test Export Database'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create a collection
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Test Export Collection',
|
||||
'permissions' => []
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create a simple attribute like the basic test
|
||||
$name = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'name',
|
||||
'size' => 255,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $name['headers']['status-code']);
|
||||
|
||||
// Create a simple attribute like the basic test
|
||||
$email = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'email',
|
||||
'size' => 255,
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $email['headers']['status-code']);
|
||||
|
||||
\sleep(3);
|
||||
|
||||
// Create sample documents
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Test User ' . $i,
|
||||
'email' => 'user' . $i . '@appwrite.io'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $doc['headers']['status-code'], 'Failed to create document ' . $i);
|
||||
}
|
||||
|
||||
// Verify documents were created
|
||||
$docs = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $docs['headers']['status-code']);
|
||||
$this->assertEquals(10, $docs['body']['total'], 'Expected 10 documents but got ' . $docs['body']['total']);
|
||||
|
||||
// Perform JSON export with notification enabled (uses internal bucket)
|
||||
$migration = $this->client->call(Client::METHOD_POST, '/migrations/json/exports', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'resourceId' => $databaseId . ':' . $collectionId,
|
||||
'filename' => 'test-json-export',
|
||||
'columns' => [],
|
||||
'queries' => [],
|
||||
'notify' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $migration['headers']['status-code']);
|
||||
$this->assertNotEmpty($migration['body']['$id']);
|
||||
$migrationId = $migration['body']['$id'];
|
||||
|
||||
$this->assertEventually(function () use ($migrationId) {
|
||||
$response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('finished', $response['body']['stage']);
|
||||
$this->assertEquals('completed', $response['body']['status']);
|
||||
$this->assertEquals('Appwrite', $response['body']['source']);
|
||||
$this->assertEquals('JSON', $response['body']['destination']);
|
||||
|
||||
return true;
|
||||
}, 30_000, 500);
|
||||
|
||||
// Check that email was sent with download link
|
||||
$lastEmail = $this->getLastEmail();
|
||||
$this->assertNotEmpty($lastEmail);
|
||||
$this->assertEquals('Your JSON export is ready', $lastEmail['subject']);
|
||||
$this->assertStringContainsStringIgnoringCase('Your data export has been completed successfully', $lastEmail['text']);
|
||||
|
||||
// Extract download URL from email HTML
|
||||
\preg_match('/href="([^"]*\/storage\/buckets\/[^"]*\/push[^"]*)"/', $lastEmail['html'], $matches);
|
||||
$this->assertNotEmpty($matches[1], 'Download URL not found in email');
|
||||
$downloadUrl = html_entity_decode($matches[1]);
|
||||
|
||||
// Parse the URL to extract components
|
||||
$components = \parse_url($downloadUrl);
|
||||
$this->assertNotEmpty($components);
|
||||
\parse_str($components['query'] ?? '', $queryParams);
|
||||
$this->assertArrayHasKey('jwt', $queryParams, 'JWT not found in download URL');
|
||||
$this->assertNotEmpty($queryParams['jwt']);
|
||||
$this->assertArrayHasKey('project', $queryParams, 'Project not found in download URL');
|
||||
$this->assertStringContainsString('/storage/buckets/default/files/', $downloadUrl);
|
||||
|
||||
// Test download with JWT
|
||||
$path = \str_replace('/v1', '', $components['path']);
|
||||
$downloadWithJwt = $this->client->call(Client::METHOD_GET, $path . '?project=' . $queryParams['project'] . '&jwt=' . $queryParams['jwt']);
|
||||
$this->assertEquals(200, $downloadWithJwt['headers']['status-code'], 'Failed to download file with JWT');
|
||||
|
||||
// Verify the downloaded content is valid JSON
|
||||
$jsonData = $downloadWithJwt['body'];
|
||||
$this->assertNotEmpty($jsonData, 'JSON export should not be empty');
|
||||
$decoded = json_decode($jsonData, true);
|
||||
$this->assertIsArray($decoded, 'JSON should be valid and decodable');
|
||||
$this->assertCount(10, $decoded, 'JSON should contain 10 documents');
|
||||
$this->assertArrayHasKey('name', $decoded[0], 'JSON documents should contain name field');
|
||||
$this->assertArrayHasKey('email', $decoded[0], 'JSON documents should contain email field');
|
||||
$this->assertStringContainsString('Test User', $decoded[0]['name'], 'JSON should contain test data');
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
}
|
||||
|
||||
public function testCreateVectorsDBJSONExport(): void
|
||||
{
|
||||
$headers = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
];
|
||||
|
||||
// Create vectorsdb database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectorsdb', $headers, [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'VectorsDB Export Test'
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create collection with dimension 16
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $headers, [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'VecExportCol',
|
||||
'dimension' => 16,
|
||||
]);
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Seed 5 documents
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$embeddings = array_map(fn () => round((mt_rand() / mt_getrandmax()) * 2 - 1, 6), range(1, 16));
|
||||
$doc = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId . '/documents', $headers, [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'embeddings' => $embeddings,
|
||||
'metadata' => ['title' => 'Doc ' . $i, 'score' => round($i * 0.2, 1)]
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(201, $doc['headers']['status-code'], 'Failed to create vector document ' . $i);
|
||||
}
|
||||
|
||||
// Trigger JSON export
|
||||
$migration = $this->client->call(Client::METHOD_POST, '/migrations/json/exports', $headers, [
|
||||
'resourceId' => $databaseId . ':' . $collectionId,
|
||||
'filename' => 'vectorsdb-export-test',
|
||||
'columns' => [],
|
||||
'queries' => [],
|
||||
'notify' => false,
|
||||
]);
|
||||
$this->assertEquals(202, $migration['headers']['status-code']);
|
||||
$migrationId = $migration['body']['$id'];
|
||||
|
||||
// Poll until completed
|
||||
$this->assertEventually(function () use ($migrationId, $headers) {
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, $headers);
|
||||
|
||||
$this->assertEquals(200, $migration['headers']['status-code']);
|
||||
$this->assertEquals('finished', $migration['body']['stage']);
|
||||
$this->assertEquals('completed', $migration['body']['status']);
|
||||
$this->assertEquals('Appwrite', $migration['body']['source']);
|
||||
$this->assertEquals('JSON', $migration['body']['destination']);
|
||||
}, 30_000, 500);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, $headers);
|
||||
}
|
||||
|
||||
public function testCreateVectorsDBJSONImport(): void
|
||||
{
|
||||
$headers = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
];
|
||||
|
||||
// Create vectorsdb database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/vectorsdb', $headers, [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'VectorsDB Import Test'
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create collection with dimension 16
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/vectorsdb/' . $databaseId . '/collections', $headers, [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'VecImportCol',
|
||||
'dimension' => 16,
|
||||
]);
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create bucket and upload test file
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', $headers, [
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'VectorsDB Import Bucket',
|
||||
'maximumFileSize' => 2000000,
|
||||
'allowedFileExtensions' => ['json'],
|
||||
]);
|
||||
$this->assertEquals(201, $bucket['headers']['status-code']);
|
||||
$bucketId = $bucket['body']['$id'];
|
||||
|
||||
$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/json/vectorsdb-documents.json'), 'application/json', 'vectorsdb-documents.json'),
|
||||
]);
|
||||
$this->assertEquals(201, $file['headers']['status-code']);
|
||||
$fileId = $file['body']['$id'];
|
||||
|
||||
// Trigger import
|
||||
$migration = $this->performJsonMigration([
|
||||
'fileId' => $fileId,
|
||||
'bucketId' => $bucketId,
|
||||
'resourceId' => $databaseId . ':' . $collectionId,
|
||||
]);
|
||||
$this->assertEquals(202, $migration['headers']['status-code']);
|
||||
|
||||
// Poll until completed
|
||||
$this->assertEventually(function () use ($migration, $headers) {
|
||||
$migrationId = $migration['body']['$id'];
|
||||
$result = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, $headers);
|
||||
|
||||
$this->assertEquals(200, $result['headers']['status-code']);
|
||||
$this->assertEquals('finished', $result['body']['stage']);
|
||||
$this->assertEquals('completed', $result['body']['status']);
|
||||
$this->assertEquals('JSON', $result['body']['source']);
|
||||
$this->assertEquals('Appwrite', $result['body']['destination']);
|
||||
}, 30_000, 500);
|
||||
|
||||
// Verify documents were imported
|
||||
$docs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $collectionId . '/documents', $headers);
|
||||
$this->assertEquals(200, $docs['headers']['status-code']);
|
||||
$this->assertEquals(10, $docs['body']['total'], 'Should have imported 10 vectorsdb documents');
|
||||
|
||||
// Verify first document structure
|
||||
$firstDoc = $docs['body']['documents'][0];
|
||||
$this->assertArrayHasKey('embeddings', $firstDoc);
|
||||
$this->assertCount(16, $firstDoc['embeddings'], 'Imported embeddings should have 16 dimensions');
|
||||
$this->assertArrayHasKey('metadata', $firstDoc);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/vectorsdb/' . $databaseId, $headers);
|
||||
}
|
||||
|
||||
public function testCreateDocumentsDBJSONExport(): void
|
||||
{
|
||||
$headers = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
];
|
||||
|
||||
// Create documentsdb database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/documentsdb', $headers, [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'DocumentsDB Export Test'
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create collection (schemaless — no attributes needed)
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $databaseId . '/collections', $headers, [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'DocExportCol',
|
||||
]);
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Seed 5 documents
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$doc = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $databaseId . '/collections/' . $collectionId . '/documents', $headers, [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'User ' . $i,
|
||||
'email' => 'user' . $i . '@test.com',
|
||||
'age' => 20 + $i,
|
||||
'address' => ['city' => 'City ' . $i, 'zip' => '1000' . $i]
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(201, $doc['headers']['status-code'], 'Failed to create document ' . $i);
|
||||
}
|
||||
|
||||
// Trigger JSON export
|
||||
$migration = $this->client->call(Client::METHOD_POST, '/migrations/json/exports', $headers, [
|
||||
'resourceId' => $databaseId . ':' . $collectionId,
|
||||
'filename' => 'documentsdb-export-test',
|
||||
'columns' => [],
|
||||
'queries' => [],
|
||||
'notify' => false,
|
||||
]);
|
||||
$this->assertEquals(202, $migration['headers']['status-code']);
|
||||
$migrationId = $migration['body']['$id'];
|
||||
|
||||
// Poll until completed
|
||||
$this->assertEventually(function () use ($migrationId, $headers) {
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, $headers);
|
||||
|
||||
$this->assertEquals(200, $migration['headers']['status-code']);
|
||||
$this->assertEquals('finished', $migration['body']['stage']);
|
||||
$this->assertEquals('completed', $migration['body']['status']);
|
||||
$this->assertEquals('Appwrite', $migration['body']['source']);
|
||||
$this->assertEquals('JSON', $migration['body']['destination']);
|
||||
}, 30_000, 500);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $databaseId, $headers);
|
||||
}
|
||||
|
||||
public function testCreateDocumentsDBJSONImport(): void
|
||||
{
|
||||
$headers = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
];
|
||||
|
||||
// Create documentsdb database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/documentsdb', $headers, [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'DocumentsDB Import Test'
|
||||
]);
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create collection (schemaless)
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/documentsdb/' . $databaseId . '/collections', $headers, [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'DocImportCol',
|
||||
]);
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create bucket and upload test file
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', $headers, [
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'DocumentsDB Import Bucket',
|
||||
'maximumFileSize' => 2000000,
|
||||
'allowedFileExtensions' => ['json'],
|
||||
]);
|
||||
$this->assertEquals(201, $bucket['headers']['status-code']);
|
||||
$bucketId = $bucket['body']['$id'];
|
||||
|
||||
$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/json/documentsdb-documents.json'), 'application/json', 'documentsdb-documents.json'),
|
||||
]);
|
||||
$this->assertEquals(201, $file['headers']['status-code']);
|
||||
$fileId = $file['body']['$id'];
|
||||
|
||||
// Trigger import
|
||||
$migration = $this->performJsonMigration([
|
||||
'fileId' => $fileId,
|
||||
'bucketId' => $bucketId,
|
||||
'resourceId' => $databaseId . ':' . $collectionId,
|
||||
]);
|
||||
$this->assertEquals(202, $migration['headers']['status-code']);
|
||||
|
||||
// Poll until completed
|
||||
$this->assertEventually(function () use ($migration, $headers) {
|
||||
$migrationId = $migration['body']['$id'];
|
||||
$result = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, $headers);
|
||||
|
||||
$this->assertEquals(200, $result['headers']['status-code']);
|
||||
$this->assertEquals('finished', $result['body']['stage']);
|
||||
$this->assertEquals('completed', $result['body']['status']);
|
||||
$this->assertEquals('JSON', $result['body']['source']);
|
||||
$this->assertEquals('Appwrite', $result['body']['destination']);
|
||||
}, 30_000, 500);
|
||||
|
||||
// Verify documents were imported
|
||||
$docs = $this->client->call(Client::METHOD_GET, '/documentsdb/' . $databaseId . '/collections/' . $collectionId . '/documents', $headers);
|
||||
$this->assertEquals(200, $docs['headers']['status-code']);
|
||||
$this->assertEquals(10, $docs['body']['total'], 'Should have imported 10 documentsdb documents');
|
||||
|
||||
// Verify first document has nested data
|
||||
$firstDoc = $docs['body']['documents'][0];
|
||||
$this->assertArrayHasKey('name', $firstDoc);
|
||||
$this->assertArrayHasKey('address', $firstDoc);
|
||||
$this->assertIsArray($firstDoc['address']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->call(Client::METHOD_DELETE, '/documentsdb/' . $databaseId, $headers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3039,8 +3039,21 @@ class RealtimeCustomClientTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $update['headers']['status-code']);
|
||||
|
||||
$event = json_decode($client->receive(), true);
|
||||
// Drain WebSocket messages until the .update event arrives.
|
||||
// Earlier events (e.g. a late-arriving .create from the row seed above) are skipped.
|
||||
$updateEvent = "tablesdb.{$databaseId}.tables.{$tableId}.rows.{$rowId}.update";
|
||||
$event = null;
|
||||
$deadline = \time() + 10;
|
||||
while (\time() < $deadline) {
|
||||
$raw = $client->receive();
|
||||
$msg = json_decode($raw, true);
|
||||
if (($msg['type'] ?? '') === 'event' && \in_array($updateEvent, $msg['data']['events'] ?? [])) {
|
||||
$event = $msg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertNotNull($event, 'Timed out waiting for the row update event');
|
||||
$this->assertArrayHasKey('type', $event);
|
||||
$this->assertArrayHasKey('data', $event);
|
||||
$this->assertEquals('event', $event['type']);
|
||||
|
||||
@@ -14,8 +14,8 @@ trait TokensBase
|
||||
|
||||
protected function setupBucketAndFile(): array
|
||||
{
|
||||
if (!empty(static::$bucketAndFileData)) {
|
||||
return static::$bucketAndFileData;
|
||||
if (!empty(self::$bucketAndFileData)) {
|
||||
return self::$bucketAndFileData;
|
||||
}
|
||||
|
||||
$bucket = $this->client->call(
|
||||
@@ -61,7 +61,7 @@ trait TokensBase
|
||||
]
|
||||
);
|
||||
|
||||
static::$bucketAndFileData = [
|
||||
self::$bucketAndFileData = [
|
||||
'fileId' => $fileId,
|
||||
'bucketId' => $bucketId,
|
||||
'token' => $token['body'],
|
||||
@@ -72,7 +72,7 @@ trait TokensBase
|
||||
],
|
||||
];
|
||||
|
||||
return static::$bucketAndFileData;
|
||||
return self::$bucketAndFileData;
|
||||
}
|
||||
|
||||
public function testCreateBucketAndFile(): void
|
||||
|
||||
@@ -25,8 +25,8 @@ class TokensConsoleClientTest extends Scope
|
||||
|
||||
protected function setupToken(): array
|
||||
{
|
||||
if (!empty(static::$tokenData)) {
|
||||
return static::$tokenData;
|
||||
if (!empty(self::$tokenData)) {
|
||||
return self::$tokenData;
|
||||
}
|
||||
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', array_merge([
|
||||
@@ -68,13 +68,13 @@ class TokensConsoleClientTest extends Scope
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()));
|
||||
|
||||
static::$tokenData = [
|
||||
self::$tokenData = [
|
||||
'fileId' => $fileId,
|
||||
'bucketId' => $bucketId,
|
||||
'tokenId' => $token['body']['$id'],
|
||||
];
|
||||
|
||||
return static::$tokenData;
|
||||
return self::$tokenData;
|
||||
}
|
||||
|
||||
public function testCreateToken(): void
|
||||
|
||||
@@ -22,8 +22,8 @@ class TokensCustomServerTest extends Scope
|
||||
|
||||
protected function setupToken(): array
|
||||
{
|
||||
if (!empty(static::$tokenData)) {
|
||||
return static::$tokenData;
|
||||
if (!empty(self::$tokenData)) {
|
||||
return self::$tokenData;
|
||||
}
|
||||
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
|
||||
@@ -66,13 +66,13 @@ class TokensCustomServerTest extends Scope
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()));
|
||||
|
||||
static::$tokenData = [
|
||||
self::$tokenData = [
|
||||
'fileId' => $fileId,
|
||||
'bucketId' => $bucketId,
|
||||
'tokenId' => $token['body']['$id'],
|
||||
];
|
||||
|
||||
return static::$tokenData;
|
||||
return self::$tokenData;
|
||||
}
|
||||
|
||||
public function testCreateToken(): void
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
[
|
||||
{
|
||||
"$id": "z1y2x3w4v5u6t7s8",
|
||||
"$createdAt": "2022-10-23T10:33:01+00:00",
|
||||
"$updatedAt": "2023-03-15T12:00:41+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")",
|
||||
"update(\"user:123\")"
|
||||
],
|
||||
"name": "Diamond Mendez",
|
||||
"age": 56
|
||||
},
|
||||
{
|
||||
"$id": "r9q0p1o2n3m4l5k6",
|
||||
"$createdAt": "2021-08-11T21:05:13+00:00",
|
||||
"$updatedAt": "2024-01-02T08:45:22+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")",
|
||||
"update(\"user:456\")"
|
||||
],
|
||||
"name": "Michael Huff",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "j7i8h9g0f1e2d3c4",
|
||||
"$createdAt": "2020-05-29T14:22:56+00:00",
|
||||
"$updatedAt": "2022-11-30T18:19:33+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Alyssa Rodriguez",
|
||||
"age": 37
|
||||
},
|
||||
{
|
||||
"$id": "b5a6z7y8x9w0v1u2",
|
||||
"$createdAt": "2023-01-18T03:44:09+00:00",
|
||||
"$updatedAt": "2023-09-07T23:50:17+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Barbara Smith",
|
||||
"age": 26
|
||||
},
|
||||
{
|
||||
"$id": "t3s4r5q6p7o8n9m0",
|
||||
"$createdAt": "2020-11-02T09:12:45+00:00",
|
||||
"$updatedAt": "2021-07-21T15:30:55+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Evelyn Edwards",
|
||||
"age": 54
|
||||
},
|
||||
{
|
||||
"$id": "l1k2j3i4h5g6f7e8",
|
||||
"$createdAt": "2022-03-19T19:55:27+00:00",
|
||||
"$updatedAt": "2024-05-14T06:28:11+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Tina Richardson",
|
||||
"age": 41
|
||||
},
|
||||
{
|
||||
"$id": "d9c0b1a2z3y4x5w6",
|
||||
"$createdAt": "2021-04-07T01:18:34+00:00",
|
||||
"$updatedAt": "2023-06-25T11:47:04+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Joel Hernandez",
|
||||
"age": 49
|
||||
},
|
||||
{
|
||||
"$id": "v7u8t9s0r1q2p3o4",
|
||||
"$createdAt": "2023-08-22T16:40:18+00:00",
|
||||
"$updatedAt": "2024-02-19T04:09:58+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Zachary Cooper",
|
||||
"age": 59
|
||||
},
|
||||
{
|
||||
"$id": "n5m6l7k8j9i0h1g2",
|
||||
"$createdAt": "2020-02-12T07:59:01+00:00",
|
||||
"$updatedAt": "2022-09-08T13:21:49+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Brittany Spears",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "f3e4d5c6b7a8z9y0",
|
||||
"$createdAt": "2021-12-05T22:33:12+00:00",
|
||||
"$updatedAt": "2023-11-11T02:55:37+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Holly White",
|
||||
"age": 47
|
||||
},
|
||||
{
|
||||
"$id": "x1w2v3u4t5s6r7q8",
|
||||
"$createdAt": "2022-07-14T05:01:50+00:00",
|
||||
"$updatedAt": "2024-04-01T20:10:26+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Kimberly Barnes",
|
||||
"age": 27
|
||||
},
|
||||
{
|
||||
"$id": "p9o0n1m2l3k4j5i6",
|
||||
"$createdAt": "2020-09-28T11:27:36+00:00",
|
||||
"$updatedAt": "2021-10-17T09:38:08+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Stephen Miller",
|
||||
"age": 53
|
||||
},
|
||||
{
|
||||
"$id": "h7g8f9e0d1c2b3a4",
|
||||
"$createdAt": "2023-04-04T08:15:59+00:00",
|
||||
"$updatedAt": "2024-06-29T17:03:14+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Yvonne Newman",
|
||||
"age": 41
|
||||
},
|
||||
{
|
||||
"$id": "y5x6w7v8u9t0s1r2",
|
||||
"$createdAt": "2021-01-25T18:09:21+00:00",
|
||||
"$updatedAt": "2022-08-16T22:44:51+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Carol Kane",
|
||||
"age": 38
|
||||
},
|
||||
{
|
||||
"$id": "q3p4o5n6m7l8k9j0",
|
||||
"$createdAt": "2022-06-09T12:53:47+00:00",
|
||||
"$updatedAt": "2023-12-24T01:16:05+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Doris Foster",
|
||||
"age": 44
|
||||
},
|
||||
{
|
||||
"$id": "i1h2g3f4e5d6c7b8",
|
||||
"$createdAt": "2020-07-03T23:37:02+00:00",
|
||||
"$updatedAt": "2021-05-09T05:52:43+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Joseph Stokes",
|
||||
"age": 28
|
||||
},
|
||||
{
|
||||
"$id": "a9z0y1x2w3v4u5t6",
|
||||
"$createdAt": "2023-10-10T02:20:15+00:00",
|
||||
"$updatedAt": "2024-03-28T14:33:29+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Steve Williams",
|
||||
"age": 31
|
||||
},
|
||||
{
|
||||
"$id": "s7r8q9p0o1n2m3l4",
|
||||
"$createdAt": "2021-06-16T13:48:53+00:00",
|
||||
"$updatedAt": "2022-04-22T07:07:19+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "James Carey",
|
||||
"age": 29
|
||||
},
|
||||
{
|
||||
"$id": "k5j6i7h8g9f0e1d2",
|
||||
"$createdAt": "2022-12-27T20:06:38+00:00",
|
||||
"$updatedAt": "2023-08-03T10:25:57+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Kathryn Henry",
|
||||
"age": 38
|
||||
},
|
||||
{
|
||||
"$id": "c3b4a5z6y7x8w9v0",
|
||||
"$createdAt": "2020-04-20T04:41:24+00:00",
|
||||
"$updatedAt": "2021-02-13T19:14:06+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Christopher Landry",
|
||||
"age": 23
|
||||
},
|
||||
{
|
||||
"$id": "u1t2s3r4q5p6o7n8",
|
||||
"$createdAt": "2023-05-08T00:58:10+00:00",
|
||||
"$updatedAt": "2024-07-05T03:36:48+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Jennifer Mcgee",
|
||||
"age": 62
|
||||
},
|
||||
{
|
||||
"$id": "m9l0k1j2i3h4g5f6",
|
||||
"$createdAt": "2021-09-01T06:11:42+00:00",
|
||||
"$updatedAt": "2022-01-26T16:59:23+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Cathy Church",
|
||||
"age": 35
|
||||
},
|
||||
{
|
||||
"$id": "e7d8c9b0a1z2y3x4",
|
||||
"$createdAt": "2022-02-18T15:24:07+00:00",
|
||||
"$updatedAt": "2023-04-12T00:40:31+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Jose Lopez",
|
||||
"age": 41
|
||||
},
|
||||
{
|
||||
"$id": "w5v6u7t8s9r0q1p2",
|
||||
"$createdAt": "2020-12-13T09:03:55+00:00",
|
||||
"$updatedAt": "2021-11-06T11:23:16+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "William Rose",
|
||||
"age": 30
|
||||
},
|
||||
{
|
||||
"$id": "o3n4m5l6k7j8i9h0",
|
||||
"$createdAt": "2021-12-13T09:03:55+00:00",
|
||||
"$updatedAt": "2022-11-06T11:23:16+00:00",
|
||||
"$permissions": [
|
||||
"read(\"any\")"
|
||||
],
|
||||
"name": "Charles Hammer",
|
||||
"age": 61
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,502 @@
|
||||
[
|
||||
{
|
||||
"$id": "hxfcwpcas5xokpwe",
|
||||
"name": "Diamond Mendez",
|
||||
"age": 56
|
||||
},
|
||||
{
|
||||
"$id": "gw8nxwf6esn3tfwf",
|
||||
"name": "Michael Huff",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "xb6bxg56lral1qy9",
|
||||
"name": "Alyssa Rodriguez",
|
||||
"age": 37
|
||||
},
|
||||
{
|
||||
"$id": "imerjq5j36y3agh2",
|
||||
"name": "Barbara Smith",
|
||||
"age": 26
|
||||
},
|
||||
{
|
||||
"$id": "07yq9qdlhmbzmr35",
|
||||
"name": "Evelyn Edwards",
|
||||
"age": 54
|
||||
},
|
||||
{
|
||||
"$id": "ksqo631sbhwj5ltg",
|
||||
"name": "Tina Richardson",
|
||||
"age": 41
|
||||
},
|
||||
{
|
||||
"$id": "j7zlndgu0gbshp15",
|
||||
"name": "Joel Hernandez",
|
||||
"age": 49
|
||||
},
|
||||
{
|
||||
"$id": "mfntvnljrcmf7h6v",
|
||||
"name": "Zachary Cooper",
|
||||
"age": 59
|
||||
},
|
||||
{
|
||||
"$id": "5f9b01nziqu2h8ed",
|
||||
"name": "Brittany Spears",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "4vxzbnzraqznk5u8",
|
||||
"name": "Holly White",
|
||||
"age": 47
|
||||
},
|
||||
{
|
||||
"$id": "d4ywy3mtphaatbpf",
|
||||
"name": "Kimberly Barnes",
|
||||
"age": 27
|
||||
},
|
||||
{
|
||||
"$id": "88odnk6nthyyvbal",
|
||||
"name": "Stephen Miller",
|
||||
"age": 53
|
||||
},
|
||||
{
|
||||
"$id": "08oekee3fn7mzaa5",
|
||||
"name": "Yvonne Newman",
|
||||
"age": 41
|
||||
},
|
||||
{
|
||||
"$id": "quw55kn9895i5e4v",
|
||||
"name": "Carol Kane",
|
||||
"age": 38
|
||||
},
|
||||
{
|
||||
"$id": "nge6bm8ykripei6f",
|
||||
"name": "Doris Foster",
|
||||
"age": 44
|
||||
},
|
||||
{
|
||||
"$id": "4k16i33s0xl2ypx9",
|
||||
"name": "Joseph Stokes",
|
||||
"age": 28
|
||||
},
|
||||
{
|
||||
"$id": "q0j5rxbgid66snyf",
|
||||
"name": "Steve Williams",
|
||||
"age": 31
|
||||
},
|
||||
{
|
||||
"$id": "n1oxun7mqq3p103y",
|
||||
"name": "James Carey",
|
||||
"age": 29
|
||||
},
|
||||
{
|
||||
"$id": "0dbvs840jkf8i0ye",
|
||||
"name": "Kathryn Henry",
|
||||
"age": 38
|
||||
},
|
||||
{
|
||||
"$id": "5sfaidgs1h87v15v",
|
||||
"name": "Christopher Landry",
|
||||
"age": 23
|
||||
},
|
||||
{
|
||||
"$id": "vg3punvfu5khmf41",
|
||||
"name": "Jennifer Mcgee",
|
||||
"age": 62
|
||||
},
|
||||
{
|
||||
"$id": "f933qydr9u5b2r11",
|
||||
"name": "Cathy Church",
|
||||
"age": 35
|
||||
},
|
||||
{
|
||||
"$id": "wjv87y1inf8yk32s",
|
||||
"name": "Jose Lopez",
|
||||
"age": 41
|
||||
},
|
||||
{
|
||||
"$id": "uljysdvdlcyrbrwk",
|
||||
"name": "William Rose",
|
||||
"age": 30
|
||||
},
|
||||
{
|
||||
"$id": "ot8xtzh77j55wq0s",
|
||||
"name": "Sarah Ford",
|
||||
"age": 26
|
||||
},
|
||||
{
|
||||
"$id": "9t76vnsv2u36s43t",
|
||||
"name": "Alisha Jones",
|
||||
"age": 61
|
||||
},
|
||||
{
|
||||
"$id": "66y4tnty62hw8c02",
|
||||
"name": "Kristin Kelly",
|
||||
"age": 61
|
||||
},
|
||||
{
|
||||
"$id": "2punfblazi5v16ar",
|
||||
"name": "Brendan Stout",
|
||||
"age": 40
|
||||
},
|
||||
{
|
||||
"$id": "sxhr4nf5w2gx4wbg",
|
||||
"name": "Kelly Cruz",
|
||||
"age": 18
|
||||
},
|
||||
{
|
||||
"$id": "68dvrqfwqnkq5el9",
|
||||
"name": "Samantha Martin",
|
||||
"age": 50
|
||||
},
|
||||
{
|
||||
"$id": "20192l6dbeinhkh0",
|
||||
"name": "David Santos",
|
||||
"age": 46
|
||||
},
|
||||
{
|
||||
"$id": "si0l4dgay09ebfmf",
|
||||
"name": "Elizabeth Carroll",
|
||||
"age": 22
|
||||
},
|
||||
{
|
||||
"$id": "lhse40vbldqb6ap1",
|
||||
"name": "Corey Owens",
|
||||
"age": 46
|
||||
},
|
||||
{
|
||||
"$id": "h5t3pslykyx3kxfm",
|
||||
"name": "Shelby Mueller",
|
||||
"age": 65
|
||||
},
|
||||
{
|
||||
"$id": "ldc0luydrw6jub0f",
|
||||
"name": "Dr. Sylvia Myers",
|
||||
"age": 29
|
||||
},
|
||||
{
|
||||
"$id": "voc9628xg4dsgw2y",
|
||||
"name": "Scott Freeman",
|
||||
"age": 48
|
||||
},
|
||||
{
|
||||
"$id": "o4y0gk3gqv1ax2fz",
|
||||
"name": "Christopher Atkinson",
|
||||
"age": 21
|
||||
},
|
||||
{
|
||||
"$id": "u1n3x4e4u7e0vzj6",
|
||||
"name": "Sean Diaz",
|
||||
"age": 31
|
||||
},
|
||||
{
|
||||
"$id": "s36eskwtm0w7lwr7",
|
||||
"name": "Bobby Dyer",
|
||||
"age": 57
|
||||
},
|
||||
{
|
||||
"$id": "4hjnag1p5iwvtixd",
|
||||
"name": "Daniel Hall",
|
||||
"age": 62
|
||||
},
|
||||
{
|
||||
"$id": "m91d80oxsa216zbh",
|
||||
"name": "Jennifer Ramirez",
|
||||
"age": 65
|
||||
},
|
||||
{
|
||||
"$id": "5hj6858zo2g85n6v",
|
||||
"name": "Angela Jackson",
|
||||
"age": 57
|
||||
},
|
||||
{
|
||||
"$id": "8m8oihv9a1e7nn92",
|
||||
"name": "Kelly Lewis",
|
||||
"age": 36
|
||||
},
|
||||
{
|
||||
"$id": "7azy39la0no0mxi7",
|
||||
"name": "Jessica Munoz",
|
||||
"age": 55
|
||||
},
|
||||
{
|
||||
"$id": "47pmjkhnnqhyit8c",
|
||||
"name": "Kelly George",
|
||||
"age": 65
|
||||
},
|
||||
{
|
||||
"$id": "6j6cpy4kgneg1mmh",
|
||||
"name": "Anthony Johnson",
|
||||
"age": 65
|
||||
},
|
||||
{
|
||||
"$id": "tnlmtvap1zz89km9",
|
||||
"name": "Regina Fields",
|
||||
"age": 61
|
||||
},
|
||||
{
|
||||
"$id": "6cyuvnwwqdmrpfzh",
|
||||
"name": "Sharon Schaefer",
|
||||
"age": 30
|
||||
},
|
||||
{
|
||||
"$id": "p1v4pyu2pqodc0ey",
|
||||
"name": "Jacob French",
|
||||
"age": 62
|
||||
},
|
||||
{
|
||||
"$id": "6npynnhjt2jd05xo",
|
||||
"name": "Jessica Costa",
|
||||
"age": 23
|
||||
},
|
||||
{
|
||||
"$id": "wcxedf13n2e9qi4l",
|
||||
"name": "George Hardy",
|
||||
"age": 53
|
||||
},
|
||||
{
|
||||
"$id": "yf2xlcmszk2tqeig",
|
||||
"name": "Andrea Allison",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "3bf2zzv7poststwa",
|
||||
"name": "Kevin Ferguson",
|
||||
"age": 32
|
||||
},
|
||||
{
|
||||
"$id": "c2iataz0hhv39q63",
|
||||
"name": "Joseph Johnson",
|
||||
"age": 58
|
||||
},
|
||||
{
|
||||
"$id": "3e8npxhov4a39pvq",
|
||||
"name": "Ashley Martinez",
|
||||
"age": 18
|
||||
},
|
||||
{
|
||||
"$id": "t7dp41tysipytywq",
|
||||
"name": "Charles Nixon",
|
||||
"age": 23
|
||||
},
|
||||
{
|
||||
"$id": "z8cztq7c47phyfhk",
|
||||
"name": "Carol Dudley",
|
||||
"age": 40
|
||||
},
|
||||
{
|
||||
"$id": "2636f9d8r4ipm3h6",
|
||||
"name": "David Weber",
|
||||
"age": 51
|
||||
},
|
||||
{
|
||||
"$id": "eh3f6wxtvkjq6ykq",
|
||||
"name": "Scott Robinson",
|
||||
"age": 32
|
||||
},
|
||||
{
|
||||
"$id": "raskbwpsje69a59h",
|
||||
"name": "Anthony Hardy",
|
||||
"age": 38
|
||||
},
|
||||
{
|
||||
"$id": "90hn1p0b4cs9e2og",
|
||||
"name": "Mackenzie Owens",
|
||||
"age": 52
|
||||
},
|
||||
{
|
||||
"$id": "am3swwfbo076x0v1",
|
||||
"name": "Brian Foster",
|
||||
"age": 27
|
||||
},
|
||||
{
|
||||
"$id": "5uw7utb9lq5cfncw",
|
||||
"name": "Hannah Forbes",
|
||||
"age": 56
|
||||
},
|
||||
{
|
||||
"$id": "cs6mbfzkzifefx6r",
|
||||
"name": "Lauren Reed",
|
||||
"age": 26
|
||||
},
|
||||
{
|
||||
"$id": "ftw3uvztziiz9x00",
|
||||
"name": "Morgan Smith",
|
||||
"age": 28
|
||||
},
|
||||
{
|
||||
"$id": "uhrqseeo43mozpaq",
|
||||
"name": "Samantha Alexander",
|
||||
"age": 65
|
||||
},
|
||||
{
|
||||
"$id": "pvvmzyfc1lxor11e",
|
||||
"name": "Tiffany Roberts",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "jia7bdag4abz123s",
|
||||
"name": "Emily Hayes",
|
||||
"age": 34
|
||||
},
|
||||
{
|
||||
"$id": "h6oozcngbz8o5x4y",
|
||||
"name": "Rebecca Villegas",
|
||||
"age": 52
|
||||
},
|
||||
{
|
||||
"$id": "9v6z1pn2f9twcy12",
|
||||
"name": "Donald Shah",
|
||||
"age": 61
|
||||
},
|
||||
{
|
||||
"$id": "wzz3jduioso77o7f",
|
||||
"name": "Denise Cain",
|
||||
"age": 59
|
||||
},
|
||||
{
|
||||
"$id": "u51plhgvjodkswnr",
|
||||
"name": "Kristine Ramirez",
|
||||
"age": 53
|
||||
},
|
||||
{
|
||||
"$id": "t1uhkmiytfyc13vc",
|
||||
"name": "Stacey Adkins",
|
||||
"age": 61
|
||||
},
|
||||
{
|
||||
"$id": "iqaqnf0ybg2ct507",
|
||||
"name": "Daniel Hunt",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "idwrwv2uu4hcpv2i",
|
||||
"name": "Roberta Johnson",
|
||||
"age": 48
|
||||
},
|
||||
{
|
||||
"$id": "2yd2hd6auetjacyo",
|
||||
"name": "Jason Williamson",
|
||||
"age": 39
|
||||
},
|
||||
{
|
||||
"$id": "egrmdbibnjhi914x",
|
||||
"name": "Sandra Robinson",
|
||||
"age": 50
|
||||
},
|
||||
{
|
||||
"$id": "15m1pz2bb0ercgyk",
|
||||
"name": "Steve Rice",
|
||||
"age": 25
|
||||
},
|
||||
{
|
||||
"$id": "0i21bhkxdagjurb7",
|
||||
"name": "Kimberly Fritz",
|
||||
"age": 53
|
||||
},
|
||||
{
|
||||
"$id": "726ofi7h5snreq67",
|
||||
"name": "Brianna Reynolds",
|
||||
"age": 33
|
||||
},
|
||||
{
|
||||
"$id": "csqxse3wym56eim6",
|
||||
"name": "Alexander Williams",
|
||||
"age": 50
|
||||
},
|
||||
{
|
||||
"$id": "qeaoylnrsf8p3byg",
|
||||
"name": "Andrew Thomas",
|
||||
"age": 25
|
||||
},
|
||||
{
|
||||
"$id": "edsswobumzyzbvhf",
|
||||
"name": "Austin Williams",
|
||||
"age": 57
|
||||
},
|
||||
{
|
||||
"$id": "hdzhzpt0ahy5hkib",
|
||||
"name": "Nicholas Williams",
|
||||
"age": 24
|
||||
},
|
||||
{
|
||||
"$id": "w1qmvmg4roa8xnwu",
|
||||
"name": "Mrs. Michelle Cisneros",
|
||||
"age": 48
|
||||
},
|
||||
{
|
||||
"$id": "3z3o73x7adyuo6w0",
|
||||
"name": "Stacey Smith",
|
||||
"age": 39
|
||||
},
|
||||
{
|
||||
"$id": "sse2u5zlgoqrgmcf",
|
||||
"name": "Laura Beck",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "rvovijmvch58r4yx",
|
||||
"name": "Molly Clark",
|
||||
"age": 51
|
||||
},
|
||||
{
|
||||
"$id": "doe06nrx8sg5mcuv",
|
||||
"name": "Carmen Morris",
|
||||
"age": 41
|
||||
},
|
||||
{
|
||||
"$id": "jbjdwuvj5s4kw04y",
|
||||
"name": "Amanda Munoz",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "6k2ewkla7js0yw23",
|
||||
"name": "Rachel Collins",
|
||||
"age": 44
|
||||
},
|
||||
{
|
||||
"$id": "fcxuyr4kkhrnigu1",
|
||||
"name": "John Alexander",
|
||||
"age": 18
|
||||
},
|
||||
{
|
||||
"$id": "d25fuwlos5mk07o0",
|
||||
"name": "Stacy Hunter",
|
||||
"age": 22
|
||||
},
|
||||
{
|
||||
"$id": "1vdai2rxmwd57oet",
|
||||
"name": "Eric Massey",
|
||||
"age": 40
|
||||
},
|
||||
{
|
||||
"$id": "pq4jnt9izu1wlrzd",
|
||||
"name": "Scott Garcia",
|
||||
"age": 20
|
||||
},
|
||||
{
|
||||
"$id": "lz9kfc0lty5xcz14",
|
||||
"name": "Cassandra Nelson",
|
||||
"age": 35
|
||||
},
|
||||
{
|
||||
"$id": "pu7w6tyab5jd4we9",
|
||||
"name": "Aaron Johnson",
|
||||
"age": 50
|
||||
},
|
||||
{
|
||||
"$id": "8dupswd2kqwdyn8v",
|
||||
"name": "Shannon Sherman",
|
||||
"age": 45
|
||||
},
|
||||
{
|
||||
"$id": "ye466l71jthiz2p6",
|
||||
"name": "April Garcia",
|
||||
"age": 60
|
||||
},
|
||||
{
|
||||
"$id": "xogsmfwb73l16qdt",
|
||||
"name": "Evan Lynn",
|
||||
"age": 20
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,162 @@
|
||||
[
|
||||
{
|
||||
"$id": "doc01",
|
||||
"name": "Alice",
|
||||
"email": "alice@test.com",
|
||||
"age": 35,
|
||||
"active": false,
|
||||
"tags": [
|
||||
"analyst",
|
||||
"engineer"
|
||||
],
|
||||
"address": {
|
||||
"city": "New York",
|
||||
"zip": "27436",
|
||||
"country": "DE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc02",
|
||||
"name": "Bob",
|
||||
"email": "bob@test.com",
|
||||
"age": 58,
|
||||
"active": false,
|
||||
"tags": [
|
||||
"engineer",
|
||||
"manager"
|
||||
],
|
||||
"address": {
|
||||
"city": "London",
|
||||
"zip": "49320",
|
||||
"country": "JP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc03",
|
||||
"name": "Charlie",
|
||||
"email": "charlie@test.com",
|
||||
"age": 23,
|
||||
"active": true,
|
||||
"tags": [
|
||||
"analyst",
|
||||
"manager"
|
||||
],
|
||||
"address": {
|
||||
"city": "Tokyo",
|
||||
"zip": "74758",
|
||||
"country": "JP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc04",
|
||||
"name": "Diana",
|
||||
"email": "diana@test.com",
|
||||
"age": 60,
|
||||
"active": true,
|
||||
"tags": [
|
||||
"analyst",
|
||||
"manager"
|
||||
],
|
||||
"address": {
|
||||
"city": "Paris",
|
||||
"zip": "50703",
|
||||
"country": "JP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc05",
|
||||
"name": "Eve",
|
||||
"email": "eve@test.com",
|
||||
"age": 59,
|
||||
"active": false,
|
||||
"tags": [
|
||||
"engineer",
|
||||
"designer"
|
||||
],
|
||||
"address": {
|
||||
"city": "Berlin",
|
||||
"zip": "98929",
|
||||
"country": "UK"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc06",
|
||||
"name": "Frank",
|
||||
"email": "frank@test.com",
|
||||
"age": 23,
|
||||
"active": true,
|
||||
"tags": [
|
||||
"engineer",
|
||||
"manager"
|
||||
],
|
||||
"address": {
|
||||
"city": "Sydney",
|
||||
"zip": "82907",
|
||||
"country": "US"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc07",
|
||||
"name": "Grace",
|
||||
"email": "grace@test.com",
|
||||
"age": 36,
|
||||
"active": true,
|
||||
"tags": [
|
||||
"analyst",
|
||||
"developer"
|
||||
],
|
||||
"address": {
|
||||
"city": "Toronto",
|
||||
"zip": "68710",
|
||||
"country": "US"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc08",
|
||||
"name": "Hank",
|
||||
"email": "hank@test.com",
|
||||
"age": 28,
|
||||
"active": false,
|
||||
"tags": [
|
||||
"engineer",
|
||||
"manager"
|
||||
],
|
||||
"address": {
|
||||
"city": "Mumbai",
|
||||
"zip": "61026",
|
||||
"country": "FR"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc09",
|
||||
"name": "Iris",
|
||||
"email": "iris@test.com",
|
||||
"age": 40,
|
||||
"active": true,
|
||||
"tags": [
|
||||
"developer",
|
||||
"designer"
|
||||
],
|
||||
"address": {
|
||||
"city": "Seoul",
|
||||
"zip": "48039",
|
||||
"country": "FR"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$id": "doc10",
|
||||
"name": "Jack",
|
||||
"email": "jack@test.com",
|
||||
"age": 45,
|
||||
"active": false,
|
||||
"tags": [
|
||||
"manager",
|
||||
"developer"
|
||||
],
|
||||
"address": {
|
||||
"city": "Dubai",
|
||||
"zip": "22733",
|
||||
"country": "JP"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,602 @@
|
||||
[
|
||||
{
|
||||
"$id": "r5ctmrqwqn1m3rc0",
|
||||
"name": "Diamond Mendez",
|
||||
"age": 56,
|
||||
"email": "diamond.mendez@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "wxwp7e7q7nx3ltfx",
|
||||
"name": "Michael Huff",
|
||||
"age": 20,
|
||||
"email": "michael.huff@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "4ct0b38fwaojawlv",
|
||||
"name": "Alyssa Rodriguez",
|
||||
"age": 37,
|
||||
"email": "alyssa.rodriguez@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "o0jjcuygbta2zvga",
|
||||
"name": "Barbara Smith",
|
||||
"age": 26,
|
||||
"email": "barbara.smith@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "bdy6l2ofl8klb4pb",
|
||||
"name": "Evelyn Edwards",
|
||||
"age": 54,
|
||||
"email": "evelyn.edwards@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "rkccl72v7zwtbila",
|
||||
"name": "Tina Richardson",
|
||||
"age": 41,
|
||||
"email": "tina.richardson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "cilw7um0cd927esj",
|
||||
"name": "Joel Hernandez",
|
||||
"age": 49,
|
||||
"email": "joel.hernandez@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "60povvz0votkve1j",
|
||||
"name": "Zachary Cooper",
|
||||
"age": 59,
|
||||
"email": "zachary.cooper@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "ayow5dzwktvbbtp2",
|
||||
"name": "Brittany Spears",
|
||||
"age": 20,
|
||||
"email": "brittany.spears@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "cfru98od0lab0b2n",
|
||||
"name": "Holly White",
|
||||
"age": 47,
|
||||
"email": "holly.white@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "vjxjldvu3r6uylq6",
|
||||
"name": "Kimberly Barnes",
|
||||
"age": 27,
|
||||
"email": "kimberly.barnes@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "d1p47hl97pw6xowb",
|
||||
"name": "Stephen Miller",
|
||||
"age": 53,
|
||||
"email": "stephen.miller@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "yxk6qaa5ryb3gqrb",
|
||||
"name": "Yvonne Newman",
|
||||
"age": 41,
|
||||
"email": "yvonne.newman@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "ifkeo7j8t7hfd7z8",
|
||||
"name": "Carol Kane",
|
||||
"age": 38,
|
||||
"email": "carol.kane@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "e1q4lq8vvpxp9ysb",
|
||||
"name": "Doris Foster",
|
||||
"age": 44,
|
||||
"email": "doris.foster@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "obec6d52dc6swzsf",
|
||||
"name": "Joseph Stokes",
|
||||
"age": 28,
|
||||
"email": "joseph.stokes@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "26v06la6ug8wkvim",
|
||||
"name": "Steve Williams",
|
||||
"age": 31,
|
||||
"email": "steve.williams@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "m4glre4ch12vkxp6",
|
||||
"name": "James Carey",
|
||||
"age": 29,
|
||||
"email": "james.carey@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "jee8fyfffnjugsd5",
|
||||
"name": "Kathryn Henry",
|
||||
"age": 38,
|
||||
"email": "kathryn.henry@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "miquc6ljb9l3a31r",
|
||||
"name": "Christopher Landry",
|
||||
"age": 23,
|
||||
"email": "christopher.landry@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "ghf7da7seeuj1zdl",
|
||||
"name": "Jennifer Mcgee",
|
||||
"age": 62,
|
||||
"email": "jennifer.mcgee@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "x7h11phjrz77w0q8",
|
||||
"name": "Cathy Church",
|
||||
"age": 35,
|
||||
"email": "cathy.church@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "dn8u5lsux4708z6j",
|
||||
"name": "Jose Lopez",
|
||||
"age": 41,
|
||||
"email": "jose.lopez@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "zb7fdlyohuyy5i9k",
|
||||
"name": "William Rose",
|
||||
"age": 30,
|
||||
"email": "william.rose@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "qyrj8m9krp4dt4wt",
|
||||
"name": "Sarah Ford",
|
||||
"age": 26,
|
||||
"email": "sarah.ford@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "t6t673zpfyhhz8pg",
|
||||
"name": "Alisha Jones",
|
||||
"age": 61,
|
||||
"email": "alisha.jones@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "0hfbo0iy1q9bwc2n",
|
||||
"name": "Kristin Kelly",
|
||||
"age": 61,
|
||||
"email": "kristin.kelly@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "8alv4e4xrpcj443z",
|
||||
"name": "Brendan Stout",
|
||||
"age": 40,
|
||||
"email": "brendan.stout@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "qxm7a0z32xdkzxdj",
|
||||
"name": "Kelly Cruz",
|
||||
"age": 18,
|
||||
"email": "kelly.cruz@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "885mti7j7oiz5p5g",
|
||||
"name": "Samantha Martin",
|
||||
"age": 50,
|
||||
"email": "samantha.martin@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "v8i7dvhby6711m66",
|
||||
"name": "David Santos",
|
||||
"age": 46,
|
||||
"email": "david.santos@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "rggc0ow8ccd2jgvp",
|
||||
"name": "Elizabeth Carroll",
|
||||
"age": 22,
|
||||
"email": "elizabeth.carroll@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "012472s64rvzq1c4",
|
||||
"name": "Corey Owens",
|
||||
"age": 46,
|
||||
"email": "corey.owens@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "0k2xrwj4g33ut14y",
|
||||
"name": "Shelby Mueller",
|
||||
"age": 65,
|
||||
"email": "shelby.mueller@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "s3y9rl4uzf3difiq",
|
||||
"name": "Dr. Sylvia Myers",
|
||||
"age": 29,
|
||||
"email": "sylvia.myers@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "ntpc2td892t7f6an",
|
||||
"name": "Scott Freeman",
|
||||
"age": 48,
|
||||
"email": "scott.freeman@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "7f703gibyr5ijdmt",
|
||||
"name": "Christopher Atkinson",
|
||||
"age": 21,
|
||||
"email": "christopher.atkinson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "r2jdf2pivkxmqd0l",
|
||||
"name": "Sean Diaz",
|
||||
"age": 31,
|
||||
"email": "sean.diaz@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "fj98fji1lrxeigs9",
|
||||
"name": "Bobby Dyer",
|
||||
"age": 57,
|
||||
"email": "bobby.dyer@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "mehqmzp9u7xv1z3j",
|
||||
"name": "Daniel Hall",
|
||||
"age": 62,
|
||||
"email": "daniel.hall@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "4cd5ln65qjfv3h4j",
|
||||
"name": "Jennifer Ramirez",
|
||||
"age": 65,
|
||||
"email": "jennifer.ramirez@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "wdi6ap0oa7m1ab1d",
|
||||
"name": "Angela Jackson",
|
||||
"age": 57,
|
||||
"email": "angela.jackson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "l2foqjhxvjhjzijb",
|
||||
"name": "Kelly Lewis",
|
||||
"age": 36,
|
||||
"email": "kelly.lewis@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "d963t5yu35uagwm4",
|
||||
"name": "Jessica Munoz",
|
||||
"age": 55,
|
||||
"email": "jessica.munoz@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "99ez9uxsim8zp64m",
|
||||
"name": "Kelly George",
|
||||
"age": 65,
|
||||
"email": "kelly.george@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "v7wl221gycftl63d",
|
||||
"name": "Anthony Johnson",
|
||||
"age": 65,
|
||||
"email": "anthony.johnson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "p2zzj0lnmjvqzfc3",
|
||||
"name": "Regina Fields",
|
||||
"age": 61,
|
||||
"email": "regina.fields@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "fk655e243z2ivvx6",
|
||||
"name": "Sharon Schaefer",
|
||||
"age": 30,
|
||||
"email": "sharon.schaefer@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "4ywsv6fw8g2d8ncw",
|
||||
"name": "Jacob French",
|
||||
"age": 62,
|
||||
"email": "jacob.french@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "y61q9k6g4h0fxxz4",
|
||||
"name": "Jessica Costa",
|
||||
"age": 23,
|
||||
"email": "jessica.costa@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "knj4hfzsthk7vx5n",
|
||||
"name": "George Hardy",
|
||||
"age": 53,
|
||||
"email": "george.hardy@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "a88u9w2pct2nn8l6",
|
||||
"name": "Andrea Allison",
|
||||
"age": 20,
|
||||
"email": "andrea.allison@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "hw960v1ybycrwr5o",
|
||||
"name": "Kevin Ferguson",
|
||||
"age": 32,
|
||||
"email": "kevin.ferguson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "j9garslpgx6jgzgb",
|
||||
"name": "Joseph Johnson",
|
||||
"age": 58,
|
||||
"email": "joseph.johnson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "gv101bz36elm84cd",
|
||||
"name": "Ashley Martinez",
|
||||
"age": 18,
|
||||
"email": "ashley.martinez@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "xrvzgt3gc0c7g4cl",
|
||||
"name": "Charles Nixon",
|
||||
"age": 23,
|
||||
"email": "charles.nixon@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "awjlu7uk0eutcfpb",
|
||||
"name": "Carol Dudley",
|
||||
"age": 40,
|
||||
"email": "carol.dudley@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "95oi26p2zdudpime",
|
||||
"name": "David Weber",
|
||||
"age": 51,
|
||||
"email": "david.weber@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "h8x7pkhdvu5bcp89",
|
||||
"name": "Scott Robinson",
|
||||
"age": 32,
|
||||
"email": "scott.robinson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "oj6cu4jm1z2afe7s",
|
||||
"name": "Anthony Hardy",
|
||||
"age": 38,
|
||||
"email": "anthony.hardy@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "hgsdi1g30poqqmf0",
|
||||
"name": "Mackenzie Owens",
|
||||
"age": 52,
|
||||
"email": "mackenzie.owens@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "8fzdz914bqlqk2tc",
|
||||
"name": "Brian Foster",
|
||||
"age": 27,
|
||||
"email": "brian.foster@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "fwlqoeiunjhczpl0",
|
||||
"name": "Hannah Forbes",
|
||||
"age": 56,
|
||||
"email": "hannah.forbes@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "rsv8156goe8z4j6j",
|
||||
"name": "Lauren Reed",
|
||||
"age": 26,
|
||||
"email": "lauren.reed@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "1fjqv3w7uwbswe2p",
|
||||
"name": "Morgan Smith",
|
||||
"age": 28,
|
||||
"email": "morgan.smith@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "soqrzmhhg05hhzn4",
|
||||
"name": "Samantha Alexander",
|
||||
"age": 65,
|
||||
"email": "samantha.alexander@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "8quy52cto9kjjokp",
|
||||
"name": "Tiffany Roberts",
|
||||
"age": 20,
|
||||
"email": "tiffany.roberts@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "e3i1g1lw04v7jd89",
|
||||
"name": "Emily Hayes",
|
||||
"age": 34,
|
||||
"email": "emily.hayes@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "s7n8lzb0sw7h93z1",
|
||||
"name": "Rebecca Villegas",
|
||||
"age": 52,
|
||||
"email": "rebecca.villegas@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "e2lc7i81tpkqs1rp",
|
||||
"name": "Donald Shah",
|
||||
"age": 61,
|
||||
"email": "donald.shah@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "3oe2mysup1xluiw0",
|
||||
"name": "Denise Cain",
|
||||
"age": 59,
|
||||
"email": "denise.cain@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "1vqypc37f85nuqz4",
|
||||
"name": "Kristine Ramirez",
|
||||
"age": 53,
|
||||
"email": "kristine.ramirez@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "m0uh7r3dc6z8ucb4",
|
||||
"name": "Stacey Adkins",
|
||||
"age": 61,
|
||||
"email": "stacey.adkins@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "jdofz6x1ahganmqf",
|
||||
"name": "Daniel Hunt",
|
||||
"age": 20,
|
||||
"email": "daniel.hunt@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "vbe903c2q4m4q97g",
|
||||
"name": "Roberta Johnson",
|
||||
"age": 48,
|
||||
"email": "roberta.johnson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "sndngrxuwpd93pdb",
|
||||
"name": "Jason Williamson",
|
||||
"age": 39,
|
||||
"email": "jason.williamson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "66hvaw2p5xwf07p8",
|
||||
"name": "Sandra Robinson",
|
||||
"age": 50,
|
||||
"email": "sandra.robinson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "9pvingfsl8cmag5c",
|
||||
"name": "Steve Rice",
|
||||
"age": 25,
|
||||
"email": "steve.rice@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "qe154m5hh00u4iiz",
|
||||
"name": "Kimberly Fritz",
|
||||
"age": 53,
|
||||
"email": "kimberly.fritz@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "avqnbrco2f0tfupk",
|
||||
"name": "Brianna Reynolds",
|
||||
"age": 33,
|
||||
"email": "brianna.reynolds@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "cqs10gi2qu1r3ugb",
|
||||
"name": "Alexander Williams",
|
||||
"age": 50,
|
||||
"email": "alexander.williams@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "jrpmfi6hmm7pmegp",
|
||||
"name": "Andrew Thomas",
|
||||
"age": 25,
|
||||
"email": "andrew.thomas@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "heeab2qqf0zm446f",
|
||||
"name": "Austin Williams",
|
||||
"age": 57,
|
||||
"email": "austin.williams@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "bkhugvnil7kjchm6",
|
||||
"name": "Nicholas Williams",
|
||||
"age": 24,
|
||||
"email": "nicholas.williams@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "b045j302pvv8l1p4",
|
||||
"name": "Mrs. Michelle Cisneros",
|
||||
"age": 48,
|
||||
"email": "michelle.cisneros@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "aikhii5q210lrfpr",
|
||||
"name": "Stacey Smith",
|
||||
"age": 39,
|
||||
"email": "stacey.smith@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "x0zajitea1z2dfo0",
|
||||
"name": "Laura Beck",
|
||||
"age": 20,
|
||||
"email": "laura.beck@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "abeecki7mdff1tv0",
|
||||
"name": "Molly Clark",
|
||||
"age": 51,
|
||||
"email": "molly.clark@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "yizama8r3i1to548",
|
||||
"name": "Carmen Morris",
|
||||
"age": 41,
|
||||
"email": "carmen.morris@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "8690yh971g4rgspj",
|
||||
"name": "Amanda Munoz",
|
||||
"age": 20,
|
||||
"email": "amanda.munoz@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "cd9vk5v97t359ul2",
|
||||
"name": "Rachel Collins",
|
||||
"age": 44,
|
||||
"email": "rachel.collins@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "wrkgmx1v0w9ja4l8",
|
||||
"name": "John Alexander",
|
||||
"age": 18,
|
||||
"email": "john.alexander@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "kxp3ucqo6ped4ss7",
|
||||
"name": "Stacy Hunter",
|
||||
"age": 22,
|
||||
"email": "stacy.hunter@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "dbvv8okae2qgo0gm",
|
||||
"name": "Eric Massey",
|
||||
"age": 40,
|
||||
"email": "eric.massey@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "9tn3nm6ppnayisje",
|
||||
"name": "Scott Garcia",
|
||||
"age": 20,
|
||||
"email": "scott.garcia@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "1xuc5t60xpcvd4qi",
|
||||
"name": "Cassandra Nelson",
|
||||
"age": 35,
|
||||
"email": "cassandra.nelson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "qao1nulwn0kqyfkc",
|
||||
"name": "Aaron Johnson",
|
||||
"age": 50,
|
||||
"email": "aaron.johnson@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "kd2q6owvuwsy5knx",
|
||||
"name": "Shannon Sherman",
|
||||
"age": 45,
|
||||
"email": "shannon.sherman@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "wsl37kjo0bib4wrc",
|
||||
"name": "April Garcia",
|
||||
"age": 60,
|
||||
"email": "april.garcia@example.com"
|
||||
},
|
||||
{
|
||||
"$id": "ujlz7k84xzfx4khs",
|
||||
"name": "Evan Lynn",
|
||||
"age": 20,
|
||||
"email": "evan.lynn@example.com"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,402 @@
|
||||
[
|
||||
{
|
||||
"$id": "hxfcwpcas5xokpwe",
|
||||
"name": "Diamond Mendez"
|
||||
},
|
||||
{
|
||||
"$id": "gw8nxwf6esn3tfwf",
|
||||
"name": "Michael Huff"
|
||||
},
|
||||
{
|
||||
"$id": "xb6bxg56lral1qy9",
|
||||
"name": "Alyssa Rodriguez"
|
||||
},
|
||||
{
|
||||
"$id": "imerjq5j36y3agh2",
|
||||
"name": "Barbara Smith"
|
||||
},
|
||||
{
|
||||
"$id": "07yq9qdlhmbzmr35",
|
||||
"name": "Evelyn Edwards"
|
||||
},
|
||||
{
|
||||
"$id": "ksqo631sbhwj5ltg",
|
||||
"name": "Tina Richardson"
|
||||
},
|
||||
{
|
||||
"$id": "j7zlndgu0gbshp15",
|
||||
"name": "Joel Hernandez"
|
||||
},
|
||||
{
|
||||
"$id": "mfntvnljrcmf7h6v",
|
||||
"name": "Zachary Cooper"
|
||||
},
|
||||
{
|
||||
"$id": "5f9b01nziqu2h8ed",
|
||||
"name": "Brittany Spears"
|
||||
},
|
||||
{
|
||||
"$id": "4vxzbnzraqznk5u8",
|
||||
"name": "Holly White"
|
||||
},
|
||||
{
|
||||
"$id": "d4ywy3mtphaatbpf",
|
||||
"name": "Kimberly Barnes"
|
||||
},
|
||||
{
|
||||
"$id": "88odnk6nthyyvbal",
|
||||
"name": "Stephen Miller"
|
||||
},
|
||||
{
|
||||
"$id": "08oekee3fn7mzaa5",
|
||||
"name": "Yvonne Newman"
|
||||
},
|
||||
{
|
||||
"$id": "quw55kn9895i5e4v",
|
||||
"name": "Carol Kane"
|
||||
},
|
||||
{
|
||||
"$id": "nge6bm8ykripei6f",
|
||||
"name": "Doris Foster"
|
||||
},
|
||||
{
|
||||
"$id": "4k16i33s0xl2ypx9",
|
||||
"name": "Joseph Stokes"
|
||||
},
|
||||
{
|
||||
"$id": "q0j5rxbgid66snyf",
|
||||
"name": "Steve Williams"
|
||||
},
|
||||
{
|
||||
"$id": "n1oxun7mqq3p103y",
|
||||
"name": "James Carey"
|
||||
},
|
||||
{
|
||||
"$id": "0dbvs840jkf8i0ye",
|
||||
"name": "Kathryn Henry"
|
||||
},
|
||||
{
|
||||
"$id": "5sfaidgs1h87v15v",
|
||||
"name": "Christopher Landry"
|
||||
},
|
||||
{
|
||||
"$id": "vg3punvfu5khmf41",
|
||||
"name": "Jennifer Mcgee"
|
||||
},
|
||||
{
|
||||
"$id": "f933qydr9u5b2r11",
|
||||
"name": "Cathy Church"
|
||||
},
|
||||
{
|
||||
"$id": "wjv87y1inf8yk32s",
|
||||
"name": "Jose Lopez"
|
||||
},
|
||||
{
|
||||
"$id": "uljysdvdlcyrbrwk",
|
||||
"name": "William Rose"
|
||||
},
|
||||
{
|
||||
"$id": "ot8xtzh77j55wq0s",
|
||||
"name": "Sarah Ford"
|
||||
},
|
||||
{
|
||||
"$id": "9t76vnsv2u36s43t",
|
||||
"name": "Alisha Jones"
|
||||
},
|
||||
{
|
||||
"$id": "66y4tnty62hw8c02",
|
||||
"name": "Kristin Kelly"
|
||||
},
|
||||
{
|
||||
"$id": "2punfblazi5v16ar",
|
||||
"name": "Brendan Stout"
|
||||
},
|
||||
{
|
||||
"$id": "sxhr4nf5w2gx4wbg",
|
||||
"name": "Kelly Cruz"
|
||||
},
|
||||
{
|
||||
"$id": "68dvrqfwqnkq5el9",
|
||||
"name": "Samantha Martin"
|
||||
},
|
||||
{
|
||||
"$id": "20192l6dbeinhkh0",
|
||||
"name": "David Santos"
|
||||
},
|
||||
{
|
||||
"$id": "si0l4dgay09ebfmf",
|
||||
"name": "Elizabeth Carroll"
|
||||
},
|
||||
{
|
||||
"$id": "lhse40vbldqb6ap1",
|
||||
"name": "Corey Owens"
|
||||
},
|
||||
{
|
||||
"$id": "h5t3pslykyx3kxfm",
|
||||
"name": "Shelby Mueller"
|
||||
},
|
||||
{
|
||||
"$id": "ldc0luydrw6jub0f",
|
||||
"name": "Dr. Sylvia Myers"
|
||||
},
|
||||
{
|
||||
"$id": "voc9628xg4dsgw2y",
|
||||
"name": "Scott Freeman"
|
||||
},
|
||||
{
|
||||
"$id": "o4y0gk3gqv1ax2fz",
|
||||
"name": "Christopher Atkinson"
|
||||
},
|
||||
{
|
||||
"$id": "u1n3x4e4u7e0vzj6",
|
||||
"name": "Sean Diaz"
|
||||
},
|
||||
{
|
||||
"$id": "s36eskwtm0w7lwr7",
|
||||
"name": "Bobby Dyer"
|
||||
},
|
||||
{
|
||||
"$id": "4hjnag1p5iwvtixd",
|
||||
"name": "Daniel Hall"
|
||||
},
|
||||
{
|
||||
"$id": "m91d80oxsa216zbh",
|
||||
"name": "Jennifer Ramirez"
|
||||
},
|
||||
{
|
||||
"$id": "5hj6858zo2g85n6v",
|
||||
"name": "Angela Jackson"
|
||||
},
|
||||
{
|
||||
"$id": "8m8oihv9a1e7nn92",
|
||||
"name": "Kelly Lewis"
|
||||
},
|
||||
{
|
||||
"$id": "7azy39la0no0mxi7",
|
||||
"name": "Jessica Munoz"
|
||||
},
|
||||
{
|
||||
"$id": "47pmjkhnnqhyit8c",
|
||||
"name": "Kelly George"
|
||||
},
|
||||
{
|
||||
"$id": "6j6cpy4kgneg1mmh",
|
||||
"name": "Anthony Johnson"
|
||||
},
|
||||
{
|
||||
"$id": "tnlmtvap1zz89km9",
|
||||
"name": "Regina Fields"
|
||||
},
|
||||
{
|
||||
"$id": "6cyuvnwwqdmrpfzh",
|
||||
"name": "Sharon Schaefer"
|
||||
},
|
||||
{
|
||||
"$id": "p1v4pyu2pqodc0ey",
|
||||
"name": "Jacob French"
|
||||
},
|
||||
{
|
||||
"$id": "6npynnhjt2jd05xo",
|
||||
"name": "Jessica Costa"
|
||||
},
|
||||
{
|
||||
"$id": "wcxedf13n2e9qi4l",
|
||||
"name": "George Hardy"
|
||||
},
|
||||
{
|
||||
"$id": "yf2xlcmszk2tqeig",
|
||||
"name": "Andrea Allison"
|
||||
},
|
||||
{
|
||||
"$id": "3bf2zzv7poststwa",
|
||||
"name": "Kevin Ferguson"
|
||||
},
|
||||
{
|
||||
"$id": "c2iataz0hhv39q63",
|
||||
"name": "Joseph Johnson"
|
||||
},
|
||||
{
|
||||
"$id": "3e8npxhov4a39pvq",
|
||||
"name": "Ashley Martinez"
|
||||
},
|
||||
{
|
||||
"$id": "t7dp41tysipytywq",
|
||||
"name": "Charles Nixon"
|
||||
},
|
||||
{
|
||||
"$id": "z8cztq7c47phyfhk",
|
||||
"name": "Carol Dudley"
|
||||
},
|
||||
{
|
||||
"$id": "2636f9d8r4ipm3h6",
|
||||
"name": "David Weber"
|
||||
},
|
||||
{
|
||||
"$id": "eh3f6wxtvkjq6ykq",
|
||||
"name": "Scott Robinson"
|
||||
},
|
||||
{
|
||||
"$id": "raskbwpsje69a59h",
|
||||
"name": "Anthony Hardy"
|
||||
},
|
||||
{
|
||||
"$id": "90hn1p0b4cs9e2og",
|
||||
"name": "Mackenzie Owens"
|
||||
},
|
||||
{
|
||||
"$id": "am3swwfbo076x0v1",
|
||||
"name": "Brian Foster"
|
||||
},
|
||||
{
|
||||
"$id": "5uw7utb9lq5cfncw",
|
||||
"name": "Hannah Forbes"
|
||||
},
|
||||
{
|
||||
"$id": "cs6mbfzkzifefx6r",
|
||||
"name": "Lauren Reed"
|
||||
},
|
||||
{
|
||||
"$id": "ftw3uvztziiz9x00",
|
||||
"name": "Morgan Smith"
|
||||
},
|
||||
{
|
||||
"$id": "uhrqseeo43mozpaq",
|
||||
"name": "Samantha Alexander"
|
||||
},
|
||||
{
|
||||
"$id": "pvvmzyfc1lxor11e",
|
||||
"name": "Tiffany Roberts"
|
||||
},
|
||||
{
|
||||
"$id": "jia7bdag4abz123s",
|
||||
"name": "Emily Hayes"
|
||||
},
|
||||
{
|
||||
"$id": "h6oozcngbz8o5x4y",
|
||||
"name": "Rebecca Villegas"
|
||||
},
|
||||
{
|
||||
"$id": "9v6z1pn2f9twcy12",
|
||||
"name": "Donald Shah"
|
||||
},
|
||||
{
|
||||
"$id": "wzz3jduioso77o7f",
|
||||
"name": "Denise Cain"
|
||||
},
|
||||
{
|
||||
"$id": "u51plhgvjodkswnr",
|
||||
"name": "Kristine Ramirez"
|
||||
},
|
||||
{
|
||||
"$id": "t1uhkmiytfyc13vc",
|
||||
"name": "Stacey Adkins"
|
||||
},
|
||||
{
|
||||
"$id": "iqaqnf0ybg2ct507",
|
||||
"name": "Daniel Hunt"
|
||||
},
|
||||
{
|
||||
"$id": "idwrwv2uu4hcpv2i",
|
||||
"name": "Roberta Johnson"
|
||||
},
|
||||
{
|
||||
"$id": "2yd2hd6auetjacyo",
|
||||
"name": "Jason Williamson"
|
||||
},
|
||||
{
|
||||
"$id": "egrmdbibnjhi914x",
|
||||
"name": "Sandra Robinson"
|
||||
},
|
||||
{
|
||||
"$id": "15m1pz2bb0ercgyk",
|
||||
"name": "Steve Rice"
|
||||
},
|
||||
{
|
||||
"$id": "0i21bhkxdagjurb7",
|
||||
"name": "Kimberly Fritz"
|
||||
},
|
||||
{
|
||||
"$id": "726ofi7h5snreq67",
|
||||
"name": "Brianna Reynolds"
|
||||
},
|
||||
{
|
||||
"$id": "csqxse3wym56eim6",
|
||||
"name": "Alexander Williams"
|
||||
},
|
||||
{
|
||||
"$id": "qeaoylnrsf8p3byg",
|
||||
"name": "Andrew Thomas"
|
||||
},
|
||||
{
|
||||
"$id": "edsswobumzyzbvhf",
|
||||
"name": "Austin Williams"
|
||||
},
|
||||
{
|
||||
"$id": "hdzhzpt0ahy5hkib",
|
||||
"name": "Nicholas Williams"
|
||||
},
|
||||
{
|
||||
"$id": "w1qmvmg4roa8xnwu",
|
||||
"name": "Mrs. Michelle Cisneros"
|
||||
},
|
||||
{
|
||||
"$id": "3z3o73x7adyuo6w0",
|
||||
"name": "Stacey Smith"
|
||||
},
|
||||
{
|
||||
"$id": "sse2u5zlgoqrgmcf",
|
||||
"name": "Laura Beck"
|
||||
},
|
||||
{
|
||||
"$id": "rvovijmvch58r4yx",
|
||||
"name": "Molly Clark"
|
||||
},
|
||||
{
|
||||
"$id": "doe06nrx8sg5mcuv",
|
||||
"name": "Carmen Morris"
|
||||
},
|
||||
{
|
||||
"$id": "jbjdwuvj5s4kw04y",
|
||||
"name": "Amanda Munoz"
|
||||
},
|
||||
{
|
||||
"$id": "6k2ewkla7js0yw23",
|
||||
"name": "Rachel Collins"
|
||||
},
|
||||
{
|
||||
"$id": "fcxuyr4kkhrnigu1",
|
||||
"name": "John Alexander"
|
||||
},
|
||||
{
|
||||
"$id": "d25fuwlos5mk07o0",
|
||||
"name": "Stacy Hunter"
|
||||
},
|
||||
{
|
||||
"$id": "1vdai2rxmwd57oet",
|
||||
"name": "Eric Massey"
|
||||
},
|
||||
{
|
||||
"$id": "pq4jnt9izu1wlrzd",
|
||||
"name": "Scott Garcia"
|
||||
},
|
||||
{
|
||||
"$id": "lz9kfc0lty5xcz14",
|
||||
"name": "Cassandra Nelson"
|
||||
},
|
||||
{
|
||||
"$id": "pu7w6tyab5jd4we9",
|
||||
"name": "Aaron Johnson"
|
||||
},
|
||||
{
|
||||
"$id": "8dupswd2kqwdyn8v",
|
||||
"name": "Shannon Sherman"
|
||||
},
|
||||
{
|
||||
"$id": "ye466l71jthiz2p6",
|
||||
"name": "April Garcia"
|
||||
},
|
||||
{
|
||||
"$id": "xogsmfwb73l16qdt",
|
||||
"name": "Evan Lynn"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,262 @@
|
||||
[
|
||||
{
|
||||
"$id": "vec01",
|
||||
"metadata": {
|
||||
"title": "Vector Document 1",
|
||||
"score": 0.643,
|
||||
"category": "art"
|
||||
},
|
||||
"embeddings": [
|
||||
-0.516361,
|
||||
0.261456,
|
||||
-0.77323,
|
||||
0.909775,
|
||||
-0.003065,
|
||||
-0.626888,
|
||||
0.704171,
|
||||
-0.366385,
|
||||
0.198681,
|
||||
0.854914,
|
||||
-0.227548,
|
||||
-0.14278,
|
||||
-0.548405,
|
||||
-0.822939,
|
||||
0.407267,
|
||||
0.570791
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec02",
|
||||
"metadata": {
|
||||
"title": "Vector Document 2",
|
||||
"score": 0.322,
|
||||
"category": "art"
|
||||
},
|
||||
"embeddings": [
|
||||
0.103211,
|
||||
0.028378,
|
||||
0.806001,
|
||||
-0.412638,
|
||||
-0.200009,
|
||||
0.448239,
|
||||
-0.781144,
|
||||
0.621957,
|
||||
-0.868352,
|
||||
0.217013,
|
||||
0.308217,
|
||||
-0.851528,
|
||||
-0.340766,
|
||||
0.805996,
|
||||
-0.065638,
|
||||
-0.363951
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec03",
|
||||
"metadata": {
|
||||
"title": "Vector Document 3",
|
||||
"score": 0.503,
|
||||
"category": "science"
|
||||
},
|
||||
"embeddings": [
|
||||
0.971901,
|
||||
-0.475154,
|
||||
0.60526,
|
||||
-0.151618,
|
||||
-0.938199,
|
||||
-0.139977,
|
||||
0.154581,
|
||||
-0.868021,
|
||||
0.069567,
|
||||
0.233545,
|
||||
-0.164826,
|
||||
0.239036,
|
||||
0.8875,
|
||||
0.488075,
|
||||
0.787231,
|
||||
0.090293
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec04",
|
||||
"metadata": {
|
||||
"title": "Vector Document 4",
|
||||
"score": 0.943,
|
||||
"category": "science"
|
||||
},
|
||||
"embeddings": [
|
||||
0.372035,
|
||||
-0.868405,
|
||||
-0.974987,
|
||||
-0.836673,
|
||||
0.030057,
|
||||
-0.667698,
|
||||
0.095189,
|
||||
0.518373,
|
||||
-0.173732,
|
||||
0.966379,
|
||||
0.509861,
|
||||
-0.172607,
|
||||
0.420855,
|
||||
-0.534562,
|
||||
0.600872,
|
||||
0.194391
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec05",
|
||||
"metadata": {
|
||||
"title": "Vector Document 5",
|
||||
"score": 0.924,
|
||||
"category": "music"
|
||||
},
|
||||
"embeddings": [
|
||||
-0.318783,
|
||||
-0.876486,
|
||||
0.843971,
|
||||
-0.472869,
|
||||
-0.350164,
|
||||
0.936237,
|
||||
-0.741591,
|
||||
0.298213,
|
||||
-0.075429,
|
||||
0.243415,
|
||||
0.929625,
|
||||
0.96649,
|
||||
0.251843,
|
||||
-0.371953,
|
||||
0.348079,
|
||||
0.090382
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec06",
|
||||
"metadata": {
|
||||
"title": "Vector Document 6",
|
||||
"score": 0.385,
|
||||
"category": "science"
|
||||
},
|
||||
"embeddings": [
|
||||
-0.608111,
|
||||
0.549885,
|
||||
0.720346,
|
||||
0.260442,
|
||||
-0.023344,
|
||||
0.800346,
|
||||
-0.956586,
|
||||
-0.407161,
|
||||
0.516883,
|
||||
0.230456,
|
||||
0.376704,
|
||||
-0.347203,
|
||||
0.925657,
|
||||
-0.486608,
|
||||
0.330032,
|
||||
0.323414
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec07",
|
||||
"metadata": {
|
||||
"title": "Vector Document 7",
|
||||
"score": 0.61,
|
||||
"category": "science"
|
||||
},
|
||||
"embeddings": [
|
||||
0.45451,
|
||||
-0.251783,
|
||||
-0.479459,
|
||||
0.167412,
|
||||
0.890343,
|
||||
0.415136,
|
||||
-0.506263,
|
||||
0.318171,
|
||||
-0.729294,
|
||||
-0.907919,
|
||||
0.607033,
|
||||
0.652333,
|
||||
0.97129,
|
||||
-0.118671,
|
||||
0.110045,
|
||||
-0.514242
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec08",
|
||||
"metadata": {
|
||||
"title": "Vector Document 8",
|
||||
"score": 0.107,
|
||||
"category": "history"
|
||||
},
|
||||
"embeddings": [
|
||||
-0.883255,
|
||||
-0.294593,
|
||||
-0.153187,
|
||||
0.595936,
|
||||
0.341818,
|
||||
-0.410719,
|
||||
0.017348,
|
||||
-0.350047,
|
||||
-0.888906,
|
||||
0.344646,
|
||||
0.184335,
|
||||
-0.429053,
|
||||
0.540542,
|
||||
-0.408493,
|
||||
-0.982748,
|
||||
-0.255031
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec09",
|
||||
"metadata": {
|
||||
"title": "Vector Document 9",
|
||||
"score": 0.914,
|
||||
"category": "art"
|
||||
},
|
||||
"embeddings": [
|
||||
0.31891,
|
||||
0.502493,
|
||||
-0.245712,
|
||||
-0.325282,
|
||||
-0.514772,
|
||||
0.097323,
|
||||
0.812412,
|
||||
-0.762069,
|
||||
0.846688,
|
||||
-0.391482,
|
||||
0.879953,
|
||||
0.986352,
|
||||
-0.562588,
|
||||
-0.566111,
|
||||
0.163109,
|
||||
0.119346
|
||||
]
|
||||
},
|
||||
{
|
||||
"$id": "vec10",
|
||||
"metadata": {
|
||||
"title": "Vector Document 10",
|
||||
"score": 0.169,
|
||||
"category": "science"
|
||||
},
|
||||
"embeddings": [
|
||||
-0.563645,
|
||||
-0.19119,
|
||||
-0.853168,
|
||||
-0.526952,
|
||||
-0.459635,
|
||||
0.850766,
|
||||
-0.345261,
|
||||
0.547467,
|
||||
0.455409,
|
||||
-0.507607,
|
||||
-0.228436,
|
||||
-0.587395,
|
||||
0.109146,
|
||||
0.190046,
|
||||
0.861559,
|
||||
-0.724741
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -134,7 +134,7 @@ class ModuleTest extends TestCase
|
||||
$this->assertActionParams($action, [
|
||||
'appDomain', 'httpPort', 'httpsPort', 'emailCertificates', 'opensslKey',
|
||||
'assistantOpenAIKey', 'accountEmail', 'accountPassword', 'database',
|
||||
'installId', 'retryStep',
|
||||
'installId', 'retryStep', 'migrate',
|
||||
]);
|
||||
$this->assertActionInjects($action, ['request', 'response', 'swooleResponse', 'installerState', 'installerConfig', 'installerPaths']);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Tests\Unit\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
use Appwrite\Utopia\Response\Filters\V16;
|
||||
use Cron\CronExpression;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
@@ -11,10 +12,7 @@ use Utopia\Database\DateTime;
|
||||
|
||||
class V16Test extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Filter
|
||||
*/
|
||||
protected $filter = null;
|
||||
protected Filter $filter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
||||
@@ -3,16 +3,14 @@
|
||||
namespace Tests\Unit\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
use Appwrite\Utopia\Response\Filters\V17;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class V17Test extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Filter
|
||||
*/
|
||||
protected $filter = null;
|
||||
protected Filter $filter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
||||
@@ -3,16 +3,14 @@
|
||||
namespace Tests\Unit\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
use Appwrite\Utopia\Response\Filters\V18;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class V18Test extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Filter
|
||||
*/
|
||||
protected $filter = null;
|
||||
protected Filter $filter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
||||
@@ -3,16 +3,14 @@
|
||||
namespace Tests\Unit\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
use Appwrite\Utopia\Response\Filters\V19;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class V19Test extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Filter
|
||||
*/
|
||||
protected $filter = null;
|
||||
protected Filter $filter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user