Resolve merge conflicts
|
|
@ -68,6 +68,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
|||
chmod +x /usr/local/bin/sdks && \
|
||||
chmod +x /usr/local/bin/specs && \
|
||||
chmod +x /usr/local/bin/ssl && \
|
||||
chmod +x /usr/local/bin/screenshot && \
|
||||
chmod +x /usr/local/bin/test && \
|
||||
chmod +x /usr/local/bin/upgrade && \
|
||||
chmod +x /usr/local/bin/vars && \
|
||||
|
|
|
|||
|
|
@ -121,7 +121,8 @@ return [
|
|||
'key' => 'template-for-onelink',
|
||||
'name' => 'Onelink template',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/template-for-onelink.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-onelink-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-onelink-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NUXT', [
|
||||
'providerRootDirectory' => './onelink',
|
||||
|
|
@ -140,7 +141,8 @@ return [
|
|||
'key' => 'starter-for-svelte',
|
||||
'name' => 'Svelte starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/starter-for-svelte.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-svelte-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/starter-for-svelte-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('SVELTEKIT', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -181,7 +183,8 @@ return [
|
|||
'key' => 'starter-for-react',
|
||||
'name' => 'React starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/starter-for-react.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/starter-for-react-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -222,7 +225,8 @@ return [
|
|||
'key' => 'starter-for-vue',
|
||||
'name' => 'Vue starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/starter-for-vue.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-vue-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/starter-for-vue-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('VUE', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -263,7 +267,8 @@ return [
|
|||
'key' => 'starter-for-react-native',
|
||||
'name' => 'React Native starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/starter-for-react-native.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/starter-for-react-native-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -305,7 +310,8 @@ return [
|
|||
'key' => 'starter-for-nextjs',
|
||||
'name' => 'Next.js starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/starter-for-nextjs.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-nextjs-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/starter-for-nextjs-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NEXTJS', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -346,7 +352,8 @@ return [
|
|||
'key' => 'starter-for-nuxt',
|
||||
'name' => 'Nuxt starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/starter-for-nuxt.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-nuxt-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/starter-for-nuxt-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NUXT', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -387,7 +394,8 @@ return [
|
|||
'key' => 'template-for-event',
|
||||
'name' => 'Event template',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/template-for-event.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-event-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-event-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NEXTJS', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -422,7 +430,8 @@ return [
|
|||
'key' => 'template-for-portfolio',
|
||||
'name' => 'Portfolio template',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/template-for-portfolio.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-portfolio-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-portfolio-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NEXTJS', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -438,7 +447,8 @@ return [
|
|||
'key' => 'template-for-store',
|
||||
'name' => 'Store template',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/template-for-store.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-store-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-store-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('SVELTEKIT', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -479,7 +489,8 @@ return [
|
|||
'key' => 'template-for-blog',
|
||||
'name' => 'Blog template',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => $url . '/console/images/sites/templates/template-for-blog.png',
|
||||
'screenshotDark' => $url . '/images/sites/templates/template-for-blog-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/template-for-blog-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('SVELTEKIT', [
|
||||
'providerRootDirectory' => './',
|
||||
|
|
@ -495,7 +506,8 @@ return [
|
|||
'key' => 'astro-starter',
|
||||
'name' => 'Astro starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => '',
|
||||
'screenshotDark' => $url . '/images/sites/templates/astro-starter-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/astro-starter-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('ASTRO', [
|
||||
'providerRootDirectory' => './astro/starter',
|
||||
|
|
@ -511,7 +523,8 @@ return [
|
|||
'key' => 'remix-starter',
|
||||
'name' => 'Remix starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => '',
|
||||
'screenshotDark' => $url . '/images/sites/templates/remix-starter-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/remix-starter-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REMIX', [
|
||||
'providerRootDirectory' => './remix/starter',
|
||||
|
|
@ -527,7 +540,8 @@ return [
|
|||
'key' => 'flutter-starter',
|
||||
'name' => 'Flutter starter',
|
||||
'useCases' => ['starter'],
|
||||
'demoImage' => '',
|
||||
'screenshotDark' => $url . '/images/sites/templates/flutter-starter-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/flutter-starter-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('FLUTTER', [
|
||||
'providerRootDirectory' => './flutter/starter',
|
||||
|
|
@ -543,6 +557,8 @@ return [
|
|||
'key' => 'nextjs-starter',
|
||||
'name' => 'Next.js starter website',
|
||||
'useCases' => ['starter'],
|
||||
'screenshotDark' => $url . '/images/sites/templates/nextjs-starter-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/nextjs-starter-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NEXTJS', [
|
||||
'providerRootDirectory' => './nextjs/starter',
|
||||
|
|
@ -558,6 +574,8 @@ return [
|
|||
'key' => 'nuxt-starter',
|
||||
'name' => 'Nuxt starter website',
|
||||
'useCases' => ['starter'],
|
||||
'screenshotDark' => $url . '/images/sites/templates/nuxt-starter-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/nuxt-starter-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NUXT', [
|
||||
'providerRootDirectory' => './nuxt/starter',
|
||||
|
|
@ -573,6 +591,8 @@ return [
|
|||
'key' => 'sveltekit-starter',
|
||||
'name' => 'SvelteKit starter website',
|
||||
'useCases' => ['starter'],
|
||||
'screenshotDark' => $url . '/images/sites/templates/sveltekit-starter-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/sveltekit-starter-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('SVELTEKIT', [
|
||||
'providerRootDirectory' => './sveltekit/starter',
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ use Utopia\Pools\Group;
|
|||
use Utopia\Swoole\Files;
|
||||
use Utopia\System\System;
|
||||
|
||||
Files::load(__DIR__.'/../public');
|
||||
|
||||
const DOMAIN_SYNC_TIMER = 30; // 30 seconds
|
||||
|
||||
$domains = new Table(1_000_000); // 1 million rows
|
||||
|
|
|
|||
|
|
@ -1977,6 +1977,7 @@ App::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return '';
|
||||
}, ['request', 'apiKey']);
|
||||
|
||||
|
|
|
|||
3
bin/screenshot
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php screenshot $@
|
||||
|
|
@ -953,7 +953,7 @@ services:
|
|||
|
||||
appwrite-browser:
|
||||
container_name: appwrite-browser
|
||||
image: appwrite/browser:0.2.0
|
||||
image: appwrite/browser:0.2.1
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
|
|
@ -962,7 +962,7 @@ services:
|
|||
hostname: exc1
|
||||
<<: *x-logging
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/executor:0.7.6
|
||||
image: openruntimes/executor:0.7.7
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
|
|
|
|||
BIN
public/images/sites/templates/astro-starter-dark.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
public/images/sites/templates/astro-starter-light.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
public/images/sites/templates/flutter-starter-dark.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/images/sites/templates/flutter-starter-light.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/images/sites/templates/nextjs-starter-dark.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/images/sites/templates/nextjs-starter-light.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
public/images/sites/templates/nuxt-starter-dark.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
public/images/sites/templates/nuxt-starter-light.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/images/sites/templates/remix-starter-dark.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
public/images/sites/templates/remix-starter-light.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
public/images/sites/templates/starter-for-nextjs-dark.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/images/sites/templates/starter-for-nextjs-light.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/images/sites/templates/starter-for-nuxt-dark.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
public/images/sites/templates/starter-for-nuxt-light.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
public/images/sites/templates/starter-for-react-dark.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/images/sites/templates/starter-for-react-light.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/images/sites/templates/starter-for-svelte-dark.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/images/sites/templates/starter-for-svelte-light.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/images/sites/templates/starter-for-vue-dark.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/images/sites/templates/starter-for-vue-light.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
public/images/sites/templates/sveltekit-starter-dark.png
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
public/images/sites/templates/sveltekit-starter-light.png
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
public/images/sites/templates/template-for-blog-dark.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
public/images/sites/templates/template-for-blog-light.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
public/images/sites/templates/template-for-event-dark.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/images/sites/templates/template-for-event-light.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/images/sites/templates/template-for-onelink-dark.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/sites/templates/template-for-onelink-light.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/sites/templates/template-for-portfolio-dark.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/images/sites/templates/template-for-portfolio-light.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/images/sites/templates/template-for-store-dark.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
public/images/sites/templates/template-for-store-light.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
|
|
@ -1,112 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Builds;
|
||||
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createDeploymentBuild';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->httpAlias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
|
||||
->desc('Rebuild deployment')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'createBuild',
|
||||
description: <<<EOT
|
||||
Create a new build for an existing function deployment. This endpoint allows you to rebuild a deployment with the updated function configuration, including its entrypoint and build commands if they have been modified. The build process will be queued and executed asynchronously. The original deployment's code will be preserved and used for the new build.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->param('buildId', '', new UID(), 'Build unique ID.', true) // added as optional param for backward compatibility
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('deviceForFunctions')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, string $deploymentId, string $buildId, Response $response, Database $dbForProject, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path');
|
||||
if (empty($path) || !$deviceForFunctions->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deploymentId = ID::unique();
|
||||
|
||||
$destination = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
||||
$deviceForFunctions->transfer($path, $destination, $deviceForFunctions);
|
||||
|
||||
$deployment->removeAttribute('$internalId');
|
||||
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
|
||||
'$internalId' => '',
|
||||
'$id' => $deploymentId,
|
||||
'buildId' => '',
|
||||
'buildInternalId' => '',
|
||||
'path' => $destination,
|
||||
'entrypoint' => $function->getAttribute('entrypoint'),
|
||||
'commands' => $function->getAttribute('commands', ''),
|
||||
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
|
||||
]));
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
->setResource($function)
|
||||
->setDeployment($deployment);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Builds\Download;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Swoole\Request;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getDeploymentBuildDownload';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId/build/download')
|
||||
->desc('Download build')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'getDeploymentBuildDownload',
|
||||
description: <<<EOT
|
||||
Get a function build content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.
|
||||
EOT,
|
||||
auth: [AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE
|
||||
)
|
||||
],
|
||||
type: MethodType::LOCATION,
|
||||
contentType: ContentType::ANY,
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->inject('deviceForBuilds')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForBuilds)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId'));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $build->getAttribute('path', '');
|
||||
if (!$deviceForBuilds->exists($path)) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response
|
||||
->setContentType('application/gzip')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
|
||||
$size = $deviceForBuilds->getFileSize($path);
|
||||
$rangeHeader = $request->getHeader('range');
|
||||
|
||||
if (!empty($rangeHeader)) {
|
||||
$start = $request->getRangeStart();
|
||||
$end = $request->getRangeEnd();
|
||||
$unit = $request->getRangeUnit();
|
||||
|
||||
if ($end === null) {
|
||||
$end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1));
|
||||
}
|
||||
|
||||
if ($unit !== 'bytes' || $start >= $end || $end >= $size) {
|
||||
throw new Exception(Exception::STORAGE_INVALID_RANGE);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Accept-Ranges', 'bytes')
|
||||
->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size)
|
||||
->addHeader('Content-Length', $end - $start + 1)
|
||||
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
|
||||
|
||||
$response->send($deviceForBuilds->read($path, $start, ($end - $start + 1)));
|
||||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceForBuilds->read(
|
||||
$path,
|
||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||
),
|
||||
(($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$response->send($deviceForBuilds->read($path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Builds;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateDeploymentBuild';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->desc('Cancel deployment')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'updateDeploymentBuild',
|
||||
description: <<<EOT
|
||||
Cancel an ongoing function deployment build. If the build is already in progress, it will be stopped and marked as canceled. If the build hasn't started yet, it will be marked as canceled without executing. You cannot cancel builds that have already completed (status 'ready') or failed. The response includes the final build status and details.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_BUILD,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
$buildId = ID::unique();
|
||||
$build = $dbForProject->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$permissions' => [],
|
||||
'startTime' => DateTime::now(),
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'canceled',
|
||||
'path' => '',
|
||||
'runtime' => $function->getAttribute('runtime'),
|
||||
'source' => $deployment->getAttribute('path', ''),
|
||||
'sourceType' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0,
|
||||
'size' => 0
|
||||
]));
|
||||
|
||||
$deployment->setAttribute('buildId', $build->getId());
|
||||
$deployment->setAttribute('buildInternalId', $build->getInternalId());
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
} else {
|
||||
if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) {
|
||||
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
|
||||
}
|
||||
|
||||
$startTime = new \DateTime($build->getAttribute('startTime'));
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([
|
||||
'endTime' => DateTime::now(),
|
||||
'duration' => $duration,
|
||||
'status' => 'canceled'
|
||||
]));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
|
||||
|
||||
try {
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
$executor->deleteRuntime($project->getId(), $deploymentId . "-build");
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->dynamic($build, Response::MODEL_BUILD);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ use Utopia\Platform\Action;
|
|||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Swoole\Request;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
|
|
@ -30,6 +31,7 @@ class Get extends Action
|
|||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
->httpAlias('/v1/functions/:functionId/deployments/:deploymentId/build/download', [ 'type' => 'output' ])
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Download deployment')
|
||||
->label('scope', 'functions.read')
|
||||
|
|
@ -52,14 +54,16 @@ class Get extends Action
|
|||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->param('type', 'source', new WhiteList(['source', 'output']), 'Deployment file to download. Can be: "source", "output".', true)
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->inject('deviceForFunctions')
|
||||
->inject('deviceForBuilds')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForFunctions)
|
||||
public function action(string $functionId, string $deploymentId, string $type, Response $response, Request $request, Database $dbForProject, Device $deviceForFunctions, Device $deviceForBuilds)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
if ($function->isEmpty()) {
|
||||
|
|
@ -75,8 +79,23 @@ class Get extends Action
|
|||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path', '');
|
||||
if (!$deviceForFunctions->exists($path)) {
|
||||
switch ($type) {
|
||||
case 'output':
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId'));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $build->getAttribute('path', '');
|
||||
$device = $deviceForBuilds;
|
||||
break;
|
||||
case 'source':
|
||||
$path = $deployment->getAttribute('path', '');
|
||||
$device = $deviceForFunctions;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$device->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +105,7 @@ class Get extends Action
|
|||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
|
||||
$size = $deviceForFunctions->getFileSize($path);
|
||||
$size = $device->getFileSize($path);
|
||||
$rangeHeader = $request->getHeader('range');
|
||||
|
||||
if (!empty($rangeHeader)) {
|
||||
|
|
@ -108,13 +127,13 @@ class Get extends Action
|
|||
->addHeader('Content-Length', $end - $start + 1)
|
||||
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
|
||||
|
||||
$response->send($deviceForFunctions->read($path, $start, ($end - $start + 1)));
|
||||
$response->send($device->read($path, $start, ($end - $start + 1)));
|
||||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceForFunctions->read(
|
||||
$device->read(
|
||||
$path,
|
||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||
|
|
@ -123,7 +142,7 @@ class Get extends Action
|
|||
);
|
||||
}
|
||||
} else {
|
||||
$response->send($deviceForFunctions->read($path));
|
||||
$response->send($device->read($path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
namespace Appwrite\Platform\Modules\Functions\Services;
|
||||
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Builds\Create as CreateBuild;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Builds\Download\Get as DownloadBuild;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Builds\Update as UpdateBuild;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Create as CreateDeployment;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Delete as DeleteDeployment;
|
||||
|
|
@ -65,7 +64,6 @@ class Http extends Service
|
|||
$this->addAction(DownloadDeployment::getName(), new DownloadDeployment());
|
||||
$this->addAction(CreateBuild::getName(), new CreateBuild());
|
||||
$this->addAction(UpdateBuild::getName(), new UpdateBuild());
|
||||
$this->addAction(DownloadBuild::getName(), new DownloadBuild());
|
||||
|
||||
// Executions
|
||||
$this->addAction(CreateExecution::getName(), new CreateExecution());
|
||||
|
|
|
|||
|
|
@ -740,6 +740,7 @@ class Builds extends Action
|
|||
}
|
||||
|
||||
$client = new FetchClient();
|
||||
$client->setTimeout(\intval($resource->getAttribute('timeout', '15')));
|
||||
$client->addHeader('content-type', FetchClient::CONTENT_TYPE_APPLICATION_JSON);
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Builds;
|
||||
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createDeploymentBuild';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/build')
|
||||
->desc('Rebuild deployment')
|
||||
->groups(['api', 'sites'])
|
||||
->label('scope', 'sites.write')
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('event', 'sites.[siteId].deployments.[deploymentId].update')
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
name: 'createDeploymentBuild',
|
||||
description: <<<EOT
|
||||
Create a new build for an existing site deployment. This endpoint allows you to rebuild a deployment with the updated site configuration, including its commands and output directory if they have been modified. The build process will be queued and executed asynchronously. The original deployment's code will be preserved and used for the new build.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('siteId', '', new UID(), 'Site ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('deviceForSites')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents, Build $queueForBuilds, Device $deviceForSites)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
|
||||
if ($site->isEmpty()) {
|
||||
throw new Exception(Exception::SITE_NOT_FOUND);
|
||||
}
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path');
|
||||
if (empty($path) || !$deviceForSites->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deploymentId = ID::unique();
|
||||
|
||||
$destination = $deviceForSites->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
||||
$deviceForSites->transfer($path, $destination, $deviceForSites);
|
||||
|
||||
$deployment->removeAttribute('$internalId');
|
||||
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
|
||||
'$internalId' => '',
|
||||
'$id' => $deploymentId,
|
||||
'buildId' => '',
|
||||
'buildInternalId' => '',
|
||||
'path' => $destination,
|
||||
'buildCommand' => $site->getAttribute('buildCommand', ''),
|
||||
'installCommand' => $site->getAttribute('installCommand', ''),
|
||||
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
|
||||
'search' => implode(' ', [$deploymentId]),
|
||||
'screenshotLight' => '',
|
||||
'screenshotDark' => ''
|
||||
]));
|
||||
|
||||
// Preview deployments for sites
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
'domain' => $domain,
|
||||
'type' => 'deployment',
|
||||
'value' => $deployment->getId(),
|
||||
'status' => 'verified',
|
||||
'certificateId' => '',
|
||||
'search' => implode(' ', [$ruleId, $domain]),
|
||||
]))
|
||||
);
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
->setResource($site)
|
||||
->setDeployment($deployment);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('siteId', $site->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Builds\Download;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Swoole\Request;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getDeploymentBuildDownload';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/build/download')
|
||||
->desc('Download build')
|
||||
->groups(['api', 'sites'])
|
||||
->label('scope', 'sites.read')
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
name: 'getDeploymentBuildDownload',
|
||||
description: <<<EOT
|
||||
Get a site build content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.
|
||||
EOT,
|
||||
auth: [AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE
|
||||
)
|
||||
],
|
||||
type: MethodType::LOCATION,
|
||||
contentType: ContentType::ANY,
|
||||
))
|
||||
->param('siteId', '', new UID(), 'Site ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->inject('deviceForBuilds')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForBuilds)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
if ($site->isEmpty()) {
|
||||
throw new Exception(Exception::SITE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $site->getId()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId'));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $build->getAttribute('path', '');
|
||||
if (!$deviceForBuilds->exists($path)) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response
|
||||
->setContentType('application/gzip')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
|
||||
$size = $deviceForBuilds->getFileSize($path);
|
||||
$rangeHeader = $request->getHeader('range');
|
||||
|
||||
if (!empty($rangeHeader)) {
|
||||
$start = $request->getRangeStart();
|
||||
$end = $request->getRangeEnd();
|
||||
$unit = $request->getRangeUnit();
|
||||
|
||||
if ($end === null) {
|
||||
$end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1));
|
||||
}
|
||||
|
||||
if ($unit !== 'bytes' || $start >= $end || $end >= $size) {
|
||||
throw new Exception(Exception::STORAGE_INVALID_RANGE);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Accept-Ranges', 'bytes')
|
||||
->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size)
|
||||
->addHeader('Content-Length', $end - $start + 1)
|
||||
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
|
||||
|
||||
$response->send($deviceForBuilds->read($path, $start, ($end - $start + 1)));
|
||||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceForBuilds->read(
|
||||
$path,
|
||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||
),
|
||||
(($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$response->send($deviceForBuilds->read($path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Sites\Http\Deployments\Builds;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateDeploymentBuild';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/build')
|
||||
->desc('Cancel deployment')
|
||||
->groups(['api', 'sites'])
|
||||
->label('scope', 'sites.write')
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'site/{request.siteId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
name: 'updateDeploymentBuild',
|
||||
description: <<<EOT
|
||||
Cancel an ongoing site deployment build. If the build is already in progress, it will be stopped and marked as canceled. If the build hasn't started yet, it will be marked as canceled without executing. You cannot cancel builds that have already completed (status 'ready') or failed. The response includes the final build status and details.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_BUILD,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('siteId', '', new UID(), 'Site ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
|
||||
if ($site->isEmpty()) {
|
||||
throw new Exception(Exception::SITE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
$buildId = ID::unique();
|
||||
$build = $dbForProject->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$permissions' => [],
|
||||
'startTime' => DateTime::now(),
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'canceled',
|
||||
'path' => '',
|
||||
'runtime' => $site->getAttribute('framework'),
|
||||
'source' => $deployment->getAttribute('path', ''),
|
||||
'sourceType' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0,
|
||||
'size' => 0
|
||||
]));
|
||||
|
||||
$deployment->setAttribute('buildId', $build->getId());
|
||||
$deployment->setAttribute('buildInternalId', $build->getInternalId());
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
} else {
|
||||
if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) {
|
||||
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
|
||||
}
|
||||
|
||||
$startTime = new \DateTime($build->getAttribute('startTime'));
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([
|
||||
'endTime' => DateTime::now(),
|
||||
'duration' => $duration,
|
||||
'status' => 'canceled'
|
||||
]));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
|
||||
|
||||
try {
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
$executor->deleteRuntime($project->getId(), $deploymentId . "-build");
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('siteId', $site->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->dynamic($build, Response::MODEL_BUILD);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ use Utopia\Platform\Action;
|
|||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Swoole\Request;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
|
|
@ -30,6 +31,7 @@ class Get extends Action
|
|||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/download')
|
||||
->httpAlias('/v1/sites/:functionId/deployments/:deploymentId/build/download', [ 'type' => 'output' ])
|
||||
->desc('Download deployment')
|
||||
->groups(['api', 'sites'])
|
||||
->label('scope', 'sites.read')
|
||||
|
|
@ -52,14 +54,16 @@ class Get extends Action
|
|||
))
|
||||
->param('siteId', '', new UID(), 'Site ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->param('type', 'source', new WhiteList(['source', 'output']), 'Deployment file to download. Can be: "source", "output".', true)
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->inject('deviceForSites')
|
||||
->inject('deviceForBuilds')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForSites)
|
||||
public function action(string $siteId, string $deploymentId, string $type, Response $response, Request $request, Database $dbForProject, Device $deviceForSites, Device $deviceForBuilds)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
if ($site->isEmpty()) {
|
||||
|
|
@ -75,18 +79,33 @@ class Get extends Action
|
|||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path', '');
|
||||
if (!$deviceForSites->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
switch ($type) {
|
||||
case 'output':
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId'));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $build->getAttribute('path', '');
|
||||
$device = $deviceForBuilds;
|
||||
break;
|
||||
case 'source':
|
||||
$path = $deployment->getAttribute('path', '');
|
||||
$device = $deviceForSites;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$device->exists($path)) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response
|
||||
->setContentType('application/gzip')
|
||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '-' . $type . '.tar.gz"');
|
||||
|
||||
$size = $deviceForSites->getFileSize($path);
|
||||
$size = $device->getFileSize($path);
|
||||
$rangeHeader = $request->getHeader('range');
|
||||
|
||||
if (!empty($rangeHeader)) {
|
||||
|
|
@ -108,13 +127,13 @@ class Get extends Action
|
|||
->addHeader('Content-Length', $end - $start + 1)
|
||||
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
|
||||
|
||||
$response->send($deviceForSites->read($path, $start, ($end - $start + 1)));
|
||||
$response->send($device->read($path, $start, ($end - $start + 1)));
|
||||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceForSites->read(
|
||||
$device->read(
|
||||
$path,
|
||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||
|
|
@ -123,7 +142,7 @@ class Get extends Action
|
|||
);
|
||||
}
|
||||
} else {
|
||||
$response->send($deviceForSites->read($path));
|
||||
$response->send($device->read($path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
namespace Appwrite\Platform\Modules\Sites\Services;
|
||||
|
||||
use Appwrite\Platform\Modules\Sites\Http\Deployments\Builds\Create as CreateBuild;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Deployments\Builds\Download\Get as DownloadBuild;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Deployments\Builds\Update as UpdateBuild;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Deployments\Create as CreateDeployment;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Deployments\Delete as DeleteDeployment;
|
||||
|
|
@ -58,7 +57,6 @@ class Http extends Service
|
|||
$this->addAction(UpdateDeployment::getName(), new UpdateDeployment());
|
||||
$this->addAction(DeleteDeployment::getName(), new DeleteDeployment());
|
||||
$this->addAction(DownloadDeployment::getName(), new DownloadDeployment());
|
||||
$this->addAction(DownloadBuild::getName(), new DownloadBuild());
|
||||
$this->addAction(CreateBuild::getName(), new CreateBuild());
|
||||
$this->addAction(UpdateBuild::getName(), new UpdateBuild());
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use Appwrite\Platform\Tasks\QueueRetry;
|
|||
use Appwrite\Platform\Tasks\ScheduleExecutions;
|
||||
use Appwrite\Platform\Tasks\ScheduleFunctions;
|
||||
use Appwrite\Platform\Tasks\ScheduleMessages;
|
||||
use Appwrite\Platform\Tasks\Screenshot;
|
||||
use Appwrite\Platform\Tasks\SDKs;
|
||||
use Appwrite\Platform\Tasks\Specs;
|
||||
use Appwrite\Platform\Tasks\SSL;
|
||||
|
|
@ -32,6 +33,7 @@ class Tasks extends Service
|
|||
->addAction(QueueRetry::getName(), new QueueRetry())
|
||||
->addAction(SDKs::getName(), new SDKs())
|
||||
->addAction(SSL::getName(), new SSL())
|
||||
->addAction(Screenshot::getName(), new Screenshot())
|
||||
->addAction(ScheduleFunctions::getName(), new ScheduleFunctions())
|
||||
->addAction(ScheduleExecutions::getName(), new ScheduleExecutions())
|
||||
->addAction(ScheduleMessages::getName(), new ScheduleMessages())
|
||||
|
|
|
|||
300
src/Appwrite/Platform/Tasks/Screenshot.php
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\ID;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Screenshot extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'screenshot';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Create Site template screenshot')
|
||||
->param('templateId', '', new Text(128), 'Template ID.')
|
||||
->callback(fn (string $templateId) => $this->action($templateId));
|
||||
}
|
||||
|
||||
public function action(string $templateId): void
|
||||
{
|
||||
$templates = Config::getParam('site-templates', []);
|
||||
|
||||
$allowedTemplates = \array_filter($templates, function ($item) use ($templateId) {
|
||||
return $item['key'] === $templateId;
|
||||
});
|
||||
$template = \array_shift($allowedTemplates);
|
||||
|
||||
if (empty($template)) {
|
||||
throw new \Exception("Template {$templateId} not found. Find correct ID in app/config/site-templates.php");
|
||||
}
|
||||
|
||||
Console::info("Found: " . $template['name']);
|
||||
|
||||
$client = new Client();
|
||||
$client->setEndpoint('http://localhost/v1');
|
||||
$client->addHeader('origin', 'http://localhost');
|
||||
|
||||
// Register
|
||||
$email = uniqid() . 'user@localhost.test';
|
||||
$password = 'password';
|
||||
$user = $client->call(Client::METHOD_POST, '/account', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
if ($user['headers']['status-code'] !== 201) {
|
||||
Console::error(\json_encode($user));
|
||||
throw new \Exception("Failed to register user");
|
||||
}
|
||||
|
||||
Console::info("User created");
|
||||
|
||||
// Login
|
||||
$session = $client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
if ($session['headers']['status-code'] !== 201) {
|
||||
Console::error(\json_encode($session));
|
||||
throw new \Exception("Failed to login user");
|
||||
}
|
||||
|
||||
Console::info("Session created");
|
||||
|
||||
$secret = $session['cookies']['a_session_console'];
|
||||
$cookieConsole = 'a_session_console=' . $secret;
|
||||
|
||||
// Create organization
|
||||
$team = $client->call(Client::METHOD_POST, '/teams', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => $cookieConsole
|
||||
], [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Demo Project Team',
|
||||
]);
|
||||
|
||||
if ($team['headers']['status-code'] !== 201) {
|
||||
Console::error(\json_encode($team));
|
||||
throw new \Exception("Failed to create team");
|
||||
}
|
||||
|
||||
Console::info("Team created");
|
||||
|
||||
$projectName = 'Demo Project';
|
||||
$projectId = ID::unique();
|
||||
|
||||
// Create project
|
||||
$project = $client->call(Client::METHOD_POST, '/projects', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => $cookieConsole
|
||||
], [
|
||||
'projectId' => $projectId,
|
||||
'region' => 'default',
|
||||
'name' => $projectName,
|
||||
'teamId' => $team['body']['$id'],
|
||||
'description' => 'Demo Project Description',
|
||||
'url' => 'https://appwrite.io',
|
||||
]);
|
||||
|
||||
if ($project['headers']['status-code'] !== 201) {
|
||||
Console::error(\json_encode($project));
|
||||
throw new \Exception("Failed to create project");
|
||||
}
|
||||
|
||||
Console::info("Project created");
|
||||
|
||||
$projectId = $project['body']['$id'];
|
||||
|
||||
$framework = $template['frameworks'][0];
|
||||
|
||||
// Create site
|
||||
$site = $client->call(Client::METHOD_POST, '/sites', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-mode' => 'admin',
|
||||
'cookie' => $cookieConsole
|
||||
], [
|
||||
'siteId' => ID::unique(),
|
||||
'name' => $template["name"],
|
||||
'framework' => $framework['key'],
|
||||
'adapter' => $framework['adapter'],
|
||||
'buildCommand' => $framework['buildCommand'],
|
||||
'buildRuntime' => $framework['buildRuntime'],
|
||||
'fallbackFile' => $framework['fallbackFile'],
|
||||
'installCommand' => $framework['installCommand'],
|
||||
'outputDirectory' => $framework['outputDirectory'],
|
||||
'providerRootDirectory' => $framework['providerRootDirectory'],
|
||||
'timeout' => 60
|
||||
]);
|
||||
|
||||
if ($site['headers']['status-code'] !== 201) {
|
||||
Console::error(\json_encode($site));
|
||||
throw new \Exception("Failed to create site");
|
||||
}
|
||||
|
||||
Console::info("Site created");
|
||||
|
||||
$siteId = $site['body']['$id'];
|
||||
|
||||
// Create variables
|
||||
if (!empty($template['variables'] ?? [])) {
|
||||
foreach ($template['variables'] as $variable) {
|
||||
if (empty($variable['value'] ?? '')) {
|
||||
if (($variable['required'] ?? false) === true) {
|
||||
throw new \Exception("Missing required variable: {$variable['name']}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $variable['value'];
|
||||
$value = \str_replace('{projectName}', $projectName, $value);
|
||||
$value = \str_replace('{projectId}', $projectId, $value);
|
||||
$value = \str_replace('{apiEndpoint}', 'http://localhost/v1', $value);
|
||||
|
||||
$response = $client->call(Client::METHOD_POST, '/sites/' . $siteId . '/variables', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-mode' => 'admin',
|
||||
'cookie' => $cookieConsole
|
||||
], [
|
||||
'key' => $variable['name'],
|
||||
'value' => $value
|
||||
]);
|
||||
|
||||
if ($response['headers']['status-code'] !== 201) {
|
||||
Console::error(\json_encode($response));
|
||||
throw new \Exception("Failed to create variable");
|
||||
}
|
||||
}
|
||||
|
||||
Console::info("Variables created");
|
||||
}
|
||||
|
||||
// Create deployment
|
||||
$deployment = $client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments/template', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-mode' => 'admin',
|
||||
'cookie' => $cookieConsole
|
||||
], [
|
||||
'owner' => $template['providerOwner'],
|
||||
'repository' => $template['providerRepositoryId'],
|
||||
'rootDirectory' => $framework['providerRootDirectory'],
|
||||
'version' => $template['providerVersion'],
|
||||
'activate' => true,
|
||||
]);
|
||||
|
||||
if ($deployment['headers']['status-code'] !== 202) {
|
||||
Console::error(\json_encode($deployment));
|
||||
throw new \Exception("Failed to create deployment");
|
||||
}
|
||||
|
||||
Console::info("Deployment created");
|
||||
|
||||
$deploymentId = $deployment['body']['$id'];
|
||||
|
||||
// Await screenshot
|
||||
$attempts = 50;
|
||||
$sleep = 5;
|
||||
|
||||
$idLight = '';
|
||||
$idDark = '';
|
||||
|
||||
Console::log("Awaiting deployment (every $sleep seconds, $attempts attempts)");
|
||||
|
||||
for ($i = 0; $i < $attempts; $i++) {
|
||||
$deployment = $client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments/' . $deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-mode' => 'admin',
|
||||
'cookie' => $cookieConsole
|
||||
]);
|
||||
|
||||
if ($deployment['headers']['status-code'] !== 200) {
|
||||
Console::error(\json_encode($deployment));
|
||||
throw new \Exception("Failed to get deployment");
|
||||
}
|
||||
|
||||
if ($deployment['body']['status'] === 'failed') {
|
||||
Console::error(\json_encode($deployment));
|
||||
throw new \Exception("Deployment build failed");
|
||||
}
|
||||
|
||||
if ($deployment['body']['status'] !== 'ready') {
|
||||
Console::log("Deployment not ready yet, status: " . $deployment['body']['status']);
|
||||
\sleep($sleep);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (empty($deployment['body']['screenshotLight'])) {
|
||||
Console::log("Light screenshot not available yet");
|
||||
\sleep($sleep);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($deployment['body']['screenshotDark'])) {
|
||||
Console::log("Dark screenshot not available yet");
|
||||
\sleep($sleep);
|
||||
continue;
|
||||
}
|
||||
|
||||
$idLight = $deployment['body']['screenshotLight'];
|
||||
$idDark = $deployment['body']['screenshotDark'];
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($idLight) || empty($idDark)) {
|
||||
Console::error(\json_encode($deployment));
|
||||
throw new \Exception("Failed to get deployment screenshot");
|
||||
}
|
||||
|
||||
Console::info("Screenshots created");
|
||||
|
||||
$themes = [
|
||||
[ 'fileId' => $idLight, 'suffix' => 'light' ],
|
||||
[ 'fileId' => $idDark, 'suffix' => 'dark' ]
|
||||
];
|
||||
|
||||
foreach ($themes as $theme) {
|
||||
$file = $client->call(Client::METHOD_GET, '/storage/buckets/screenshots/files/' . $theme['fileId'] . '/download', [
|
||||
'x-appwrite-project' => 'console',
|
||||
'x-appwrite-mode' => 'admin',
|
||||
'cookie' => $cookieConsole
|
||||
]);
|
||||
|
||||
if ($file['headers']['status-code'] !== 200) {
|
||||
Console::error(\json_encode($file));
|
||||
throw new \Exception("Failed to download {$theme['suffix']} screenshot");
|
||||
}
|
||||
|
||||
$path = "/usr/src/code/public/images/sites/templates/{$template['key']}-{$theme['suffix']}.png";
|
||||
|
||||
if (!\file_put_contents($path, $file['body'])) {
|
||||
throw new \Exception("Failed to save {$theme['suffix']} screenshot");
|
||||
}
|
||||
}
|
||||
|
||||
Console::success("Screenshots saved");
|
||||
}
|
||||
}
|
||||
|
|
@ -210,8 +210,24 @@ abstract class Format
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case 'functions':
|
||||
switch ($method) {
|
||||
case 'getDeploymentDownload':
|
||||
switch ($param) {
|
||||
case 'type':
|
||||
return 'DeploymentDownloadType';
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'sites':
|
||||
switch ($method) {
|
||||
case 'getDeploymentDownload':
|
||||
switch ($param) {
|
||||
case 'type':
|
||||
return 'DeploymentDownloadType';
|
||||
}
|
||||
break;
|
||||
case 'getUsage':
|
||||
case 'listUsage':
|
||||
switch ($param) {
|
||||
|
|
|
|||
|
|
@ -28,11 +28,17 @@ class TemplateSite extends Model
|
|||
'default' => '',
|
||||
'example' => 'https://nextjs-starter.appwrite.network/',
|
||||
])
|
||||
->addRule('demoImage', [
|
||||
->addRule('screenshotDark', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File URL with preview screenshot.',
|
||||
'description' => 'File URL with preview screenshot in dark theme preference.',
|
||||
'default' => '',
|
||||
'example' => 'https://cloud.appwrite.io/console/images/sites/templates/nextjs-starter.png',
|
||||
'example' => 'https://cloud.appwrite.io/images/sites/templates/template-for-blog-dark.png',
|
||||
])
|
||||
->addRule('screenshotLight', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'File URL with preview screenshot in light theme preference.',
|
||||
'default' => '',
|
||||
'example' => 'https://cloud.appwrite.io/images/sites/templates/template-for-blog-light.png',
|
||||
])
|
||||
->addRule('useCases', [
|
||||
'type' => self::TYPE_STRING,
|
||||
|
|
|
|||
|
|
@ -313,22 +313,14 @@ trait FunctionsBase
|
|||
return $domain;
|
||||
}
|
||||
|
||||
protected function getDeploymentDownload(string $functionId, string $deploymentId): mixed
|
||||
protected function getDeploymentDownload(string $functionId, string $deploymentId, string $type): mixed
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId . '/download', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function getBuildDownload(string $functionId, string $deploymentId): mixed
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId . '/build/download', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
], $this->getHeaders()), [
|
||||
'type' => $type
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -517,7 +517,7 @@ class FunctionsConsoleClientTest extends Scope
|
|||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$response = $this->getDeploymentDownload($functionId, $deploymentId);
|
||||
$response = $this->getDeploymentDownload($functionId, $deploymentId, 'source');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('application/gzip', $response['headers']['content-type']);
|
||||
$this->assertGreaterThan(0, $response['headers']['content-length']);
|
||||
|
|
@ -525,7 +525,7 @@ class FunctionsConsoleClientTest extends Scope
|
|||
|
||||
$deploymentMd5 = \md5($response['body']);
|
||||
|
||||
$response = $this->getBuildDownload($functionId, $deploymentId);
|
||||
$response = $this->getDeploymentDownload($functionId, $deploymentId, 'output');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('application/gzip', $response['headers']['content-type']);
|
||||
$this->assertGreaterThan(0, $response['headers']['content-length']);
|
||||
|
|
|
|||
|
|
@ -348,22 +348,14 @@ trait SitesBase
|
|||
return $domain;
|
||||
}
|
||||
|
||||
protected function getDeploymentDownload(string $siteId, string $deploymentId): mixed
|
||||
protected function getDeploymentDownload(string $siteId, string $deploymentId, string $type): mixed
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments/' . $deploymentId . '/download', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function getBuildDownload(string $siteId, string $deploymentId): mixed
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments/' . $deploymentId . '/build/download', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
], $this->getHeaders()), [
|
||||
'type' => $type,
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1657,7 +1657,7 @@ class SitesCustomServerTest extends Scope
|
|||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$response = $this->getDeploymentDownload($siteId, $deploymentId);
|
||||
$response = $this->getDeploymentDownload($siteId, $deploymentId, 'source');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('application/gzip', $response['headers']['content-type']);
|
||||
$this->assertGreaterThan(0, $response['headers']['content-length']);
|
||||
|
|
@ -1665,7 +1665,7 @@ class SitesCustomServerTest extends Scope
|
|||
|
||||
$deploymentMd5 = \md5($response['body']);
|
||||
|
||||
$response = $this->getBuildDownload($siteId, $deploymentId);
|
||||
$response = $this->getDeploymentDownload($siteId, $deploymentId, 'output');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('application/gzip', $response['headers']['content-type']);
|
||||
$this->assertGreaterThan(0, $response['headers']['content-length']);
|
||||
|
|
|
|||