Merge branch '1.6.x' into feat-1.5.x-mfa-emails

This commit is contained in:
Bradley Schofield
2024-10-04 10:58:35 +09:00
committed by GitHub
49 changed files with 2698 additions and 2733 deletions
+19 -16
View File
@@ -16,22 +16,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build Appwrite
uses: docker/build-push-action@v3
uses: docker/build-push-action@v6
with:
context: .
push: false
tags: ${{ env.IMAGE }}
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
cache-from: type=gha,scope=appwrite
cache-to: type=gha,mode=max,scope=appwrite
outputs: type=docker,dest=/tmp/${{ env.IMAGE }}.tar
build-args: |
DEBUG=false
@@ -39,9 +39,11 @@ jobs:
VERSION=dev
- name: Cache Docker Image
uses: actions/cache@v3
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
restore-keys: |
appwrite-dev-
path: /tmp/${{ env.IMAGE }}.tar
unit_test:
@@ -51,10 +53,10 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -81,10 +83,10 @@ jobs:
needs: setup
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -113,6 +115,7 @@ jobs:
Console,
Databases,
Functions,
FunctionsSchedule,
GraphQL,
Health,
Locale,
@@ -128,10 +131,10 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
@@ -141,7 +144,7 @@ jobs:
run: |
docker load --input /tmp/${{ env.IMAGE }}.tar
docker compose up -d
sleep 25
sleep 30
- name: Run ${{matrix.service}} Tests
run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
@@ -149,15 +152,15 @@ jobs:
- name: Run ${{matrix.service}} Shared Tables Tests
run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug
benchamrking:
benchmarking:
name: Benchmark
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Load Cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
key: ${{ env.CACHE_KEY }}
path: /tmp/${{ env.IMAGE }}.tar
+34 -1
View File
@@ -4109,13 +4109,24 @@ $projectCollections = array_merge([
'$id' => ID::custom('source'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 8192,
'size' => 8192, // reduce size
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('destination'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false, // make true after patch script
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('credentials'),
'type' => Database::VAR_STRING,
@@ -4138,6 +4149,28 @@ $projectCollections = array_merge([
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('statusCounters'),
'type' => Database::VAR_STRING,
-4
View File
@@ -1,9 +1,5 @@
<?php
const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
return [
APP_PLATFORM_CLIENT => [
'key' => APP_PLATFORM_CLIENT,
+1 -1
View File
@@ -75,7 +75,7 @@ $admins = [
'topics.write',
'topics.read',
'subscribers.write',
'subscribers.read'
'subscribers.read',
];
return [
+136 -4
View File
@@ -32948,6 +32948,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -32967,6 +32977,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"size"
]
},
@@ -33005,6 +33017,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -33032,7 +33054,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeFloat": {
@@ -33070,6 +33094,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -33097,7 +33131,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeBoolean": {
@@ -33135,6 +33171,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -33147,7 +33193,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeEmail": {
@@ -33185,6 +33233,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33203,6 +33261,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33241,6 +33301,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -33267,6 +33337,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"elements",
"format"
]
@@ -33306,6 +33378,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33324,6 +33406,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33362,6 +33446,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33380,6 +33474,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33418,6 +33514,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -33436,6 +33542,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33474,6 +33582,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -33511,6 +33629,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -33559,6 +33679,16 @@
},
"x-example": [],
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Index creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Index update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -33566,7 +33696,9 @@
"type",
"status",
"error",
"attributes"
"attributes",
"$createdAt",
"$updatedAt"
]
},
"document": {
+136 -4
View File
@@ -23678,6 +23678,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -23697,6 +23707,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"size"
]
},
@@ -23735,6 +23747,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -23762,7 +23784,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeFloat": {
@@ -23800,6 +23824,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -23827,7 +23861,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeBoolean": {
@@ -23865,6 +23901,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -23877,7 +23923,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeEmail": {
@@ -23915,6 +23963,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -23933,6 +23991,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -23971,6 +24031,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -23997,6 +24067,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"elements",
"format"
]
@@ -24036,6 +24108,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24054,6 +24136,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24092,6 +24176,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24110,6 +24204,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24148,6 +24244,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -24166,6 +24272,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24204,6 +24312,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -24241,6 +24359,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -24289,6 +24409,16 @@
},
"x-example": [],
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Index creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Index update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -24296,7 +24426,9 @@
"type",
"status",
"error",
"attributes"
"attributes",
"$createdAt",
"$updatedAt"
]
},
"document": {
+136 -4
View File
@@ -32948,6 +32948,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -32967,6 +32977,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"size"
]
},
@@ -33005,6 +33017,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -33032,7 +33054,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeFloat": {
@@ -33070,6 +33094,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -33097,7 +33131,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeBoolean": {
@@ -33135,6 +33171,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -33147,7 +33193,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeEmail": {
@@ -33185,6 +33233,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33203,6 +33261,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33241,6 +33301,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -33267,6 +33337,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"elements",
"format"
]
@@ -33306,6 +33378,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33324,6 +33406,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33362,6 +33446,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33380,6 +33474,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33418,6 +33514,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -33436,6 +33542,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33474,6 +33582,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -33511,6 +33629,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -33559,6 +33679,16 @@
},
"x-example": [],
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Index creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Index update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -33566,7 +33696,9 @@
"type",
"status",
"error",
"attributes"
"attributes",
"$createdAt",
"$updatedAt"
]
},
"document": {
+136 -4
View File
@@ -23678,6 +23678,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -23697,6 +23707,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"size"
]
},
@@ -23735,6 +23747,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -23762,7 +23784,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeFloat": {
@@ -23800,6 +23824,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -23827,7 +23861,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeBoolean": {
@@ -23865,6 +23901,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -23877,7 +23923,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeEmail": {
@@ -23915,6 +23963,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -23933,6 +23991,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -23971,6 +24031,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -23997,6 +24067,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"elements",
"format"
]
@@ -24036,6 +24108,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24054,6 +24136,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24092,6 +24176,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24110,6 +24204,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24148,6 +24244,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -24166,6 +24272,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24204,6 +24312,16 @@
"x-example": false,
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -24241,6 +24359,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -24289,6 +24409,16 @@
},
"x-example": [],
"nullable": true
},
"$createdAt": {
"type": "string",
"description": "Index creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Index update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -24296,7 +24426,9 @@
"type",
"status",
"error",
"attributes"
"attributes",
"$createdAt",
"$updatedAt"
]
},
"document": {
+136 -4
View File
@@ -33458,6 +33458,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -33477,6 +33487,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"size"
]
},
@@ -33515,6 +33527,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -33542,7 +33564,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeFloat": {
@@ -33580,6 +33604,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -33607,7 +33641,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeBoolean": {
@@ -33645,6 +33681,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -33657,7 +33703,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeEmail": {
@@ -33695,6 +33743,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33713,6 +33771,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33751,6 +33811,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -33777,6 +33847,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"elements",
"format"
]
@@ -33816,6 +33888,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33834,6 +33916,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33872,6 +33956,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33890,6 +33984,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33928,6 +34024,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -33946,6 +34052,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33984,6 +34092,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -34021,6 +34139,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -34069,6 +34189,16 @@
},
"x-example": [],
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Index creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Index update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -34076,7 +34206,9 @@
"type",
"status",
"error",
"attributes"
"attributes",
"$createdAt",
"$updatedAt"
]
},
"document": {
+136 -4
View File
@@ -24167,6 +24167,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -24186,6 +24196,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"size"
]
},
@@ -24224,6 +24236,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -24251,7 +24273,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeFloat": {
@@ -24289,6 +24313,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -24316,7 +24350,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeBoolean": {
@@ -24354,6 +24390,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -24366,7 +24412,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeEmail": {
@@ -24404,6 +24452,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24422,6 +24480,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24460,6 +24520,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -24486,6 +24556,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"elements",
"format"
]
@@ -24525,6 +24597,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24543,6 +24625,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24581,6 +24665,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24599,6 +24693,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24637,6 +24733,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -24655,6 +24761,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24693,6 +24801,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -24730,6 +24848,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -24778,6 +24898,16 @@
},
"x-example": [],
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Index creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Index update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -24785,7 +24915,9 @@
"type",
"status",
"error",
"attributes"
"attributes",
"$createdAt",
"$updatedAt"
]
},
"document": {
+136 -4
View File
@@ -33458,6 +33458,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -33477,6 +33487,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"size"
]
},
@@ -33515,6 +33527,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -33542,7 +33564,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeFloat": {
@@ -33580,6 +33604,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -33607,7 +33641,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeBoolean": {
@@ -33645,6 +33681,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -33657,7 +33703,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeEmail": {
@@ -33695,6 +33743,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33713,6 +33771,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33751,6 +33811,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -33777,6 +33847,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"elements",
"format"
]
@@ -33816,6 +33888,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33834,6 +33916,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33872,6 +33956,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -33890,6 +33984,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33928,6 +34024,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -33946,6 +34052,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -33984,6 +34092,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -34021,6 +34139,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -34069,6 +34189,16 @@
},
"x-example": [],
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Index creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Index update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -34076,7 +34206,9 @@
"type",
"status",
"error",
"attributes"
"attributes",
"$createdAt",
"$updatedAt"
]
},
"document": {
+136 -4
View File
@@ -24167,6 +24167,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"size": {
"type": "integer",
"description": "Attribute size.",
@@ -24186,6 +24196,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"size"
]
},
@@ -24224,6 +24236,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "integer",
"description": "Minimum value to enforce for new documents.",
@@ -24251,7 +24273,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeFloat": {
@@ -24289,6 +24313,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"min": {
"type": "number",
"description": "Minimum value to enforce for new documents.",
@@ -24316,7 +24350,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeBoolean": {
@@ -24354,6 +24390,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"default": {
"type": "boolean",
"description": "Default value for attribute when not provided. Cannot be set when attribute is required.",
@@ -24366,7 +24412,9 @@
"type",
"status",
"error",
"required"
"required",
"$createdAt",
"$updatedAt"
]
},
"attributeEmail": {
@@ -24404,6 +24452,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24422,6 +24480,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24460,6 +24520,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"elements": {
"type": "array",
"description": "Array of elements in enumerated type.",
@@ -24486,6 +24556,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"elements",
"format"
]
@@ -24525,6 +24597,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24543,6 +24625,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24581,6 +24665,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "String format.",
@@ -24599,6 +24693,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24637,6 +24733,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"format": {
"type": "string",
"description": "ISO 8601 format.",
@@ -24655,6 +24761,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"format"
]
},
@@ -24693,6 +24801,16 @@
"x-example": false,
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Attribute creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Attribute update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"relatedCollection": {
"type": "string",
"description": "The ID of the related collection.",
@@ -24730,6 +24848,8 @@
"status",
"error",
"required",
"$createdAt",
"$updatedAt",
"relatedCollection",
"relationType",
"twoWay",
@@ -24778,6 +24898,16 @@
},
"x-example": [],
"x-nullable": true
},
"$createdAt": {
"type": "string",
"description": "Index creation date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
},
"$updatedAt": {
"type": "string",
"description": "Index update date in ISO 8601 format.",
"x-example": "2020-10-15T06:38:00.000+00:00"
}
},
"required": [
@@ -24785,7 +24915,9 @@
"type",
"status",
"error",
"attributes"
"attributes",
"$createdAt",
"$updatedAt"
]
},
"document": {
+1 -1
View File
@@ -2509,7 +2509,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
'required' => true,
'array' => false,
'default' => null,
'size' => 36
'size' => Database::LENGTH_KEY
];
$oldAttributes[] = [
+5
View File
@@ -60,6 +60,7 @@ App::post('/v1/migrations/appwrite')
'status' => 'pending',
'stage' => 'init',
'source' => Appwrite::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'endpoint' => $endpoint,
'projectId' => $projectId,
@@ -164,6 +165,7 @@ App::post('/v1/migrations/firebase/oauth')
'status' => 'pending',
'stage' => 'init',
'source' => Firebase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'serviceAccount' => json_encode($serviceAccount),
],
@@ -224,6 +226,7 @@ App::post('/v1/migrations/firebase')
'status' => 'pending',
'stage' => 'init',
'source' => Firebase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'serviceAccount' => $serviceAccount,
],
@@ -279,6 +282,7 @@ App::post('/v1/migrations/supabase')
'status' => 'pending',
'stage' => 'init',
'source' => Supabase::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'endpoint' => $endpoint,
'apiKey' => $apiKey,
@@ -340,6 +344,7 @@ App::post('/v1/migrations/nhost')
'status' => 'pending',
'stage' => 'init',
'source' => NHost::getName(),
'destination' => Appwrite::getName(),
'credentials' => [
'subdomain' => $subdomain,
'region' => $region,
+1
View File
@@ -923,6 +923,7 @@ App::delete('/v1/projects/:projectId')
}
$queueForDeletes
->setProject($project)
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($project);
+5 -2
View File
@@ -152,6 +152,9 @@ const APP_HOSTNAME_INTERNAL = 'appwrite';
const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_05VCPU_512MB;
const APP_FUNCTION_CPUS_DEFAULT = 0.5;
const APP_FUNCTION_MEMORY_DEFAULT = 512;
const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
@@ -1525,9 +1528,9 @@ App::setResource('deviceForBuilds', function ($project) {
return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId());
}, ['project']);
function getDevice($root): Device
function getDevice(string $root, string $connection = ''): Device
{
$connection = System::getEnv('_APP_CONNECTIONS_STORAGE', '');
$connection = !empty($connection) ? $connection : System::getEnv('_APP_CONNECTIONS_STORAGE', '');
if (!empty($connection)) {
$acl = 'private';
+11 -4
View File
@@ -40,7 +40,7 @@ require_once __DIR__ . '/init.php';
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
// Allows overriding
if (!function_exists("getConsoleDB")) {
if (!function_exists('getConsoleDB')) {
function getConsoleDB(): Database
{
global $register;
@@ -66,7 +66,7 @@ if (!function_exists("getConsoleDB")) {
}
// Allows overriding
if (!function_exists("getProjectDB")) {
if (!function_exists('getProjectDB')) {
function getProjectDB(Document $project): Database
{
global $register;
@@ -113,7 +113,7 @@ if (!function_exists("getProjectDB")) {
}
// Allows overriding
if (!function_exists("getCache")) {
if (!function_exists('getCache')) {
function getCache(): Cache
{
global $register;
@@ -135,7 +135,14 @@ if (!function_exists("getCache")) {
}
}
$realtime = new Realtime();
if (!function_exists('getRealtime')) {
function getRealtime(): Realtime
{
return new Realtime();
}
}
$realtime = getRealtime();
/**
* Table for statistics across all workers.
+1 -1
View File
@@ -58,7 +58,7 @@ Server::setResource('project', function (Message $message, Database $dbForConsol
$payload = $message->getPayload() ?? [];
$project = new Document($payload['project'] ?? []);
if ($project->getId() === 'console') {
if ($project->getId() === 'console' || $project->isEmpty() || ! empty($project->getInternalId())) {
return $project;
}
+1 -1
View File
@@ -60,7 +60,7 @@
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.12.*",
"utopia-php/migration": "0.5.*",
"utopia-php/migration": "0.6.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.5.*",
Generated
+29 -20
View File
@@ -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": "66e31af1f7d0d1617694a1c5a975f887",
"content-hash": "1884e3a2966762c4a955842426b64f6c",
"packages": [
{
"name": "adhocore/jwt",
@@ -65,16 +65,16 @@
},
{
"name": "appwrite/appwrite",
"version": "10.1.0",
"version": "11.1.0",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-for-php.git",
"reference": "da579af70723cfc117b5af84375bdef117e27312"
"reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/da579af70723cfc117b5af84375bdef117e27312",
"reference": "da579af70723cfc117b5af84375bdef117e27312",
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/1d043f543acdb17b9fdb440b1b2dd208e400bad3",
"reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3",
"shasum": ""
},
"require": {
@@ -83,7 +83,8 @@
"php": ">=7.1.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.35"
"mockery/mockery": "^1.6.6",
"phpunit/phpunit": "^10"
},
"type": "library",
"autoload": {
@@ -99,10 +100,10 @@
"support": {
"email": "team@appwrite.io",
"issues": "https://github.com/appwrite/sdk-for-php/issues",
"source": "https://github.com/appwrite/sdk-for-php/tree/10.1.0",
"source": "https://github.com/appwrite/sdk-for-php/tree/11.1.0",
"url": "https://appwrite.io/support"
},
"time": "2023-11-20T09:56:12+00:00"
"time": "2024-06-26T07:03:23+00:00"
},
{
"name": "appwrite/php-clamav",
@@ -2174,27 +2175,35 @@
},
{
"name": "utopia-php/migration",
"version": "0.5.3",
"version": "0.6.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "b30e7834da69e25084b0c8e9ba29e4a7b54c6eb6"
"reference": "e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/b30e7834da69e25084b0c8e9ba29e4a7b54c6eb6",
"reference": "b30e7834da69e25084b0c8e9ba29e4a7b54c6eb6",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c",
"reference": "e43ef283f1386084e11d1ffe093fb6c6d7a6ce6c",
"shasum": ""
},
"require": {
"appwrite/appwrite": "10.1.0",
"php": "8.*"
"appwrite/appwrite": "11.1.*",
"ext-curl": "*",
"ext-openssl": "*",
"php": "8.3.*",
"utopia-php/database": "0.53.*",
"utopia-php/dsn": "0.2.*",
"utopia-php/framework": "0.33.*",
"utopia-php/storage": "0.18.*"
},
"require-dev": {
"laravel/pint": "1.*",
"phpunit/phpunit": "9.*",
"utopia-php/cli": "^0.18.0",
"vlucas/phpdotenv": "5.*"
"ext-pdo": "*",
"laravel/pint": "1.17.*",
"phpstan/phpstan": "1.11.*",
"phpunit/phpunit": "11.2.*",
"utopia-php/cli": "0.16.*",
"vlucas/phpdotenv": "5.6.*"
},
"type": "library",
"autoload": {
@@ -2216,9 +2225,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.5.3"
"source": "https://github.com/utopia-php/migration/tree/0.6.4"
},
"time": "2024-09-10T10:45:18+00:00"
"time": "2024-10-02T15:16:36+00:00"
},
{
"name": "utopia-php/mongo",
+1 -1
View File
@@ -81,7 +81,7 @@ class Migration extends Event
return $client->enqueue([
'project' => $this->project,
'user' => $this->user,
'migration' => $this->migration
'migration' => $this->migration,
]);
}
}
@@ -243,7 +243,11 @@ class Realtime extends Adapter
* @param string $event
* @param Document $payload
* @param Document|null $project
* @param Document|null $database
* @param Document|null $collection
* @param Document|null $bucket
* @return array
* @throws \Exception
*/
public static function fromPayload(string $event, Document $payload, Document $project = null, Document $database = null, Document $collection = null, Document $bucket = null): array
{
@@ -262,6 +266,7 @@ class Realtime extends Adapter
break;
case 'rules':
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
break;
@@ -280,6 +285,7 @@ class Realtime extends Adapter
case 'databases':
if (in_array($parts[4] ?? [], ['attributes', 'indexes'])) {
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
} elseif (($parts[4] ?? '') === 'documents') {
@@ -319,6 +325,7 @@ class Realtime extends Adapter
if ($parts[2] === 'executions') {
if (!empty($payload->getRead())) {
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$channels[] = 'executions';
$channels[] = 'executions.' . $payload->getId();
$channels[] = 'functions.' . $payload->getAttribute('functionId');
@@ -326,6 +333,7 @@ class Realtime extends Adapter
}
} elseif ($parts[2] === 'deployments') {
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
}
@@ -333,6 +341,7 @@ class Realtime extends Adapter
break;
case 'migrations':
$channels[] = 'console';
$channels[] = 'projects.' . $project->getId();
$projectId = 'console';
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
break;
+14 -6
View File
@@ -49,12 +49,7 @@ class Maintenance extends Action
$this->foreachProject($dbForConsole, function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
$queueForDeletes->setProject($project);
$this->notifyDeleteTargets($queueForDeletes);
$this->notifyDeleteExecutionLogs($queueForDeletes);
$this->notifyDeleteAbuseLogs($queueForDeletes);
$this->notifyDeleteAuditLogs($queueForDeletes);
$this->notifyDeleteUsageStats($usageStatsRetentionHourly, $queueForDeletes);
$this->notifyDeleteExpiredSessions($queueForDeletes);
$this->notifyProjects($queueForDeletes, $usageStatsRetentionHourly);
});
$this->notifyDeleteConnections($queueForDeletes);
@@ -64,6 +59,19 @@ class Maintenance extends Action
}, $interval, $delay);
}
/**
* Hook to allow sub-classes to extend project-level maintenance functionality.
*/
protected function notifyProjects(Delete $queueForDeletes, int $usageStatsRetentionHourly): void
{
$this->notifyDeleteTargets($queueForDeletes);
$this->notifyDeleteExecutionLogs($queueForDeletes);
$this->notifyDeleteAbuseLogs($queueForDeletes);
$this->notifyDeleteAuditLogs($queueForDeletes);
$this->notifyDeleteUsageStats($usageStatsRetentionHourly, $queueForDeletes);
$this->notifyDeleteExpiredSessions($queueForDeletes);
}
protected function foreachProject(Database $dbForConsole, callable $callback): void
{
// TODO: @Meldiron name of this method no longer matches. It does not delete, and it gives whole document
+9 -23
View File
@@ -24,11 +24,8 @@ abstract class ScheduleBase extends Action
abstract public static function getName(): string;
abstract public static function getSupportedResource(): string;
abstract protected function enqueueResources(
Group $pools,
Database $dbForConsole
);
abstract public static function getCollectionId(): string;
abstract protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void;
public function __construct()
{
@@ -62,14 +59,8 @@ abstract class ScheduleBase extends Action
$getSchedule = function (Document $schedule) use ($dbForConsole, $getProjectDB): array {
$project = $dbForConsole->getDocument('projects', $schedule->getAttribute('projectId'));
$collectionId = match ($schedule->getAttribute('resourceType')) {
'function' => 'functions',
'message' => 'messages',
'execution' => 'executions'
};
$resource = $getProjectDB($project)->getDocument(
$collectionId,
static::getCollectionId(),
$schedule->getAttribute('resourceId')
);
@@ -113,12 +104,7 @@ abstract class ScheduleBase extends Action
try {
$this->schedules[$document->getInternalId()] = $getSchedule($document);
} catch (\Throwable $th) {
$collectionId = match ($document->getAttribute('resourceType')) {
'function' => 'functions',
'message' => 'messages',
'execution' => 'executions'
};
$collectionId = static::getCollectionId();
Console::error("Failed to load schedule for project {$document['projectId']} {$collectionId} {$document['resourceId']}");
Console::error($th->getMessage());
}
@@ -133,7 +119,7 @@ abstract class ScheduleBase extends Action
Console::success("Starting timers at " . DateTime::now());
run(function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools) {
run(function () use ($dbForConsole, &$lastSyncUpdate, $getSchedule, $pools, $getProjectDB) {
/**
* The timer synchronize $schedules copy with database collection.
*/
@@ -172,10 +158,10 @@ abstract class ScheduleBase extends Action
$new = \strtotime($document['resourceUpdatedAt']);
if (!$document['active']) {
Console::info("Removing: {$document['resourceId']}");
Console::info("Removing: {$document['resourceType']}::{$document['resourceId']}");
unset($this->schedules[$document->getInternalId()]);
} elseif ($new !== $org) {
Console::info("Updating: {$document['resourceId']}");
Console::info("Updating: {$document['resourceType']}::{$document['resourceId']}");
$this->schedules[$document->getInternalId()] = $getSchedule($document);
}
}
@@ -193,10 +179,10 @@ abstract class ScheduleBase extends Action
Timer::tick(
static::ENQUEUE_TIMER * 1000,
fn () => $this->enqueueResources($pools, $dbForConsole)
fn () => $this->enqueueResources($pools, $dbForConsole, $getProjectDB)
);
$this->enqueueResources($pools, $dbForConsole);
$this->enqueueResources($pools, $dbForConsole, $getProjectDB);
});
}
}
@@ -22,7 +22,12 @@ class ScheduleExecutions extends ScheduleBase
return 'execution';
}
protected function enqueueResources(Group $pools, Database $dbForConsole): void
public static function getCollectionId(): string
{
return 'executions';
}
protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
{
$queue = $pools->get('queue')->pop();
$connection = $queue->getResource();
@@ -26,7 +26,12 @@ class ScheduleFunctions extends ScheduleBase
return 'function';
}
protected function enqueueResources(Group $pools, Database $dbForConsole): void
public static function getCollectionId(): string
{
return 'functions';
}
protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
{
$timerStart = \microtime(true);
$time = DateTime::now();
@@ -21,7 +21,12 @@ class ScheduleMessages extends ScheduleBase
return 'message';
}
protected function enqueueResources(Group $pools, Database $dbForConsole): void
public static function getCollectionId(): string
{
return 'messages';
}
protected function enqueueResources(Group $pools, Database $dbForConsole, callable $getProjectDB): void
{
foreach ($this->schedules as $schedule) {
if (!$schedule['active']) {
+19 -8
View File
@@ -5,9 +5,11 @@ namespace Appwrite\Platform\Tasks;
use Appwrite\Specification\Format\OpenAPI3;
use Appwrite\Specification\Format\Swagger2;
use Appwrite\Specification\Specification;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Request as AppwriteRequest;
use Appwrite\Utopia\Response as AppwriteResponse;
use Exception;
use Swoole\Http\Response as HttpResponse;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
use Utopia\App;
use Utopia\Cache\Adapter\None;
use Utopia\Cache\Cache;
@@ -17,7 +19,8 @@ use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Database;
use Utopia\Platform\Action;
use Utopia\Registry\Registry;
use Utopia\Request;
use Utopia\Request as UtopiaRequest;
use Utopia\Response as UtopiaResponse;
use Utopia\System\System;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -29,6 +32,16 @@ class Specs extends Action
return 'specs';
}
public function getRequest(): UtopiaRequest
{
return new AppwriteRequest(new SwooleRequest());
}
public function getResponse(): UtopiaResponse
{
return new AppwriteResponse(new SwooleResponse());
}
public function __construct()
{
$this
@@ -42,11 +55,11 @@ class Specs extends Action
public function action(string $version, string $mode, Registry $register): void
{
$appRoutes = App::getRoutes();
$response = new Response(new HttpResponse());
$response = $this->getResponse();
$mocks = ($mode === 'mocks');
// Mock dependencies
App::setResource('request', fn () => new Request());
App::setResource('request', fn () => $this->getRequest());
App::setResource('response', fn () => $response);
App::setResource('dbForConsole', fn () => new Database(new MySQL(''), new Cache(new None())));
App::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
@@ -183,10 +196,8 @@ class Specs extends Action
case APP_AUTH_TYPE_SESSION:
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case APP_AUTH_TYPE_KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_JWT:
case APP_AUTH_TYPE_KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case APP_AUTH_TYPE_ADMIN:
+8 -2
View File
@@ -480,6 +480,7 @@ class Deletes extends Action
private function deleteProject(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void
{
$projectInternalId = $document->getInternalId();
$projectId = $document->getId();
try {
$dsn = new DSN($document->getAttribute('database', 'console'));
@@ -568,6 +569,11 @@ class Deletes extends Action
Query::equal('projectInternalId', [$projectInternalId]),
], $dbForConsole);
// Delete Schedules (No projectInternalId in this collection)
$this->deleteByGroup('schedules', [
Query::equal('projectId', [$projectId]),
], $dbForConsole);
// Delete metadata table
if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
$dbForProject->deleteCollection('_metadata');
@@ -966,7 +972,7 @@ class Deletes extends Action
* @return void
* @throws Exception
*/
private function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
protected function deleteByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
{
$count = 0;
$chunk = 0;
@@ -1008,7 +1014,7 @@ class Deletes extends Action
* @return void
* @throws Exception
*/
private function listByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
protected function listByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
{
$count = 0;
$chunk = 0;
+2 -1
View File
@@ -587,7 +587,8 @@ class Functions extends Action
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $execution
payload: $execution,
project: $project
);
Realtime::send(
projectId: 'console',
+120 -79
View File
@@ -8,6 +8,7 @@ use Appwrite\Permission;
use Appwrite\Role;
use Exception;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization;
@@ -16,11 +17,11 @@ use Utopia\Database\Exception\Restricted;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
use Utopia\Logger\Log;
use Utopia\Logger\Log\Breadcrumb;
use Utopia\Migration\Destinations\Appwrite as DestinationsAppwrite;
use Utopia\Migration\Destination;
use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
use Utopia\Migration\Exception as MigrationException;
use Utopia\Migration\Source;
use Utopia\Migration\Sources\Appwrite;
use Utopia\Migration\Sources\Appwrite as SourceAppwrite;
use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
@@ -30,8 +31,11 @@ use Utopia\Queue\Message;
class Migrations extends Action
{
private ?Database $dbForProject = null;
private ?Database $dbForConsole = null;
protected Database $dbForProject;
protected Database $dbForConsole;
protected Document $project;
public static function getName(): string
{
@@ -53,11 +57,6 @@ class Migrations extends Action
}
/**
* @param Message $message
* @param Database $dbForProject
* @param Database $dbForConsole
* @param Log $log
* @return void
* @throws Exception
*/
public function action(Message $message, Database $dbForProject, Database $dbForConsole, Log $log): void
@@ -78,6 +77,7 @@ class Migrations extends Action
$this->dbForProject = $dbForProject;
$this->dbForConsole = $dbForConsole;
$this->project = $project;
/**
* Handle Event execution.
@@ -89,17 +89,17 @@ class Migrations extends Action
$log->addTag('migrationId', $migration->getId());
$log->addTag('projectId', $project->getId());
$this->processMigration($project, $migration, $log);
$this->processMigration($migration, $log);
}
/**
* @param string $source
* @param array $credentials
* @return Source
* @throws Exception
*/
protected function processSource(string $source, array $credentials): Source
protected function processSource(Document $migration): Source
{
$source = $migration->getAttribute('source');
$credentials = $migration->getAttribute('credentials');
return match ($source) {
Firebase::getName() => new Firebase(
json_decode($credentials['serviceAccount'], true),
@@ -122,11 +122,35 @@ class Migrations extends Action
$credentials['password'],
$credentials['port'],
),
Appwrite::getName() => new Appwrite($credentials['projectId'], str_starts_with($credentials['endpoint'], 'http://localhost/v1') ? 'http://appwrite/v1' : $credentials['endpoint'], $credentials['apiKey']),
SourceAppwrite::getName() => new SourceAppwrite(
$credentials['projectId'],
$credentials['endpoint'],
$credentials['apiKey'],
),
default => throw new \Exception('Invalid source type'),
};
}
/**
* @throws Exception
*/
protected function processDestination(Document $migration): Destination
{
$destination = $migration->getAttribute('destination');
$credentials = $migration->getAttribute('credentials');
return match ($destination) {
DestinationAppwrite::getName() => new DestinationAppwrite(
$credentials['projectId'],
$credentials['endpoint'],
$credentials['apiKey'],
$this->dbForProject,
Config::getParam('collections', [])['databases']['collections'],
),
default => throw new \Exception('Invalid destination type'),
};
}
/**
* @throws Authorization
* @throws Structure
@@ -167,8 +191,6 @@ class Migrations extends Action
}
/**
* @param Document $apiKey
* @return void
* @throws \Utopia\Database\Exception
* @throws Authorization
* @throws Conflict
@@ -181,8 +203,6 @@ class Migrations extends Action
}
/**
* @param Document $project
* @return Document
* @throws Authorization
* @throws Structure
* @throws \Utopia\Database\Exception
@@ -233,99 +253,116 @@ class Migrations extends Action
}
/**
* @param Document $project
* @param Document $migration
* @param Log $log
* @return void
* @throws Authorization
* @throws Conflict
* @throws Restricted
* @throws Structure
* @throws \Utopia\Database\Exception
* @throws Exception
*/
protected function processMigration(Document $project, Document $migration, Log $log): void
protected function processMigration(Document $migration, Log $log): void
{
/**
* @var Document $migrationDocument
* @var Transfer $transfer
*/
$migrationDocument = null;
$transfer = null;
$project = $this->project;
$projectDocument = $this->dbForConsole->getDocument('projects', $project->getId());
$tempAPIKey = $this->generateAPIKey($projectDocument);
$transfer = $source = $destination = null;
try {
$migrationDocument = $this->dbForProject->getDocument('migrations', $migration->getId());
$migrationDocument->setAttribute('stage', 'processing');
$migrationDocument->setAttribute('status', 'processing');
$log->addBreadcrumb(new Breadcrumb("debug", "migration", "Migration hit stage 'processing'", \microtime(true)));
$this->updateMigrationDocument($migrationDocument, $projectDocument);
$migration = $this->dbForProject->getDocument('migrations', $migration->getId());
$log->addTag('type', $migrationDocument->getAttribute('source'));
if (
$migration->getAttribute('source') === SourceAppwrite::getName() ||
$migration->getAttribute('destination') === DestinationAppwrite::getName()
) {
$credentials = $migration->getAttribute('credentials', []);
$source = $this->processSource($migrationDocument->getAttribute('source'), $migrationDocument->getAttribute('credentials'));
$credentials['projectId'] = $credentials['projectId'] ?? $projectDocument->getId();
$credentials['endpoint'] = $credentials['endpoint'] ?? 'http://appwrite/v1';
$credentials['apiKey'] = $credentials['apiKey'] ?? $tempAPIKey['secret'];
$migration->setAttribute('credentials', $credentials);
}
$migration->setAttribute('stage', 'processing');
$migration->setAttribute('status', 'processing');
$this->updateMigrationDocument($migration, $projectDocument);
$log->addTag('type', $migration->getAttribute('source'));
$source = $this->processSource($migration);
$destination = $this->processDestination($migration);
$source->report();
$destination = new DestinationsAppwrite(
$projectDocument->getId(),
'http://appwrite/v1',
$tempAPIKey['secret'],
);
$transfer = new Transfer(
$source,
$destination
);
/** Start Transfer */
$migrationDocument->setAttribute('stage', 'migrating');
$log->addBreadcrumb(new Breadcrumb("debug", "migration", "Migration hit stage 'migrating'", \microtime(true)));
$this->updateMigrationDocument($migrationDocument, $projectDocument);
$transfer->run($migrationDocument->getAttribute('resources'), function () use ($migrationDocument, $transfer, $projectDocument) {
$migrationDocument->setAttribute('resourceData', json_encode($transfer->getCache()));
$migrationDocument->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
$migration->setAttribute('stage', 'migrating');
$this->updateMigrationDocument($migration, $projectDocument);
$this->updateMigrationDocument($migrationDocument, $projectDocument);
});
$transfer->run(
$migration->getAttribute('resources'),
function () use ($migration, $transfer, $projectDocument) {
$migration->setAttribute('resourceData', json_encode($transfer->getCache()));
$migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
$this->updateMigrationDocument($migration, $projectDocument);
},
$migration->getAttribute('resourceId'),
$migration->getAttribute('resourceType')
);
$destination->shutDown();
$source->shutDown();
$sourceErrors = $source->getErrors();
$destinationErrors = $destination->getErrors();
if (!empty($sourceErrors) || !empty($destinationErrors)) {
$migrationDocument->setAttribute('status', 'failed');
$migrationDocument->setAttribute('stage', 'finished');
$log->addBreadcrumb(new Breadcrumb("debug", "migration", "Migration hit stage 'finished' and failed", \microtime(true)));
if (! empty($sourceErrors) || ! empty($destinationErrors)) {
$migration->setAttribute('status', 'failed');
$migration->setAttribute('stage', 'finished');
$errorMessages = [];
foreach ($sourceErrors as $error) {
/** @var MigrationException $error */
$errorMessages[] = "Error occurred while fetching '{$error->getResourceGroup()}:{$error->getResourceId()}' from source with message: '{$error->getMessage()}'";
/** @var $sourceErrors $error */
$message = "Error occurred while fetching '{$error->getResourceName()}:{$error->getResourceId()}' from source with message: '{$error->getMessage()}'";
if ($error->getPrevious()) {
$message .= " Message: ".$error->getPrevious()->getMessage() . " File: ".$error->getPrevious()->getFile() . " Line: ".$error->getPrevious()->getLine();
}
$errorMessages[] = $message;
}
foreach ($destinationErrors as $error) {
$message = "Error occurred while pushing '{$error->getResourceName()}:{$error->getResourceId()}' to destination with message: '{$error->getMessage()}'";
if ($error->getPrevious()) {
$message .= " Message: ".$error->getPrevious()->getMessage() . " File: ".$error->getPrevious()->getFile() . " Line: ".$error->getPrevious()->getLine();
}
/** @var MigrationException $error */
$errorMessages[] = "Error occurred while pushing '{$error->getResourceGroup()}:{$error->getResourceId()}' to destination with message: '{$error->getMessage()}'";
$errorMessages[] = $message;
}
$migrationDocument->setAttribute('errors', $errorMessages);
$migration->setAttribute('errors', $errorMessages);
$log->addExtra('migrationErrors', json_encode($errorMessages));
$this->updateMigrationDocument($migrationDocument, $projectDocument);
$this->updateMigrationDocument($migration, $projectDocument);
return;
}
$migrationDocument->setAttribute('status', 'completed');
$migrationDocument->setAttribute('stage', 'finished');
$log->addBreadcrumb(new Breadcrumb("debug", "migration", "Migration hit stage 'finished' and succeeded", \microtime(true)));
$migration->setAttribute('status', 'completed');
$migration->setAttribute('stage', 'finished');
} catch (\Throwable $th) {
Console::error($th->getMessage());
Console::error($th->getTraceAsString());
if ($migrationDocument) {
Console::error($th->getMessage());
Console::error($th->getTraceAsString());
$migrationDocument->setAttribute('status', 'failed');
$migrationDocument->setAttribute('stage', 'finished');
$migrationDocument->setAttribute('errors', [$th->getMessage()]);
if (! $migration->isEmpty()) {
$migration->setAttribute('status', 'failed');
$migration->setAttribute('stage', 'finished');
$migration->setAttribute('errors', [$th->getMessage()]);
return;
}
@@ -337,26 +374,30 @@ class Migrations extends Action
$errorMessages = [];
foreach ($sourceErrors as $error) {
/** @var MigrationException $error */
$errorMessages[] = "Error occurred while fetching '{$error->getResourceGroup()}:{$error->getResourceId()}' from source with message '{$error->getMessage()}'";
$errorMessages[] = "Error occurred while fetching '{$error->getResourceName()}:{$error->getResourceId()}' from source with message '{$error->getMessage()}'";
}
foreach ($destinationErrors as $error) {
/** @var MigrationException $error */
$errorMessages[] = "Error occurred while pushing '{$error->getResourceGroup()}:{$error->getResourceId()}' to destination with message '{$error->getMessage()}'";
$errorMessages[] = "Error occurred while pushing '{$error->getResourceName()}:{$error->getResourceId()}' to destination with message '{$error->getMessage()}'";
}
$migrationDocument->setAttribute('errors', $errorMessages);
$migration->setAttribute('errors', $errorMessages);
$log->addTag('migrationErrors', json_encode($errorMessages));
}
} finally {
if ($tempAPIKey) {
if (! $tempAPIKey->isEmpty()) {
$this->removeAPIKey($tempAPIKey);
}
if ($migrationDocument) {
$this->updateMigrationDocument($migrationDocument, $projectDocument);
if ($migrationDocument->getAttribute('status', '') == 'failed') {
throw new Exception("Migration failed");
}
$this->updateMigrationDocument($migration, $projectDocument);
if ($migration->getAttribute('status', '') === 'failed') {
Console::error('Migration('.$migration->getInternalId().':'.$migration->getId().') failed, Project('.$this->project->getInternalId().':'.$this->project->getId().')');
$destination->error();
$source->error();
throw new Exception('Migration failed');
}
}
}
+19 -28
View File
@@ -286,13 +286,26 @@ class Swagger2 extends Format
$validator = $validator->getValidator();
}
$validatorClass = (!empty($validator)) ? \get_class($validator) : '';
if ($validatorClass === 'Utopia\Validator\AnyOf') {
$validator = $param['validator']->getValidators()[0];
$validatorClass = \get_class($validator);
$class = !empty($validator)
? \get_class($validator)
: '';
$base = !empty($class)
? \get_parent_class($class)
: '';
switch ($base) {
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
$class = $base;
break;
}
switch ($validatorClass) {
if ($class === 'Utopia\Validator\AnyOf') {
$validator = $param['validator']->getValidators()[0];
$class = \get_class($validator);
}
switch ($class) {
case 'Utopia\Validator\Text':
case 'Utopia\Database\Validator\UID':
$node['type'] = $validator->getType();
@@ -348,29 +361,7 @@ class Swagger2 extends Format
$consumes = ['multipart/form-data'];
$node['type'] = 'payload';
break;
case 'Appwrite\Utopia\Database\Validator\Queries\Attributes':
case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
case 'Appwrite\Utopia\Database\Validator\Queries\Functions':
case 'Appwrite\Utopia\Database\Validator\Queries\Identities':
case 'Appwrite\Utopia\Database\Validator\Queries\Indexes':
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
case 'Appwrite\Utopia\Database\Validator\Queries\Messages':
case 'Appwrite\Utopia\Database\Validator\Queries\Migrations':
case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
case 'Appwrite\Utopia\Database\Validator\Queries\Providers':
case 'Appwrite\Utopia\Database\Validator\Queries\Rules':
case 'Appwrite\Utopia\Database\Validator\Queries\Subscribers':
case 'Appwrite\Utopia\Database\Validator\Queries\Targets':
case 'Appwrite\Utopia\Database\Validator\Queries\Teams':
case 'Appwrite\Utopia\Database\Validator\Queries\Topics':
case 'Appwrite\Utopia\Database\Validator\Queries\Users':
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
case 'Utopia\Database\Validator\Queries':
case 'Utopia\Database\Validator\Queries\Document':
case 'Utopia\Database\Validator\Queries\Documents':
+5 -1
View File
@@ -122,7 +122,11 @@ class Request extends UtopiaRequest
*/
public function getHeaders(): array
{
$headers = $this->generateHeaders();
try {
$headers = $this->generateHeaders();
} catch (\Throwable) {
$headers = [];
}
if (empty($this->swoole->cookie)) {
return $headers;
@@ -47,7 +47,18 @@ class Attribute extends Model
'required' => false,
'example' => false,
])
;
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Attribute creation date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Attribute update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
]);
}
public array $conditions = [];
+12 -5
View File
@@ -49,13 +49,22 @@ class Index extends Model
'array' => true,
'required' => false,
])
;
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Index creation date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Index update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
@@ -64,8 +73,6 @@ class Index extends Model
/**
* Get Collection
*
* @return string
*/
public function getType(): string
{
+4 -7
View File
@@ -883,9 +883,6 @@ class UsageTest extends Scope
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$code = realpath(__DIR__ . '/../../resources/functions') . "/php/code.tar.gz";
$this->packageCode('php');
$response = $this->client->call(
Client::METHOD_POST,
'/functions/' . $functionId . '/deployments',
@@ -895,8 +892,8 @@ class UsageTest extends Scope
], $this->getHeaders()),
[
'entrypoint' => 'index.php',
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
'activate' => true
'code' => $this->packageFunction('php'),
'activate' => true,
]
);
@@ -934,7 +931,7 @@ class UsageTest extends Scope
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()),
[
'async' => false,
'async' => 'false',
]
);
@@ -958,7 +955,7 @@ class UsageTest extends Scope
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()),
[
'async' => false,
'async' => 'false',
]
);
+154 -181
View File
@@ -2,234 +2,207 @@
namespace Tests\E2E\Services\Functions;
use Appwrite\Tests\Async;
use CURLFile;
use Tests\E2E\Client;
use Utopia\CLI\Console;
trait FunctionsBase
{
use Async;
protected string $stdout = '';
protected string $stderr = '';
protected function packageCode($folder)
protected function setupFunction(mixed $params): string
{
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), $params);
$this->assertEquals($function['headers']['status-code'], 201, 'Setup function failed with status code: ' . $function['headers']['status-code'] . ' and response: ' . json_encode($function['body'], JSON_PRETTY_PRINT));
$functionId = $function['body']['$id'];
return $functionId;
}
protected function awaitDeploymentIsBuilt($functionId, $deploymentId, $checkForSuccess = true): void
protected function setupDeployment(string $functionId, mixed $params): string
{
while (true) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]), $params);
$this->assertEquals($deployment['headers']['status-code'], 202, 'Setup deployment failed with status code: ' . $deployment['headers']['status-code'] . ' and response: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEventually(function () use ($functionId, $deploymentId) {
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
]));
$this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
}, 50000, 500);
if (
$deployment['headers']['status-code'] >= 400
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
) {
break;
}
\sleep(1);
}
if ($checkForSuccess) {
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body']));
}
return $deploymentId;
}
// /**
// * @depends testCreateTeam
// */
// public function testGetTeam($data):array
// {
// $id = $data['teamUid'] ?? '';
protected function cleanupFunction(string $functionId): void
{
$function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]));
// /**
// * Test for SUCCESS
// */
// $response = $this->client->call(Client::METHOD_GET, '/teams/'.$id, array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()));
$this->assertEquals($function['headers']['status-code'], 204);
}
// $this->assertEquals(200, $response['headers']['status-code']);
// $this->assertNotEmpty($response['body']['$id']);
// $this->assertEquals('Arsenal', $response['body']['name']);
// $this->assertGreaterThan(-1, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertIsInt($response['body']['dateCreated']);
protected function createFunction(mixed $params): mixed
{
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
// /**
// * Test for FAILURE
// */
return $function;
}
// return [];
// }
protected function createVariable(string $functionId, mixed $params): mixed
{
$variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
// /**
// * @depends testCreateTeam
// */
// public function testListTeams($data):array
// {
// /**
// * Test for SUCCESS
// */
// $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()));
return $variable;
}
// $this->assertEquals(200, $response['headers']['status-code']);
// $this->assertGreaterThan(0, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertCount(3, $response['body']['teams']);
protected function getFunction(string $functionId): mixed
{
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
// $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'limit' => 2,
// ]);
return $function;
}
// $this->assertEquals(200, $response['headers']['status-code']);
// $this->assertGreaterThan(0, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertCount(2, $response['body']['teams']);
protected function getDeployment(string $functionId, string $deploymentId): mixed
{
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
// $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'offset' => 1,
// ]);
return $deployment;
}
// $this->assertEquals(200, $response['headers']['status-code']);
// $this->assertGreaterThan(0, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertCount(2, $response['body']['teams']);
protected function getExecution(string $functionId, $executionId): mixed
{
$execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
// $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'search' => 'Manchester',
// ]);
return $execution;
}
// $this->assertEquals(200, $response['headers']['status-code']);
// $this->assertGreaterThan(0, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertCount(1, $response['body']['teams']);
// $this->assertEquals('Manchester United', $response['body']['teams'][0]['name']);
protected function listFunctions(mixed $params = []): mixed
{
$functions = $this->client->call(Client::METHOD_GET, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
// $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'search' => 'United',
// ]);
return $functions;
}
// $this->assertEquals(200, $response['headers']['status-code']);
// $this->assertGreaterThan(0, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertCount(1, $response['body']['teams']);
// $this->assertEquals('Manchester United', $response['body']['teams'][0]['name']);
protected function listDeployments(string $functionId, $params = []): mixed
{
$deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
// /**
// * Test for FAILURE
// */
return $deployments;
}
// return [];
// }
protected function listExecutions(string $functionId, mixed $params = []): mixed
{
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
// public function testUpdateTeam():array
// {
// /**
// * Test for SUCCESS
// */
// $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'name' => 'Demo'
// ]);
return $executions;
}
// $this->assertEquals(201, $response['headers']['status-code']);
// $this->assertNotEmpty($response['body']['$id']);
// $this->assertEquals('Demo', $response['body']['name']);
// $this->assertGreaterThan(-1, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertIsInt($response['body']['dateCreated']);
protected function packageFunction(string $function): CURLFile
{
$folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function";
$tarPath = "$folderPath/code.tar.gz";
// $response = $this->client->call(Client::METHOD_PUT, '/teams/'.$response['body']['$id'], array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'name' => 'Demo New'
// ]);
Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
// $this->assertEquals(200, $response['headers']['status-code']);
// $this->assertNotEmpty($response['body']['$id']);
// $this->assertEquals('Demo New', $response['body']['name']);
// $this->assertGreaterThan(-1, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertIsInt($response['body']['dateCreated']);
if (filesize($tarPath) > 1024 * 1024 * 5) {
throw new \Exception('Code package is too large. Use the chunked upload method instead.');
}
// /**
// * Test for FAILURE
// */
// $response = $this->client->call(Client::METHOD_PUT, '/teams/'.$response['body']['$id'], array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// ]);
return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
}
// $this->assertEquals(400, $response['headers']['status-code']);
protected function createDeployment(string $functionId, mixed $params = []): mixed
{
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
// return [];
// }
return $deployment;
}
// public function testDeleteTeam():array
// {
// /**
// * Test for SUCCESS
// */
// $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'name' => 'Demo'
// ]);
protected function getFunctionUsage(string $functionId, mixed $params): mixed
{
$usage = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
// $teamUid = $response['body']['$id'];
return $usage;
}
// $this->assertEquals(201, $response['headers']['status-code']);
// $this->assertNotEmpty($response['body']['$id']);
// $this->assertEquals('Demo', $response['body']['name']);
// $this->assertGreaterThan(-1, $response['body']['total']);
// $this->assertIsInt($response['body']['total']);
// $this->assertIsInt($response['body']['dateCreated']);
protected function getTemplate(string $templateId)
{
$template = $this->client->call(Client::METHOD_GET, '/functions/templates/' . $templateId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
// $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid, array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()));
return $template;
}
// $this->assertEquals(204, $response['headers']['status-code']);
// $this->assertEmpty($response['body']);
protected function createExecution(string $functionId, mixed $params = []): mixed
{
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
// /**
// * Test for FAILURE
// */
// $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid, array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()));
return $execution;
}
// $this->assertEquals(404, $response['headers']['status-code']);
protected function deleteFunction(string $functionId): mixed
{
$function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
// return [];
// }
return $function;
}
}
@@ -13,13 +13,11 @@ class FunctionsConsoleClientTest extends Scope
{
use ProjectCustom;
use SideConsole;
use FunctionsBase;
public function testCreateFunction(): array
{
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
@@ -35,10 +33,9 @@ class FunctionsConsoleClientTest extends Scope
$this->assertEquals(201, $function['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
$functionId = $function['body']['$id'];
$function2 = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Test Failure',
'execute' => ['some-random-string'],
@@ -46,73 +43,59 @@ class FunctionsConsoleClientTest extends Scope
'entrypoint' => 'index.php',
]);
$this->assertEquals(400, $response['headers']['status-code']);
$this->assertEquals(400, $function2['headers']['status-code']);
return [
'functionId' => $function['body']['$id']
'functionId' => $functionId,
];
}
/**
* @depends testCreateFunction
*/
public function testGetCollectionUsage(array $data)
public function testFunctionUsage(array $data)
{
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'range' => '232h'
]);
$this->assertEquals(400, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/functions/randomFunctionId/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'range' => '24h'
]);
$this->assertEquals(404, $response['headers']['status-code']);
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
$usage = $this->getFunctionUsage($data['functionId'], [
'range' => '24h'
]);
$this->assertEquals(200, $usage['headers']['status-code']);
$this->assertEquals(19, count($usage['body']));
$this->assertEquals('24h', $usage['body']['range']);
$this->assertIsNumeric($usage['body']['deploymentsTotal']);
$this->assertIsNumeric($usage['body']['deploymentsStorageTotal']);
$this->assertIsNumeric($usage['body']['buildsTotal']);
$this->assertIsNumeric($usage['body']['buildsStorageTotal']);
$this->assertIsNumeric($usage['body']['buildsTimeTotal']);
$this->assertIsNumeric($usage['body']['buildsMbSecondsTotal']);
$this->assertIsNumeric($usage['body']['executionsTotal']);
$this->assertIsNumeric($usage['body']['executionsTimeTotal']);
$this->assertIsNumeric($usage['body']['executionsMbSecondsTotal']);
$this->assertIsArray($usage['body']['deployments']);
$this->assertIsArray($usage['body']['deploymentsStorage']);
$this->assertIsArray($usage['body']['builds']);
$this->assertIsArray($usage['body']['buildsTime']);
$this->assertIsArray($usage['body']['buildsStorage']);
$this->assertIsArray($usage['body']['buildsTime']);
$this->assertIsArray($usage['body']['buildsMbSeconds']);
$this->assertIsArray($usage['body']['executions']);
$this->assertIsArray($usage['body']['executionsTime']);
$this->assertIsArray($usage['body']['executionsMbSeconds']);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(19, count($response['body']));
$this->assertEquals('24h', $response['body']['range']);
$this->assertIsNumeric($response['body']['deploymentsTotal']);
$this->assertIsNumeric($response['body']['deploymentsStorageTotal']);
$this->assertIsNumeric($response['body']['buildsTotal']);
$this->assertIsNumeric($response['body']['buildsStorageTotal']);
$this->assertIsNumeric($response['body']['buildsTimeTotal']);
$this->assertIsNumeric($response['body']['buildsMbSecondsTotal']);
$this->assertIsNumeric($response['body']['executionsTotal']);
$this->assertIsNumeric($response['body']['executionsTimeTotal']);
$this->assertIsNumeric($response['body']['executionsMbSecondsTotal']);
$this->assertIsArray($response['body']['deployments']);
$this->assertIsArray($response['body']['deploymentsStorage']);
$this->assertIsArray($response['body']['builds']);
$this->assertIsArray($response['body']['buildsTime']);
$this->assertIsArray($response['body']['buildsStorage']);
$this->assertIsArray($response['body']['buildsTime']);
$this->assertIsArray($response['body']['buildsMbSeconds']);
$this->assertIsArray($response['body']['executions']);
$this->assertIsArray($response['body']['executionsTime']);
$this->assertIsArray($response['body']['executionsMbSeconds']);
/**
* Test for FAILURE
*/
$usage = $this->getFunctionUsage($data['functionId'], [
'range' => '232h'
]);
$this->assertEquals(400, $usage['headers']['status-code']);
$usage = $this->getFunctionUsage('randomFunctionId', [
'range' => '24h'
]);
$this->assertEquals(404, $usage['headers']['status-code']);
}
/**
@@ -123,31 +106,53 @@ class FunctionsConsoleClientTest extends Scope
/**
* Test for SUCCESS
*/
$variable = $this->createVariable(
$data['functionId'],
[
'key' => 'APP_TEST',
'value' => 'TESTINGVALUE'
]
);
$response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'APP_TEST',
'value' => 'TESTINGVALUE'
]);
$this->assertEquals(201, $variable['headers']['status-code']);
$this->assertEquals(201, $response['headers']['status-code']);
$variableId = $response['body']['$id'];
$variableId = $variable['body']['$id'];
/**
* Test for FAILURE
*/
// Test for duplicate key
$variable = $this->createVariable(
$data['functionId'],
[
'key' => 'APP_TEST',
'value' => 'ANOTHERTESTINGVALUE'
]
);
$response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'APP_TEST',
'value' => 'ANOTHER_TESTINGVALUE'
]);
$this->assertEquals(409, $variable['headers']['status-code']);
$this->assertEquals(409, $response['headers']['status-code']);
// Test for invalid key
$variable = $this->createVariable(
$data['functionId'],
[
'key' => str_repeat("A", 256),
'value' => 'TESTINGVALUE'
]
);
$this->assertEquals(400, $variable['headers']['status-code']);
// Test for invalid value
$variable = $this->createVariable(
$data['functionId'],
[
'key' => 'LONGKEY',
'value' => str_repeat("#", 8193),
]
);
$this->assertEquals(400, $variable['headers']['status-code']);
return array_merge(
$data,
@@ -155,28 +160,6 @@ class FunctionsConsoleClientTest extends Scope
'variableId' => $variableId
]
);
$longKey = str_repeat("A", 256);
$response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => $longKey,
'value' => 'TESTINGVALUE'
]);
$this->assertEquals(400, $response['headers']['status-code']);
$longValue = str_repeat("#", 8193);
$response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'LONGKEY',
'value' => $longValue
]);
$this->assertEquals(400, $response['headers']['status-code']);
}
/**
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,214 @@
<?php
namespace Tests\E2E\Services\Functions;
use Appwrite\ID;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Utopia\Database\Helpers\Role;
class FunctionsScheduleTest extends Scope
{
use FunctionsBase;
use ProjectCustom;
use SideServer;
public function testCreateScheduledExecution()
{
/**
* Test for SUCCESS
*/
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'events' => [
'users.*.create',
'users.*.delete',
],
'schedule' => '* * * * *', // Execute every 60 seconds
'timeout' => 10,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'activate' => true
]);
// Wait for scheduled execution
\sleep(60);
$this->assertEventually(function () use ($functionId) {
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
]);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertCount(1, $executions['body']['executions']);
$asyncExecution = $executions['body']['executions'][0];
$this->assertEquals('schedule', $asyncExecution['trigger']);
$this->assertEquals('completed', $asyncExecution['status']);
$this->assertEquals(200, $asyncExecution['responseStatusCode']);
$this->assertEquals('', $asyncExecution['responseBody']);
$this->assertNotEmpty($asyncExecution['logs']);
$this->assertNotEmpty($asyncExecution['errors']);
$this->assertGreaterThan(0, $asyncExecution['duration']);
}, 60000, 500);
$this->cleanupFunction($functionId);
}
public function testCreateScheduledAtExecution(): void
{
/**
* Test for SUCCESS
*/
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'timeout' => 10,
'logging' => true,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'activate' => true
]);
// Schedule execution for the future
\date_default_timezone_set('UTC');
$futureTime = (new \DateTime())->add(new \DateInterval('PT2M')); // 2 minute in the future
$futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
$execution = $this->client->call(
Client::METHOD_POST,
'/functions/' . $functionId . '/executions',
[
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'origin' => 'http://localhost',
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $this->getUser()['session'],
],
[
'async' => true,
'scheduledAt' => $futureTime->format(\DateTime::ATOM),
'path' => '/custom-path',
'method' => 'PATCH',
'body' => 'custom-body',
'headers' => [
'x-custom-header' => 'custom-value'
]
]
);
$executionId = $execution['body']['$id'];
$this->assertEquals(202, $execution['headers']['status-code']);
$this->assertEquals('scheduled', $execution['body']['status']);
$this->assertEquals('PATCH', $execution['body']['requestMethod']);
$this->assertEquals('/custom-path', $execution['body']['requestPath']);
$this->assertCount(0, $execution['body']['requestHeaders']);
\sleep(120);
$this->assertEventually(function () use ($functionId, $executionId) {
$execution = $this->getExecution($functionId, $executionId);
$this->assertEquals(200, $execution['headers']['status-code']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals('/custom-path', $execution['body']['requestPath']);
$this->assertEquals('PATCH', $execution['body']['requestMethod']);
$this->assertStringContainsString('body-is-custom-body', $execution['body']['logs']);
$this->assertStringContainsString('custom-header-is-custom-value', $execution['body']['logs']);
$this->assertStringContainsString('method-is-patch', $execution['body']['logs']);
$this->assertStringContainsString('path-is-/custom-path', $execution['body']['logs']);
$this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']);
$this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']);
$this->assertGreaterThan(0, $execution['body']['duration']);
}, 10000, 500);
/* Test for FAILURE */
// Schedule synchronous execution
$execution = $this->createExecution($functionId, [
'async' => 'false',
'scheduledAt' => $futureTime->format(\DateTime::ATOM),
]);
$this->assertEquals(400, $execution['headers']['status-code']);
// Execution with seconds precision
$execution = $this->createExecution($functionId, [
'async' => true,
'scheduledAt' => (new \DateTime("2100-12-08 16:12:02"))->format(\DateTime::ATOM)
]);
$this->assertEquals(400, $execution['headers']['status-code']);
// Execution with milliseconds precision
$execution = $this->createExecution($functionId, [
'async' => true,
'scheduledAt' => (new \DateTime("2100-12-08 16:12:02.255"))->format(\DateTime::ATOM)
]);
$this->assertEquals(400, $execution['headers']['status-code']);
// Execution too soon
$execution = $this->createExecution($functionId, [
'async' => true,
'scheduledAt' => (new \DateTime())->add(new \DateInterval('PT1S'))->format(\DateTime::ATOM)
]);
$this->assertEquals(400, $execution['headers']['status-code']);
$this->cleanupFunction($functionId, $executionId);
}
public function testDeleteScheduledExecution()
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test',
'execute' => [Role::user($this->getUser()['$id'])->toString()],
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'timeout' => 10,
'logging' => true,
]);
$this->setupDeployment($functionId, [
'entrypoint' => 'index.php',
'code' => $this->packageFunction('php'),
'activate' => true
]);
$futureTime = (new \DateTime())->add(new \DateInterval('PT10H'));
$futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0);
$execution = $this->createExecution($functionId, [
'async' => true,
'scheduledAt' => $futureTime->format('Y-m-d H:i:s'),
]);
$this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
$execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(204, $execution['headers']['status-code']);
$this->cleanupFunction($functionId);
}
}
+12 -2
View File
@@ -2,6 +2,7 @@
namespace Tests\E2E\Services\GraphQL;
use CURLFile;
use Utopia\CLI\Console;
trait Base
@@ -2496,8 +2497,17 @@ trait Base
protected string $stdout = '';
protected string $stderr = '';
protected function packageCode($folder): void
protected function packageFunction(string $function): CURLFile
{
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
$folderPath = realpath(__DIR__ . '/../../../resources/functions') . "/$function";
$tarPath = "$folderPath/code.tar.gz";
Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr);
if (filesize($tarPath) > 1024 * 1024 * 5) {
throw new \Exception('Code package is too large. Use the chunked upload method instead.');
}
return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath));
}
}
@@ -2,7 +2,6 @@
namespace Tests\E2E\Services\GraphQL;
use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@@ -83,10 +82,6 @@ class FunctionsClientTest extends Scope
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
$folder = 'php';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
$this->packageCode($folder);
$gqlPayload = [
'operations' => \json_encode([
'query' => $query,
@@ -99,7 +94,7 @@ class FunctionsClientTest extends Scope
'map' => \json_encode([
'code' => ["variables.code"]
]),
'code' => new CURLFile($code, 'application/gzip', 'code.tar.gz'),
'code' => $this->packageFunction('php')
];
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', [
@@ -2,7 +2,6 @@
namespace Tests\E2E\Services\GraphQL;
use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@@ -82,10 +81,6 @@ class FunctionsServerTest extends Scope
$projectId = $this->getProject()['$id'];
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
$folder = 'php';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
$this->packageCode($folder);
$gqlPayload = [
'operations' => \json_encode([
'query' => $query,
@@ -98,7 +93,7 @@ class FunctionsServerTest extends Scope
'map' => \json_encode([
'code' => ["variables.code"]
]),
'code' => new CURLFile($code, 'application/gzip', 'code.tar.gz'),
'code' => $this->packageFunction('php'),
];
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
+41 -26
View File
@@ -7,25 +7,34 @@ use WebSocket\ConnectionException;
trait RealtimeBase
{
private function getWebsocket($channels = [], $headers = [], $projectId = null): WebSocketClient
{
private function getWebsocket(
array $channels = [],
array $headers = [],
string $projectId = null
): WebSocketClient {
if (is_null($projectId)) {
$projectId = $this->getProject()['$id'];
}
$headers = array_merge([
'Origin' => 'appwrite.test'
], $headers);
$headers = array_merge(
[
"Origin" => "appwrite.test",
],
$headers
);
$query = [
'project' => $projectId,
'channels' => $channels
"project" => $projectId,
"channels" => $channels,
];
return new WebSocketClient('ws://appwrite-traefik/v1/realtime?' . http_build_query($query), [
'headers' => $headers,
'timeout' => 30,
]);
return new WebSocketClient(
"ws://appwrite-traefik/v1/realtime?" . http_build_query($query),
[
"headers" => $headers,
"timeout" => 30,
]
);
}
public function testConnection(): void
@@ -33,7 +42,7 @@ trait RealtimeBase
/**
* Test for SUCCESS
*/
$client = $this->getWebsocket(['documents']);
$client = $this->getWebsocket(["documents"]);
$this->assertNotEmpty($client->receive());
$client->close();
}
@@ -43,11 +52,11 @@ trait RealtimeBase
$client = $this->getWebsocket();
$payload = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $payload);
$this->assertArrayHasKey('data', $payload);
$this->assertEquals('error', $payload['type']);
$this->assertEquals(1008, $payload['data']['code']);
$this->assertEquals('Missing channels', $payload['data']['message']);
$this->assertArrayHasKey("type", $payload);
$this->assertArrayHasKey("data", $payload);
$this->assertEquals("error", $payload["type"]);
$this->assertEquals(1008, $payload["data"]["code"]);
$this->assertEquals("Missing channels", $payload["data"]["message"]);
\usleep(250000); // 250ms
$this->expectException(ConnectionException::class); // Check if server disconnnected client
$client->close();
@@ -55,18 +64,24 @@ trait RealtimeBase
public function testConnectionFailureUnknownProject(): void
{
$client = new WebSocketClient('ws://appwrite-traefik/v1/realtime?project=123', [
'headers' => [
'Origin' => 'appwrite.test'
$client = new WebSocketClient(
"ws://appwrite-traefik/v1/realtime?project=123",
[
"headers" => [
"Origin" => "appwrite.test",
],
]
]);
);
$payload = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $payload);
$this->assertArrayHasKey('data', $payload);
$this->assertEquals('error', $payload['type']);
$this->assertEquals(1008, $payload['data']['code']);
$this->assertEquals('Missing or unknown project ID', $payload['data']['message']);
$this->assertArrayHasKey("type", $payload);
$this->assertArrayHasKey("data", $payload);
$this->assertEquals("error", $payload["type"]);
$this->assertEquals(1008, $payload["data"]["code"]);
$this->assertEquals(
"Missing or unknown project ID",
$payload["data"]["message"]
);
\usleep(250000); // 250ms
$this->expectException(ConnectionException::class); // Check if server disconnnected client
$client->close();
@@ -2,7 +2,6 @@
namespace Tests\E2E\Services\Realtime;
use CURLFile;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@@ -19,7 +18,7 @@ class RealtimeConsoleClientTest extends Scope
use ProjectCustom;
use SideConsole;
public function testManualAuthentication()
public function testManualAuthentication(): void
{
$user = $this->getUser();
$userId = $user['$id'] ?? '';
@@ -124,7 +123,7 @@ class RealtimeConsoleClientTest extends Scope
$client->close();
}
public function testAttributes()
public function testAttributes(): array
{
$user = $this->getUser();
$projectId = 'console';
@@ -184,6 +183,7 @@ class RealtimeConsoleClientTest extends Scope
'required' => true,
]);
$projectId = $this->getProject()['$id'];
$attributeKey = $name['body']['key'];
$this->assertEquals($name['headers']['status-code'], 202);
@@ -198,8 +198,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*.create", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -218,8 +219,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*.update", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -276,6 +278,8 @@ class RealtimeConsoleClientTest extends Scope
]);
$this->assertEquals($index['headers']['status-code'], 202);
$projectId = $this->getProject()['$id'];
$indexKey = $index['body']['key'];
$response = json_decode($client->receive(), true);
@@ -285,8 +289,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*.create", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -303,8 +308,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*.update", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -343,6 +349,8 @@ class RealtimeConsoleClientTest extends Scope
$this->assertContains('console', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
$projectId = $this->getProject()['$id'];
/**
* Test Delete Index
*/
@@ -353,6 +361,7 @@ class RealtimeConsoleClientTest extends Scope
], $this->getHeaders()));
$this->assertEquals($attribute['headers']['status-code'], 204);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
@@ -360,8 +369,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*.update", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -377,8 +387,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*.delete", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.indexes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -416,10 +427,12 @@ class RealtimeConsoleClientTest extends Scope
$this->assertContains('console', $response['data']['channels']);
$this->assertNotEmpty($response['data']['user']);
$attributeKey = 'name';
$projectId = $this->getProject()['$id'];
/**
* Test Delete Attribute
*/
$attributeKey = 'name';
$attribute = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['actorsId'] . '/attributes/' . $attributeKey, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -433,8 +446,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*.update", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -450,8 +464,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*.delete", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}.attributes.*", $response['data']['events']);
$this->assertContains("databases.{$databaseId}.collections.{$actorsId}", $response['data']['events']);
@@ -505,22 +520,16 @@ class RealtimeConsoleClientTest extends Scope
/**
* Test Create Deployment
*/
$folder = 'php';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
$this->packageCode($folder);
$projectId = $this->getProject()['$id'];
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-project' => $projectId,
], $this->getHeaders()), [
'entrypoint' => 'index.php',
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
'code' => $this->packageFunction('php'),
'activate' => true
]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(202, $deployment['headers']['status-code']);
$response = json_decode($client->receive(), true);
@@ -530,8 +539,9 @@ class RealtimeConsoleClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertCount(2, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
// $this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.create", $response['data']['events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertNotEmpty($response['data']['payload']);
@@ -7,7 +7,7 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Utopia\CLI\Console;
use Tests\E2E\Services\Functions\FunctionsBase;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
@@ -15,6 +15,7 @@ use WebSocket\ConnectionException;
class RealtimeCustomClientTest extends Scope
{
use FunctionsBase;
use RealtimeBase;
use ProjectCustom;
use SideClient;
@@ -1271,20 +1272,13 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals($function['headers']['status-code'], 201);
$this->assertNotEmpty($function['body']['$id']);
$folder = 'timeout';
$stderr = '';
$stdout = '';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/{$folder}/code.tar.gz";
Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/{$folder} && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr);
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'entrypoint' => 'index.php',
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
'code' => $this->packageFunction('timeout'),
'activate' => true
]);
@@ -1340,8 +1334,9 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals('event', $response['type']);
$this->assertNotEmpty($response['data']);
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(4, $response['data']['channels']);
$this->assertCount(5, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$this->getProject()['$id']}", $response['data']['channels']);
$this->assertContains('executions', $response['data']['channels']);
$this->assertContains("executions.{$executionId}", $response['data']['channels']);
$this->assertContains("functions.{$functionId}", $response['data']['channels']);
@@ -1362,8 +1357,9 @@ class RealtimeCustomClientTest extends Scope
$this->assertEquals('event', $responseUpdate['type']);
$this->assertNotEmpty($responseUpdate['data']);
$this->assertArrayHasKey('timestamp', $responseUpdate['data']);
$this->assertCount(4, $responseUpdate['data']['channels']);
$this->assertCount(5, $responseUpdate['data']['channels']);
$this->assertContains('console', $responseUpdate['data']['channels']);
$this->assertContains("projects.{$this->getProject()['$id']}", $response['data']['channels']);
$this->assertContains('executions', $responseUpdate['data']['channels']);
$this->assertContains("executions.{$executionId}", $responseUpdate['data']['channels']);
$this->assertContains("functions.{$functionId}", $responseUpdate['data']['channels']);
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace Appwrite\Tests;
use Appwrite\Tests\Async\Eventually;
use PHPUnit\Framework\Assert;
const DEFAULT_TIMEOUT_MS = 10000;
const DEFAULT_WAIT_MS = 500;
trait Async
{
public static function assertEventually(callable $probe, int $timeoutMs = DEFAULT_TIMEOUT_MS, int $waitMs = DEFAULT_WAIT_MS): void
{
Assert::assertThat($probe, new Eventually($timeoutMs, $waitMs));
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace Appwrite\Tests\Async;
use PHPUnit\Framework\Constraint\Constraint;
final class Eventually extends Constraint
{
public function __construct(private int $timeoutMs, private int $waitMs)
{
}
public function evaluate(mixed $probe, string $description = '', bool $returnResult = false): ?bool
{
if (!is_callable($probe)) {
throw new \Exception('Probe must be a callable');
}
$start = microtime(true);
$lastException = null;
do {
try {
$probe();
return true;
} catch (\Exception $exception) {
$lastException = $exception;
}
usleep($this->waitMs * 1000);
} while (microtime(true) - $start < $this->timeoutMs / 1000);
if ($returnResult) {
return false;
}
throw $lastException;
}
protected function failureDescription(mixed $other): string
{
return 'the given probe was satisfied within ' . $this->timeoutMs . 'ms.';
}
public function toString(): string
{
return 'Eventually';
}
}