Improved backwards compatibility

This commit is contained in:
Matej Bačo
2026-03-27 15:34:34 +01:00
parent bb80e50d01
commit d9a75c5938
2 changed files with 72 additions and 10 deletions
@@ -10,6 +10,7 @@ use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
@@ -20,7 +21,12 @@ use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Hostname;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
/**
* WARNING: This kind of platform has most complex action, because it holds backwards compatibility too.
* If possible, refer to any other type of platform for APIs, for more simpler endpoint.
*/
class Create extends Base
{
use HTTP;
@@ -35,6 +41,7 @@ class Create extends Base
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/project/platforms/web')
->httpAlias('/v1/projects/:projectId/platforms')
->desc('Create project web platform')
->groups(['api', 'project'])
->label('scope', 'project.write')
@@ -58,7 +65,9 @@ class Create extends Base
))
->param('platformId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForPlatform'])
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
->param('hostname', '', new Hostname(), 'Platform web hostname. Max length: 256 chars.')
->param('hostname', '', new Hostname(), 'Platform web hostname. Max length: 256 chars.', true) // Optional for backwards compatibility
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true) // Exists for backwards compatibility
->inject('request')
->inject('response')
->inject('queueForEvents')
->inject('project')
@@ -71,12 +80,65 @@ class Create extends Base
string $platformId,
string $name,
string $hostname,
Request $request,
Response $response,
QueueEvent $queueForEvents,
Document $project,
Database $dbForPlatform,
Authorization $authorization,
) {
$type = Platform::TYPE_WEB;
$key = ''; // App platform attribute
// Backwards compatibility
// Used to have: type, name, key, hostname
if(!empty($request->getParam('type', ''))) {
// Validate deprecated type, and rename to new type
$deprecatedtypeMapping = [
// Web
'web' => Platform::TYPE_WEB,
'flutter-web' => Platform::TYPE_WEB,
'unity' => Platform::TYPE_WEB, // Was not officially supported anyway
// Apple
'flutter-macos' => Platform::TYPE_APPLE,
'flutter-ios' => Platform::TYPE_APPLE,
'react-native-ios' => Platform::TYPE_APPLE,
'apple-ios' => Platform::TYPE_APPLE,
'apple-macos' => Platform::TYPE_APPLE,
'apple-watchos' => Platform::TYPE_APPLE,
'apple-tvos' => Platform::TYPE_APPLE,
// Android
'flutter-android' => Platform::TYPE_ANDROID,
'android' => Platform::TYPE_ANDROID,
'react-native-android' => Platform::TYPE_ANDROID,
'flutter-windows' => Platform::TYPE_WINDOWS,
];
$typeValidator = new WhiteList(\array_keys($deprecatedtypeMapping));
if(!$typeValidator->isValid($request->getParam('type', ''))) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "type" is invalid: ' . $typeValidator->getDescription());
}
$type = $deprecatedtypeMapping[$request->getParam('type', '')] ?? Platform::TYPE_WEB;
// Validate deprecated app id (key)
if (!empty($request->getParam('key', ''))) {
$keyValidator = new Text(256);
if(!$keyValidator->isValid($request->getParam('key', ''))) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "key" is invalid: ' . $keyValidator->getDescription());
}
$key = $request->getParam('key', '');
}
} else {
// Modern request, validate hostname
if (empty($hostname)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "hostname" is not optional.');
}
}
$platformId = ($platformId == 'unique()') ? ID::unique() : $platformId;
$platform = new Document([
@@ -84,9 +146,9 @@ class Create extends Base
'$permissions' => [],
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'type' => Platform::TYPE_WEB,
'type' => $type,
'name' => $name,
'key' => '', // App platform attribute
'key' => $key,
'hostname' => $hostname
]);
@@ -3980,7 +3980,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals($platformFultteriOSId, $response['body']['$id']);
$this->assertEquals('flutter-ios', $response['body']['type']);
$this->assertEquals('apple', $response['body']['type']);
$this->assertEquals('Flutter App (iOS)', $response['body']['name']);
$this->assertEquals('com.example.ios', $response['body']['key']);
$this->assertEquals('', $response['body']['store']);
@@ -3997,7 +3997,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals($platformFultterAndroidId, $response['body']['$id']);
$this->assertEquals('flutter-android', $response['body']['type']);
$this->assertEquals('android', $response['body']['type']);
$this->assertEquals('Flutter App (Android)', $response['body']['name']);
$this->assertEquals('com.example.android', $response['body']['key']);
$this->assertEquals('', $response['body']['store']);
@@ -4014,7 +4014,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals($platformFultterWebId, $response['body']['$id']);
$this->assertEquals('flutter-web', $response['body']['type']);
$this->assertEquals('web', $response['body']['type']);
$this->assertEquals('Flutter App (Web)', $response['body']['name']);
$this->assertEquals('', $response['body']['key']);
$this->assertEquals('', $response['body']['store']);
@@ -4031,7 +4031,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals($platformAppleIosId, $response['body']['$id']);
$this->assertEquals('apple-ios', $response['body']['type']);
$this->assertEquals('apple', $response['body']['type']);
$this->assertEquals('iOS App', $response['body']['name']);
$this->assertEquals('com.example.ios', $response['body']['key']);
$this->assertEquals('', $response['body']['store']);
@@ -4048,7 +4048,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals($platformAppleMacOsId, $response['body']['$id']);
$this->assertEquals('apple-macos', $response['body']['type']);
$this->assertEquals('apple', $response['body']['type']);
$this->assertEquals('macOS App', $response['body']['name']);
$this->assertEquals('com.example.macos', $response['body']['key']);
$this->assertEquals('', $response['body']['store']);
@@ -4065,7 +4065,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals($platformAppleWatchOsId, $response['body']['$id']);
$this->assertEquals('apple-watchos', $response['body']['type']);
$this->assertEquals('apple', $response['body']['type']);
$this->assertEquals('watchOS App', $response['body']['name']);
$this->assertEquals('com.example.watchos', $response['body']['key']);
$this->assertEquals('', $response['body']['store']);
@@ -4082,7 +4082,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals($platformAppleTvOsId, $response['body']['$id']);
$this->assertEquals('apple-tvos', $response['body']['type']);
$this->assertEquals('apple', $response['body']['type']);
$this->assertEquals('tvOS App', $response['body']['name']);
$this->assertEquals('com.example.tvos', $response['body']['key']);
$this->assertEquals('', $response['body']['store']);