Merge branch '1.8.x' into ser-539-permissions

This commit is contained in:
Hemachandar
2026-02-18 12:02:14 +05:30
committed by GitHub
37 changed files with 412 additions and 139 deletions
+113
View File
@@ -0,0 +1,113 @@
name: "Generate Specs"
env:
COMPOSE_FILE: docker-compose.yml
on:
workflow_dispatch:
inputs:
version:
type: choice
description: "Appwrite version to generate specs for"
required: true
options:
- "1.8.x"
- "1.7.x"
- "1.6.x"
- "1.5.x"
- "latest"
push:
type: boolean
description: "Push specs to appwrite/specs repo and create PR"
default: true
message:
type: string
description: "Commit message for the specs PR"
default: "chore: update API specs and SDK examples"
jobs:
generate:
name: Generate Specs
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build and start Appwrite
run: |
docker compose build
docker compose up -d
- name: Generate specs
run: |
docker compose exec appwrite specs --version=${{ inputs.version }} --mode=normal --git=no
- name: Generate SDK examples
if: inputs.push
run: |
docker compose exec appwrite sdks --platform=* --sdk=* --version=${{ inputs.version }} --git=no --mode=examples
sudo chown -R $USER:$USER ./docs/examples/
- name: Push to appwrite/specs and create PR
if: inputs.push
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ inputs.version }}"
MESSAGE="${{ inputs.message }}"
GIT_BRANCH="feat-${VERSION}-specs"
SPECS_DIR="./app/config/specs"
EXAMPLES_DIR="./docs/examples/${VERSION}"
TARGET="/tmp/specs-repo"
sudo chown -R $USER:$USER "${SPECS_DIR}"
# Clone the specs repo
git clone "https://x-access-token:${GH_TOKEN}@github.com/appwrite/specs.git" "${TARGET}"
cd "${TARGET}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Create or checkout the feature branch
git checkout -b "${GIT_BRANCH}" || git checkout "${GIT_BRANCH}"
# Copy spec files
mkdir -p "specs/${VERSION}"
cp ${GITHUB_WORKSPACE}/${SPECS_DIR}/*${VERSION}*.json "specs/${VERSION}/" 2>/dev/null || true
# Copy latest specs if version is latest
if [ "${VERSION}" = "latest" ]; then
cp ${GITHUB_WORKSPACE}/${SPECS_DIR}/*latest*.json "specs/latest/" 2>/dev/null || true
fi
# Copy SDK examples
if [ -d "${GITHUB_WORKSPACE}/docs/examples/${VERSION}" ]; then
mkdir -p "examples/${VERSION}"
cp -r "${GITHUB_WORKSPACE}/docs/examples/${VERSION}/." "examples/${VERSION}/"
fi
# Commit and push
git add -A
git diff --cached --quiet && echo "No changes to commit" && exit 0
git commit -m "${MESSAGE}"
git push -u origin "${GIT_BRANCH}" --force
# Create or update PR
EXISTING_PR=$(gh pr list --repo appwrite/specs --head "${GIT_BRANCH}" --json number --jq '.[0].number' 2>/dev/null || true)
if [ -n "${EXISTING_PR}" ]; then
echo "PR #${EXISTING_PR} already exists, updated branch"
echo "https://github.com/appwrite/specs/pull/${EXISTING_PR}"
else
gh pr create \
--repo "appwrite/specs" \
--title "feat: API specs update for version ${VERSION}" \
--body "This PR contains API specification updates and SDK examples for version ${VERSION}." \
--base "main" \
--head "${GIT_BRANCH}"
fi
- name: Stop containers
if: always()
run: docker compose down
+2 -2
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '22.1.0',
'version' => '22.3.1',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -140,7 +140,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.21.0',
'version' => '0.23.1',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
+3 -3
View File
@@ -11,6 +11,9 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CompoundUID;
use Appwrite\Utopia\Database\Validator\Queries\Migrations;
use Appwrite\Utopia\Response;
use Utopia\Compression\Algorithms\GZIP;
use Utopia\Compression\Algorithms\Zstd;
use Utopia\Compression\Compression;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
@@ -29,9 +32,6 @@ use Utopia\Migration\Sources\Firebase;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Sources\Supabase;
use Utopia\Migration\Transfer;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
+7 -8
View File
@@ -34,6 +34,7 @@ use Appwrite\Utopia\View;
use Executor\Executor;
use MaxMind\Db\Reader;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Table;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Database;
@@ -1073,21 +1074,20 @@ Http::init()
->inject('queueForCertificates')
->inject('platform')
->inject('authorization')
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) {
->inject('certifiedDomains')
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization, Table $certifiedDomains) {
$hostname = $request->getHostname();
$cache = Config::getParam('hostnames', []);
$platformHostnames = $platform['hostnames'] ?? [];
// 1. Cache hit
if (array_key_exists($hostname, $cache)) {
if ($certifiedDomains->exists(md5($hostname))) {
return;
}
// 2. Domain validation
$domain = new Domain(!empty($hostname) ? $hostname : '');
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$cache[$domain->get()] = false;
Config::setParam('hostnames', $cache);
$certifiedDomains->set(md5($domain->get()), ['value' => 0]);
return;
}
@@ -1101,7 +1101,7 @@ Http::init()
}
// 4. Check/create rule (requires DB access)
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, &$cache) {
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, $certifiedDomains) {
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
@@ -1164,8 +1164,7 @@ Http::init()
} catch (Duplicate $e) {
Console::info('Certificate already exists');
} finally {
$cache[$domain->get()] = true;
Config::setParam('hostnames', $cache);
$certifiedDomains->set(md5($domain->get()), ['value' => 1]);
}
});
});
+23 -16
View File
@@ -37,11 +37,19 @@ use Utopia\System\System;
const DOMAIN_SYNC_TIMER = 30; // 30 seconds
$files = null;
$files = new Files();
$files->load(__DIR__ . '/../public');
$domains = new Table(1_000_000); // 1 million rows
$domains->column('value', Table::TYPE_INT, 1);
$domains->create();
$riskyDomains = new Table(100_000);
$riskyDomains->column('value', Table::TYPE_INT, 1);
$riskyDomains->create();
$certifiedDomains = new Table(100_000);
$certifiedDomains->column('value', Table::TYPE_INT, 1);
$certifiedDomains->create();
Http::setResource('riskyDomains', fn () => $riskyDomains);
Http::setResource('certifiedDomains', fn () => $certifiedDomains);
$http = new Server(
host: "0.0.0.0",
@@ -70,7 +78,7 @@ $totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intva
function dispatch(Server $server, int $fd, int $type, $data = null): int
{
$resolveWorkerId = function (Server $server, $data = null) {
global $totalWorkers, $domains;
global $totalWorkers, $riskyDomains;
// If data is not set we can send request to any worker
// first we try to pick idle worker, if not we randomly pick a worker
@@ -102,7 +110,7 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
$risky = false;
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
$risky = true;
} elseif ($domains->get(md5($domain), 'value') === 1) {
} elseif ($riskyDomains->get(md5($domain), 'value') === 1) {
// executions request coming from custom domain
$risky = true;
} else {
@@ -164,11 +172,7 @@ $http
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) use (&$files) {
if (!$server->taskworker) {
$files = new Files();
$files->load(__DIR__ . '/../public');
}
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
});
$http->on(Constant::EVENT_WORKER_STOP, function ($server, $workerId) {
@@ -452,7 +456,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
});
});
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register, &$files) {
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register, $files) {
Span::init('http.request');
Http::setResource('swooleRequest', fn () => $swooleRequest);
@@ -463,7 +467,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
Span::add('http.method', $request->getMethod());
if ($files instanceof Files && $files->isFileLoaded($request->getURI())) {
if ($files->isFileLoaded($request->getURI())) {
$time = (60 * 60 * 24 * 45); // 45 days cache
$response
@@ -582,7 +586,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
});
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
$http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
$http->on(Constant::EVENT_TASK, function () use ($register) {
$lastSyncUpdate = null;
$pools = $register->get('pools');
Http::setResource('pools', fn () => $pools);
@@ -591,7 +595,10 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
/** @var Utopia\Database\Database $dbForPlatform */
$dbForPlatform = $app->getResource('dbForPlatform');
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) {
/** @var Table $riskyDomains */
$riskyDomains = $app->getResource('riskyDomains');
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $app) {
try {
$time = DateTime::now();
$limit = 1000;
@@ -644,7 +651,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
continue;
}
$domains->set(md5($domain), ['value' => 1]);
$riskyDomains->set(md5($domain), ['value' => 1]);
}
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
}
+2 -1
View File
@@ -51,6 +51,7 @@
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "1.0.*",
"utopia-php/cli": "0.22.*",
"utopia-php/compression": "0.1.*",
"utopia-php/config": "1.*",
"utopia-php/console": "0.1.*",
"utopia-php/database": "5.*",
@@ -72,7 +73,7 @@
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.15.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/storage": "1.0.*",
"utopia-php/system": "0.10.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/vcs": "1.*",
Generated
+48 -53
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": "fe847eccf6ba73bfca1e08d26e9fc7ab",
"content-hash": "dd9887a50fb434887461a99413007573",
"packages": [
{
"name": "adhocore/jwt",
@@ -161,16 +161,16 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.19.3",
"version": "0.19.4",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "bfe9f3c730c8c203c5e2d80bd5a81c9b1579714e"
"reference": "eea9d1b3ca2540eab623b419c8afde09ef406c0b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/bfe9f3c730c8c203c5e2d80bd5a81c9b1579714e",
"reference": "bfe9f3c730c8c203c5e2d80bd5a81c9b1579714e",
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/eea9d1b3ca2540eab623b419c8afde09ef406c0b",
"reference": "eea9d1b3ca2540eab623b419c8afde09ef406c0b",
"shasum": ""
},
"require": {
@@ -210,9 +210,9 @@
],
"support": {
"issues": "https://github.com/appwrite/runtimes/issues",
"source": "https://github.com/appwrite/runtimes/tree/0.19.3"
"source": "https://github.com/appwrite/runtimes/tree/0.19.4"
},
"time": "2026-02-06T10:08:18+00:00"
"time": "2026-02-17T10:04:39+00:00"
},
{
"name": "brick/math",
@@ -3656,20 +3656,20 @@
},
{
"name": "utopia-php/compression",
"version": "0.1.3",
"version": "0.1.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/compression.git",
"reference": "66f093557ba66d98245e562036182016c7dcfe8a"
"reference": "68045cb9d714c1259582d2dfd0e76bd34f83e713"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/compression/zipball/66f093557ba66d98245e562036182016c7dcfe8a",
"reference": "66f093557ba66d98245e562036182016c7dcfe8a",
"url": "https://api.github.com/repos/utopia-php/compression/zipball/68045cb9d714c1259582d2dfd0e76bd34f83e713",
"reference": "68045cb9d714c1259582d2dfd0e76bd34f83e713",
"shasum": ""
},
"require": {
"php": ">=8.0"
"php": ">=8.1"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -3696,9 +3696,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/compression/issues",
"source": "https://github.com/utopia-php/compression/tree/0.1.3"
"source": "https://github.com/utopia-php/compression/tree/0.1.4"
},
"time": "2025-01-15T15:15:51+00:00"
"time": "2026-02-17T05:53:40+00:00"
},
{
"name": "utopia-php/config",
@@ -3797,16 +3797,16 @@
},
{
"name": "utopia-php/database",
"version": "5.2.0",
"version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "5c89b39de00f2b3126d0fbbdea36786341293df7"
"reference": "5e49f32ebb6e40a78209231564c40e5068810429"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/5c89b39de00f2b3126d0fbbdea36786341293df7",
"reference": "5c89b39de00f2b3126d0fbbdea36786341293df7",
"url": "https://api.github.com/repos/utopia-php/database/zipball/5e49f32ebb6e40a78209231564c40e5068810429",
"reference": "5e49f32ebb6e40a78209231564c40e5068810429",
"shasum": ""
},
"require": {
@@ -3849,9 +3849,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/5.2.0"
"source": "https://github.com/utopia-php/database/tree/5.3.0"
},
"time": "2026-02-14T09:37:28+00:00"
"time": "2026-02-17T11:50:01+00:00"
},
{
"name": "utopia-php/detector",
@@ -3948,16 +3948,16 @@
},
{
"name": "utopia-php/dns",
"version": "1.6.2",
"version": "1.6.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/dns.git",
"reference": "98c70520213a41e2fe1867e5b110273c06bf1cab"
"reference": "c46a13c42bc19365985264a6fdaaaf7d8e736d3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/98c70520213a41e2fe1867e5b110273c06bf1cab",
"reference": "98c70520213a41e2fe1867e5b110273c06bf1cab",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/c46a13c42bc19365985264a6fdaaaf7d8e736d3e",
"reference": "c46a13c42bc19365985264a6fdaaaf7d8e736d3e",
"shasum": ""
},
"require": {
@@ -3999,9 +3999,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/dns/issues",
"source": "https://github.com/utopia-php/dns/tree/1.6.2"
"source": "https://github.com/utopia-php/dns/tree/1.6.3"
},
"time": "2026-02-13T12:29:08+00:00"
"time": "2026-02-17T10:45:46+00:00"
},
{
"name": "utopia-php/domains",
@@ -4464,16 +4464,16 @@
},
{
"name": "utopia-php/migration",
"version": "1.6.0",
"version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "aa07cf9ae8cc4b8df0ab64588e033693b5ad6849"
"reference": "c5c7544d02d2418536d41050794050132f247d62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/aa07cf9ae8cc4b8df0ab64588e033693b5ad6849",
"reference": "aa07cf9ae8cc4b8df0ab64588e033693b5ad6849",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/c5c7544d02d2418536d41050794050132f247d62",
"reference": "c5c7544d02d2418536d41050794050132f247d62",
"shasum": ""
},
"require": {
@@ -4484,7 +4484,7 @@
"php": ">=8.1",
"utopia-php/database": "5.*",
"utopia-php/dsn": "0.2.*",
"utopia-php/storage": "0.18.*"
"utopia-php/storage": "1.0.*"
},
"require-dev": {
"ext-pdo": "*",
@@ -4513,9 +4513,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/1.6.0"
"source": "https://github.com/utopia-php/migration/tree/1.6.1"
},
"time": "2026-02-16T07:19:27+00:00"
"time": "2026-02-17T05:49:48+00:00"
},
{
"name": "utopia-php/mongo",
@@ -4953,32 +4953,27 @@
},
{
"name": "utopia-php/storage",
"version": "0.18.19",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "9c3f9a471250d22de7d405ee19e23e72b14181ca"
"reference": "f672e8865938e5d1d6dc3bd149ceba4e5582199e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/9c3f9a471250d22de7d405ee19e23e72b14181ca",
"reference": "9c3f9a471250d22de7d405ee19e23e72b14181ca",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/f672e8865938e5d1d6dc3bd149ceba4e5582199e",
"reference": "f672e8865938e5d1d6dc3bd149ceba4e5582199e",
"shasum": ""
},
"require": {
"ext-brotli": "*",
"ext-curl": "*",
"ext-fileinfo": "*",
"ext-lz4": "*",
"ext-simplexml": "*",
"ext-snappy": "*",
"ext-xz": "*",
"ext-zlib": "*",
"ext-zstd": "*",
"php": ">=8.1",
"utopia-php/framework": "0.*.*",
"utopia-php/system": "0.*.*",
"utopia-php/telemetry": "0.2.*"
"utopia-php/telemetry": "0.2.*",
"utopia-php/validators": "0.2.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -5005,9 +5000,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.18.19"
"source": "https://github.com/utopia-php/storage/tree/1.0.0"
},
"time": "2025-12-17T13:55:20+00:00"
"time": "2026-02-17T04:37:10+00:00"
},
{
"name": "utopia-php/system",
@@ -5390,16 +5385,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.9.2",
"version": "1.9.3",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "74de906ea5051030c5299a5d4aa74d963a531130"
"reference": "5a8a304e404c84539f3ac6562c736f8c7ef2bef2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/74de906ea5051030c5299a5d4aa74d963a531130",
"reference": "74de906ea5051030c5299a5d4aa74d963a531130",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/5a8a304e404c84539f3ac6562c736f8c7ef2bef2",
"reference": "5a8a304e404c84539f3ac6562c736f8c7ef2bef2",
"shasum": ""
},
"require": {
@@ -5435,9 +5430,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/1.9.2"
"source": "https://github.com/appwrite/sdk-generator/tree/1.9.3"
},
"time": "2026-02-16T06:59:54+00:00"
"time": "2026-02-17T11:48:05+00:00"
},
{
"name": "doctrine/annotations",
@@ -8890,7 +8885,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@@ -13,7 +13,8 @@ const result = await databases.createLongtextAttribute({
key: '',
required: false,
default: '<DEFAULT>', // optional
array: false // optional
array: false, // optional
encrypt: false // optional
});
console.log(result);
@@ -13,7 +13,8 @@ const result = await databases.createMediumtextAttribute({
key: '',
required: false,
default: '<DEFAULT>', // optional
array: false // optional
array: false, // optional
encrypt: false // optional
});
console.log(result);
@@ -13,7 +13,8 @@ const result = await databases.createTextAttribute({
key: '',
required: false,
default: '<DEFAULT>', // optional
array: false // optional
array: false, // optional
encrypt: false // optional
});
console.log(result);
@@ -14,7 +14,8 @@ const result = await databases.createVarcharAttribute({
size: 1,
required: false,
default: '<DEFAULT>', // optional
array: false // optional
array: false, // optional
encrypt: false // optional
});
console.log(result);
@@ -13,7 +13,8 @@ const result = await tablesDB.createLongtextColumn({
key: '',
required: false,
default: '<DEFAULT>', // optional
array: false // optional
array: false, // optional
encrypt: false // optional
});
console.log(result);
@@ -13,7 +13,8 @@ const result = await tablesDB.createMediumtextColumn({
key: '',
required: false,
default: '<DEFAULT>', // optional
array: false // optional
array: false, // optional
encrypt: false // optional
});
console.log(result);
@@ -13,7 +13,8 @@ const result = await tablesDB.createTextColumn({
key: '',
required: false,
default: '<DEFAULT>', // optional
array: false // optional
array: false, // optional
encrypt: false // optional
});
console.log(result);
@@ -14,7 +14,8 @@ const result = await tablesDB.createVarcharColumn({
size: 1,
required: false,
default: '<DEFAULT>', // optional
array: false // optional
array: false, // optional
encrypt: false // optional
});
console.log(result);
+3
View File
@@ -4,6 +4,9 @@
* Add `--queries` option to `list-keys` command
* Add `--key-id` option to `create-key` command
* Add `encrypt` param support to new string type attributes
* Fix `bignumber.js` bundler conflict by removing direct dependency in favor of transitive dependency from `json-bigint`
* Fix missing region in console failUrl for failed deployments
## 13.3.2
+5
View File
@@ -1,5 +1,10 @@
# Change log
## 0.23.1
* Add `upsert` method to Realtime `Channels` helper class
* Fix `bignumber.js` bundler conflict by removing direct dependency in favor of transitive dependency from `json-bigint`
## 0.21.0
* Add `queries` parameter to `client.subscribe()` for filtering Realtime events
+5
View File
@@ -1,5 +1,10 @@
# Change Log
## 22.3.1
* Add `upsert` method to Realtime `Channels` helper class
* Fix `bignumber.js` bundler conflict with Next.js Turbopack by removing direct dependency in favor of transitive dependency from `json-bigint`
## 22.1.0
* Add `queries` parameter to `Realtime.subscribe()` and `client.subscribe()` for server-side query filtering
@@ -56,7 +56,8 @@ class Get extends Action
$isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest();
$validator = new IP(IP::V4);
$isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A')));
$targetA = \explode(',', System::getEnv('_APP_DOMAIN_TARGET_A', ''))[0];
$isAValid = !empty($targetA) && ($validator->isValid($targetA));
$validator = new IP(IP::V6);
$isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'));
@@ -74,7 +75,7 @@ class Get extends Action
$variables = new Document([
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
'_APP_DOMAIN_TARGET_A' => $targetA,
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
'_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'),
@@ -7,6 +7,7 @@ use Appwrite\Event\Realtime;
use Appwrite\Permission;
use Appwrite\Role;
use Exception;
use Utopia\Compression\Compression;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Database;
@@ -16,7 +17,6 @@ use Utopia\Database\Query;
use Utopia\Fetch\Client as FetchClient;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\System\System;
+12 -16
View File
@@ -130,15 +130,7 @@ class Action extends PlatformAction
$resourceType = $rule->getAttribute('deploymentResourceType', '');
// Ensures different target based on rule's type, as configured by env variables
if ($resourceType === 'function') {
// For example: fra.appwrite.run
foreach (\explode(',', System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) as $targetCNAME) {
if (empty($targetCNAME)) {
continue;
}
$targetCNAMEs[] = new Domain($targetCNAME);
}
} elseif ($resourceType === 'site') {
if ($resourceType === 'site') {
// For example: appwrite.network
foreach (\explode(',', System::getEnv('_APP_DOMAIN_SITES', '')) as $targetCNAME) {
if (empty($targetCNAME)) {
@@ -146,7 +138,7 @@ class Action extends PlatformAction
}
$targetCNAMEs[] = new Domain($targetCNAME);
}
} elseif ($ruleType === 'api') {
} elseif ($resourceType === 'function' || $ruleType === 'api') {
// For example: fra.cloud.appwrite.io
$targetCNAMEs[] = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
} elseif ($ruleType === 'redirect') {
@@ -178,13 +170,17 @@ class Action extends PlatformAction
}
// Ensure at least one of CNAME/A/AAAA record points to our servers properly
$targetA = System::getEnv('_APP_DOMAIN_TARGET_A', '');
if ((new IP(IP::V4))->isValid($targetA)) {
$validator = new $dnsValidatorClass($targetA, Record::TYPE_A, $dnsServers);
$validators[] = $validator;
foreach (\explode(',', System::getEnv('_APP_DOMAIN_TARGET_A', '')) as $targetA) {
if (empty($targetA)) {
continue;
}
if ((new IP(IP::V4))->isValid($targetA)) {
$validator = new $dnsValidatorClass($targetA, Record::TYPE_A, $dnsServers);
$validators[] = $validator;
if (\is_null($mainValidator)) {
$mainValidator = $validator;
if (\is_null($mainValidator)) {
$mainValidator = $validator;
}
}
}
@@ -9,6 +9,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Utopia\Compression\Compression;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -17,7 +18,6 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Storage;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -14,6 +14,9 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Utopia\Compression\Algorithms\GZIP;
use Utopia\Compression\Algorithms\Zstd;
use Utopia\Compression\Compression;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
@@ -28,9 +31,6 @@ use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\Storage\Storage;
use Utopia\Storage\Validator\File;
@@ -11,6 +11,9 @@ use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Response;
use Utopia\Compression\Algorithms\GZIP;
use Utopia\Compression\Algorithms\Zstd;
use Utopia\Compression\Compression;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
@@ -19,9 +22,6 @@ use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\Text;
@@ -11,6 +11,9 @@ use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Response;
use Utopia\Compression\Algorithms\GZIP;
use Utopia\Compression\Algorithms\Zstd;
use Utopia\Compression\Compression;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Database;
@@ -23,9 +26,6 @@ use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Image\Image;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\HexColor;
@@ -8,6 +8,9 @@ use Appwrite\Extend\Exception;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Response;
use Utopia\Compression\Algorithms\GZIP;
use Utopia\Compression\Algorithms\Zstd;
use Utopia\Compression\Compression;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -16,9 +19,6 @@ use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\Text;
@@ -11,6 +11,9 @@ use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Response;
use Utopia\Compression\Algorithms\GZIP;
use Utopia\Compression\Algorithms\Zstd;
use Utopia\Compression\Compression;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -20,9 +23,6 @@ use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\System\System;
use Utopia\Validator\Text;
@@ -8,13 +8,13 @@ use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Compression\Compression;
use Utopia\Database\Database;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Validator\Permissions;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Storage;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
+4 -3
View File
@@ -63,10 +63,11 @@ class Doctor extends Action
}
$ipv4 = new IP(IP::V4);
if (!$ipv4->isValid(System::getEnv('_APP_DOMAIN_TARGET_A'))) {
Console::log('🔴 A record target is not valid (' . System::getEnv('_APP_DOMAIN_TARGET_A') . ')');
$targetA = \explode(',', System::getEnv('_APP_DOMAIN_TARGET_A', ''))[0];
if (!$ipv4->isValid($targetA)) {
Console::log('🔴 A record target is not valid (' . $targetA . ')');
} else {
Console::log('🟢 A record target is valid (' . System::getEnv('_APP_DOMAIN_TARGET_A') . ')');
Console::log('🟢 A record target is valid (' . $targetA . ')');
}
$ipv6 = new IP(IP::V6);
+11 -4
View File
@@ -77,11 +77,13 @@ class SDKs extends Action
->param('release', null, new Nullable(new WhiteList(['yes', 'no'])), 'Should we create releases?', optional: true)
->param('commit', null, new Nullable(new WhiteList(['yes', 'no'])), 'Actually create releases (yes) or dry-run (no)?', optional: true)
->param('sdks', null, new Nullable(new Text(256)), 'Selected SDKs', optional: true)
->param('mode', 'full', new WhiteList(['full', 'examples']), 'Generation mode: full (default) or examples (only generate and copy examples)', optional: true)
->callback($this->action(...));
}
public function action(?string $platform, ?string $sdk, ?string $version, ?string $git, ?string $message, ?string $release, ?string $commit, ?string $sdks): void
public function action(?string $platform, ?string $sdk, ?string $version, ?string $git, ?string $message, ?string $release, ?string $commit, ?string $sdks, string $mode): void
{
$examplesOnly = ($mode === 'examples');
$selectedPlatform = $platform;
$selectedSDK = $sdk;
@@ -99,7 +101,7 @@ class SDKs extends Action
$createRelease = ($release === 'yes');
$commitRelease = ($commit === 'yes');
if (!$createRelease) {
if (!$createRelease && !$examplesOnly) {
$git ??= Console::confirm('Should we use git push? (yes/no)');
$git = ($git === 'yes');
@@ -108,6 +110,9 @@ class SDKs extends Action
if ($git) {
$message ??= Console::confirm('Please enter your commit message:');
}
} elseif ($examplesOnly) {
$git = false;
$prUrls = [];
}
if (!\in_array($version, [
@@ -293,7 +298,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
throw new \Exception('Language "' . $language['key'] . '" not supported');
}
if ($createRelease) {
if ($createRelease && !$examplesOnly) {
$releaseVersion = $language['version'];
$repoName = $language['gitUserName'] . '/' . $language['gitRepoName'];
@@ -403,7 +408,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
continue;
}
Console::info("Generating {$language['name']} SDK...");
Console::info($examplesOnly
? "Generating examples for {$language['name']} SDK..."
: "Generating {$language['name']} SDK...");
$sdk = new SDK($config, new Swagger2($spec));
+125 -1
View File
@@ -23,6 +23,7 @@ use Utopia\Http\Request as UtopiaRequest;
use Utopia\Http\Response as UtopiaResponse;
use Utopia\Platform\Action;
use Utopia\System\System;
use Utopia\Validator\Nullable;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -34,6 +35,9 @@ class Specs extends Action
->desc('Generate Appwrite API specifications')
->param('version', 'latest', new Text(16), 'Spec version', true)
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
->param('git', null, new Nullable(new WhiteList(['yes', 'no'])), 'Should we push to the specs repo?', optional: true)
->param('message', null, new Nullable(new Text(256)), 'Commit Message', optional: true)
->param('branch', null, new Nullable(new Text(256)), 'Target branch for PR (defaults to main)', optional: true)
->callback($this->action(...));
}
@@ -230,8 +234,20 @@ class Specs extends Action
return $sdkPlatforms;
}
public function action(string $version, string $mode): void
public function action(string $version, string $mode, ?string $git, ?string $message, ?string $branch): void
{
if (\is_null($git)) {
$git = Console::confirm('Should we push specs to the appwrite/specs repo? (yes/no)');
}
if ($git === 'yes' && \is_null($message)) {
$message = Console::confirm('Please enter your commit message:');
}
if (\is_null($branch)) {
$branch = 'main';
}
$appRoutes = Http::getRoutes();
/** @var AppwriteResponse $response */
@@ -249,6 +265,8 @@ class Specs extends Action
$authCounts = $this->getAuthCounts();
$keys = $this->getKeys();
$generatedFiles = [];
foreach ($platforms as $platform) {
$routes = [];
$models = [];
@@ -371,6 +389,7 @@ class Specs extends Action
throw new Exception('Failed to save mocks spec file: ' . $path);
}
$generatedFiles[] = realpath($path);
Console::success('Saved mocks spec file: ' . realpath($path));
continue;
@@ -382,8 +401,113 @@ class Specs extends Action
throw new Exception('Failed to save spec file: ' . $path);
}
$generatedFiles[] = realpath($path);
Console::success('Saved spec file: ' . realpath($path));
}
}
if ($git === 'yes') {
$gitUrl = 'git@github.com:appwrite/specs.git';
$gitRepoName = 'appwrite/specs';
$gitBranch = 'feat-' . $version . '-specs';
$repoBranch = $branch;
$target = \realpath(__DIR__ . '/../../../../app') . '/sdks/git/specs/';
$examplesDir = \realpath(__DIR__ . '/../../../..') . '/docs/examples/' . $version;
Console::info("Cloning {$gitRepoName} into {$target}...");
\exec('rm -rf ' . $target . ' && \
mkdir -p ' . $target . ' && \
cd ' . $target . ' && \
git init && \
git config core.ignorecase false && \
git config pull.rebase false && \
git remote add origin ' . $gitUrl . ' && \
git fetch origin && \
(git checkout -f ' . $repoBranch . ' 2>/dev/null || git checkout -b ' . $repoBranch . ') && \
git pull origin ' . $repoBranch . ' && \
(git checkout -f ' . $gitBranch . ' 2>/dev/null || git checkout -b ' . $gitBranch . ') && \
(git fetch origin ' . $gitBranch . ' 2>/dev/null || git push -u origin ' . $gitBranch . ') && \
git reset --hard origin/' . $gitBranch . ' 2>/dev/null || true
');
// Copy generated spec files into specs/{version}/ subdirectory
$specsSubDir = $mocks ? 'mocks' : $version;
\exec('mkdir -p ' . \escapeshellarg("{$target}/specs/{$specsSubDir}"));
foreach ($generatedFiles as $file) {
$fileName = \basename($file);
\exec('cp ' . \escapeshellarg($file) . ' ' . \escapeshellarg("{$target}/specs/{$specsSubDir}/{$fileName}"));
Console::success("Copied spec file to repo: specs/{$specsSubDir}/{$fileName}");
}
// Regenerate SDK examples for this version
Console::info("Regenerating SDK examples for version {$version}...");
$sdksCommand = 'php ' . \escapeshellarg(\realpath(__DIR__ . '/../../../../app') . '/cli.php')
. ' sdks --platform=* --sdk=* --version=' . \escapeshellarg($version)
. ' --git=no --mode=examples 2>&1';
\exec($sdksCommand, $sdksOutput, $sdksReturnCode);
if ($sdksReturnCode !== 0) {
Console::warning("SDK examples generation returned non-zero exit code: {$sdksReturnCode}");
Console::warning(\implode("\n", $sdksOutput));
} else {
Console::success("Regenerated SDK examples for version {$version}");
}
// Copy SDK examples for this version
if (\is_dir($examplesDir)) {
\exec('mkdir -p ' . \escapeshellarg("{$target}/examples/{$version}") . ' && \
cp -r ' . \escapeshellarg($examplesDir) . '/. ' . \escapeshellarg("{$target}/examples/{$version}/"));
Console::success("Copied SDK examples for version {$version} to repo: examples/{$version}/");
} else {
Console::warning("No SDK examples found at: {$examplesDir}. Skipping examples copy.");
}
// Git add, commit, and push
\exec('cd ' . $target . ' && \
git add -A && \
git commit -m "' . \addslashes($message) . '" && \
git push -u origin ' . $gitBranch . '
');
Console::success("Pushed specs to {$gitRepoName} on branch {$gitBranch}");
// Create or update PR
$prTitle = "feat: API specs update for version {$version}";
$prBody = "This PR contains API specification updates and SDK examples for version {$version}.";
$prCommand = 'cd ' . $target . ' && \
gh pr create \
--repo "' . $gitRepoName . '" \
--title "' . $prTitle . '" \
--body "' . $prBody . '" \
--base "' . $repoBranch . '" \
--head "' . $gitBranch . '" \
2>&1';
$prUrl = '';
\exec($prCommand, $prOutput);
$prOutput = \implode("\n", $prOutput);
if (\str_contains($prOutput, 'already exists')) {
Console::warning("PR already exists for branch {$gitBranch}");
// Try to get the existing PR URL
\exec('cd ' . $target . ' && gh pr view --repo "' . $gitRepoName . '" --json url -q .url 2>&1', $existingPrOutput);
$prUrl = \trim(\implode("\n", $existingPrOutput));
} else {
$prUrl = \trim($prOutput);
}
// Clean up temp directory
\exec('chmod -R u+w ' . $target . ' && rm -rf ' . $target);
Console::success("Removed temp directory '{$target}'");
if (!empty($prUrl)) {
Console::log('');
Console::log('Pull Request Summary');
Console::log("Specs PR: {$prUrl}");
Console::log('');
}
}
}
}
+1 -1
View File
@@ -8,6 +8,7 @@ use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Template\Template;
use Utopia\Compression\Compression;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Database;
@@ -39,7 +40,6 @@ use Utopia\Migration\Sources\Supabase;
use Utopia\Migration\Transfer;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Storage\Compression\Compression;
use Utopia\Storage\Device;
use Utopia\System\System;
@@ -4,7 +4,7 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use Utopia\Storage\Compression\Compression;
use Utopia\Compression\Compression;
class Bucket extends Model
{
+1 -1
View File
@@ -4,8 +4,8 @@ namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use Utopia\Compression\Compression;
use Utopia\Database\Document;
use Utopia\Storage\Compression\Compression;
class File extends Model
{
@@ -283,6 +283,12 @@ class Project extends Model
'example' => ['vip'],
'array' => true,
])
->addRule('status', [
'type' => self::TYPE_STRING,
'description' => 'Project status.',
'default' => 'active',
'example' => 'active',
])
;
$services = Config::getParam('services', []);
@@ -57,6 +57,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals('Project Test', $response['body']['name']);
$this->assertEquals($team['body']['$id'], $response['body']['teamId']);
$this->assertEquals(PROJECT_STATUS_ACTIVE, $response['body']['status']);
$this->assertArrayHasKey('platforms', $response['body']);
$this->assertArrayHasKey('webhooks', $response['body']);
$this->assertArrayHasKey('keys', $response['body']);
@@ -76,6 +77,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals('Project Test', $response['body']['name']);
$this->assertEquals($team['body']['$id'], $response['body']['teamId']);
$this->assertEquals(PROJECT_STATUS_ACTIVE, $response['body']['status']);
$this->assertArrayHasKey('platforms', $response['body']);
$this->assertArrayHasKey('webhooks', $response['body']);
$this->assertArrayHasKey('keys', $response['body']);
+1 -1
View File
@@ -19,7 +19,7 @@ webapp.com {
template IN CNAME {
match "^stage-function\.webapp\.com\.$"
answer "{{ .Name }} 60 IN CNAME functions.localhost."
answer "{{ .Name }} 60 IN CNAME cname.localhost."
fallthrough
}