diff --git a/Dockerfile b/Dockerfile index 2bb9f80d9e..c40ea06d1c 100755 --- a/Dockerfile +++ b/Dockerfile @@ -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 && \ diff --git a/app/config/site-templates.php b/app/config/site-templates.php index b22b559a15..b34394e6e1 100644 --- a/app/config/site-templates.php +++ b/app/config/site-templates.php @@ -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', diff --git a/app/http.php b/app/http.php index 83f56aeb94..4712d4ebf9 100644 --- a/app/http.php +++ b/app/http.php @@ -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 diff --git a/app/init.php b/app/init.php index 4e36c91cb1..3291b160b9 100644 --- a/app/init.php +++ b/app/init.php @@ -1977,6 +1977,7 @@ App::setResource('previewHostname', function (Request $request, ?Key $apiKey) { } } + return ''; }, ['request', 'apiKey']); diff --git a/bin/screenshot b/bin/screenshot new file mode 100755 index 0000000000..4d8ceb998f --- /dev/null +++ b/bin/screenshot @@ -0,0 +1,3 @@ +#!/bin/sh + +php /usr/src/code/app/cli.php screenshot $@ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 8349c14ccb..c2288c0097 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/public/images/sites/templates/astro-starter-dark.png b/public/images/sites/templates/astro-starter-dark.png new file mode 100644 index 0000000000..3d064c5cdd Binary files /dev/null and b/public/images/sites/templates/astro-starter-dark.png differ diff --git a/public/images/sites/templates/astro-starter-light.png b/public/images/sites/templates/astro-starter-light.png new file mode 100644 index 0000000000..3d064c5cdd Binary files /dev/null and b/public/images/sites/templates/astro-starter-light.png differ diff --git a/public/images/sites/templates/flutter-starter-dark.png b/public/images/sites/templates/flutter-starter-dark.png new file mode 100644 index 0000000000..9b239ff767 Binary files /dev/null and b/public/images/sites/templates/flutter-starter-dark.png differ diff --git a/public/images/sites/templates/flutter-starter-light.png b/public/images/sites/templates/flutter-starter-light.png new file mode 100644 index 0000000000..9b239ff767 Binary files /dev/null and b/public/images/sites/templates/flutter-starter-light.png differ diff --git a/public/images/sites/templates/nextjs-starter-dark.png b/public/images/sites/templates/nextjs-starter-dark.png new file mode 100644 index 0000000000..022e981d08 Binary files /dev/null and b/public/images/sites/templates/nextjs-starter-dark.png differ diff --git a/public/images/sites/templates/nextjs-starter-light.png b/public/images/sites/templates/nextjs-starter-light.png new file mode 100644 index 0000000000..0e19bf5d37 Binary files /dev/null and b/public/images/sites/templates/nextjs-starter-light.png differ diff --git a/public/images/sites/templates/nuxt-starter-dark.png b/public/images/sites/templates/nuxt-starter-dark.png new file mode 100644 index 0000000000..bea04f1cf9 Binary files /dev/null and b/public/images/sites/templates/nuxt-starter-dark.png differ diff --git a/public/images/sites/templates/nuxt-starter-light.png b/public/images/sites/templates/nuxt-starter-light.png new file mode 100644 index 0000000000..006e366a9f Binary files /dev/null and b/public/images/sites/templates/nuxt-starter-light.png differ diff --git a/public/images/sites/templates/remix-starter-dark.png b/public/images/sites/templates/remix-starter-dark.png new file mode 100644 index 0000000000..47c0cc0394 Binary files /dev/null and b/public/images/sites/templates/remix-starter-dark.png differ diff --git a/public/images/sites/templates/remix-starter-light.png b/public/images/sites/templates/remix-starter-light.png new file mode 100644 index 0000000000..73c2d12625 Binary files /dev/null and b/public/images/sites/templates/remix-starter-light.png differ diff --git a/public/images/sites/templates/starter-for-nextjs-dark.png b/public/images/sites/templates/starter-for-nextjs-dark.png new file mode 100644 index 0000000000..7acc80d4d4 Binary files /dev/null and b/public/images/sites/templates/starter-for-nextjs-dark.png differ diff --git a/public/images/sites/templates/starter-for-nextjs-light.png b/public/images/sites/templates/starter-for-nextjs-light.png new file mode 100644 index 0000000000..7acc80d4d4 Binary files /dev/null and b/public/images/sites/templates/starter-for-nextjs-light.png differ diff --git a/public/images/sites/templates/starter-for-nuxt-dark.png b/public/images/sites/templates/starter-for-nuxt-dark.png new file mode 100644 index 0000000000..ab8884febd Binary files /dev/null and b/public/images/sites/templates/starter-for-nuxt-dark.png differ diff --git a/public/images/sites/templates/starter-for-nuxt-light.png b/public/images/sites/templates/starter-for-nuxt-light.png new file mode 100644 index 0000000000..ab8884febd Binary files /dev/null and b/public/images/sites/templates/starter-for-nuxt-light.png differ diff --git a/public/images/sites/templates/starter-for-react-dark.png b/public/images/sites/templates/starter-for-react-dark.png new file mode 100644 index 0000000000..dcfc0a5521 Binary files /dev/null and b/public/images/sites/templates/starter-for-react-dark.png differ diff --git a/public/images/sites/templates/starter-for-react-light.png b/public/images/sites/templates/starter-for-react-light.png new file mode 100644 index 0000000000..dcfc0a5521 Binary files /dev/null and b/public/images/sites/templates/starter-for-react-light.png differ diff --git a/public/images/sites/templates/starter-for-svelte-dark.png b/public/images/sites/templates/starter-for-svelte-dark.png new file mode 100644 index 0000000000..f25943ae48 Binary files /dev/null and b/public/images/sites/templates/starter-for-svelte-dark.png differ diff --git a/public/images/sites/templates/starter-for-svelte-light.png b/public/images/sites/templates/starter-for-svelte-light.png new file mode 100644 index 0000000000..f25943ae48 Binary files /dev/null and b/public/images/sites/templates/starter-for-svelte-light.png differ diff --git a/public/images/sites/templates/starter-for-vue-dark.png b/public/images/sites/templates/starter-for-vue-dark.png new file mode 100644 index 0000000000..9d9af29f87 Binary files /dev/null and b/public/images/sites/templates/starter-for-vue-dark.png differ diff --git a/public/images/sites/templates/starter-for-vue-light.png b/public/images/sites/templates/starter-for-vue-light.png new file mode 100644 index 0000000000..9d9af29f87 Binary files /dev/null and b/public/images/sites/templates/starter-for-vue-light.png differ diff --git a/public/images/sites/templates/sveltekit-starter-dark.png b/public/images/sites/templates/sveltekit-starter-dark.png new file mode 100644 index 0000000000..90ac7f96aa Binary files /dev/null and b/public/images/sites/templates/sveltekit-starter-dark.png differ diff --git a/public/images/sites/templates/sveltekit-starter-light.png b/public/images/sites/templates/sveltekit-starter-light.png new file mode 100644 index 0000000000..90ac7f96aa Binary files /dev/null and b/public/images/sites/templates/sveltekit-starter-light.png differ diff --git a/public/images/sites/templates/template-for-blog-dark.png b/public/images/sites/templates/template-for-blog-dark.png new file mode 100644 index 0000000000..b076b918a8 Binary files /dev/null and b/public/images/sites/templates/template-for-blog-dark.png differ diff --git a/public/images/sites/templates/template-for-blog-light.png b/public/images/sites/templates/template-for-blog-light.png new file mode 100644 index 0000000000..b076b918a8 Binary files /dev/null and b/public/images/sites/templates/template-for-blog-light.png differ diff --git a/public/images/sites/templates/template-for-event-dark.png b/public/images/sites/templates/template-for-event-dark.png new file mode 100644 index 0000000000..c3376d3ff6 Binary files /dev/null and b/public/images/sites/templates/template-for-event-dark.png differ diff --git a/public/images/sites/templates/template-for-event-light.png b/public/images/sites/templates/template-for-event-light.png new file mode 100644 index 0000000000..c97901326a Binary files /dev/null and b/public/images/sites/templates/template-for-event-light.png differ diff --git a/public/images/sites/templates/template-for-onelink-dark.png b/public/images/sites/templates/template-for-onelink-dark.png new file mode 100644 index 0000000000..4680399672 Binary files /dev/null and b/public/images/sites/templates/template-for-onelink-dark.png differ diff --git a/public/images/sites/templates/template-for-onelink-light.png b/public/images/sites/templates/template-for-onelink-light.png new file mode 100644 index 0000000000..7a040b5d8d Binary files /dev/null and b/public/images/sites/templates/template-for-onelink-light.png differ diff --git a/public/images/sites/templates/template-for-portfolio-dark.png b/public/images/sites/templates/template-for-portfolio-dark.png new file mode 100644 index 0000000000..4b4382bf80 Binary files /dev/null and b/public/images/sites/templates/template-for-portfolio-dark.png differ diff --git a/public/images/sites/templates/template-for-portfolio-light.png b/public/images/sites/templates/template-for-portfolio-light.png new file mode 100644 index 0000000000..4b4382bf80 Binary files /dev/null and b/public/images/sites/templates/template-for-portfolio-light.png differ diff --git a/public/images/sites/templates/template-for-store-dark.png b/public/images/sites/templates/template-for-store-dark.png new file mode 100644 index 0000000000..645671ab55 Binary files /dev/null and b/public/images/sites/templates/template-for-store-dark.png differ diff --git a/public/images/sites/templates/template-for-store-light.png b/public/images/sites/templates/template-for-store-light.png new file mode 100644 index 0000000000..645671ab55 Binary files /dev/null and b/public/images/sites/templates/template-for-store-light.png differ diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 9067e042e4..aa24182e9a 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -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')); diff --git a/src/Appwrite/Platform/Services/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php index 6a6cb3237a..3ada193cf7 100644 --- a/src/Appwrite/Platform/Services/Tasks.php +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -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()) diff --git a/src/Appwrite/Platform/Tasks/Screenshot.php b/src/Appwrite/Platform/Tasks/Screenshot.php new file mode 100644 index 0000000000..43cf434408 --- /dev/null +++ b/src/Appwrite/Platform/Tasks/Screenshot.php @@ -0,0 +1,300 @@ +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"); + } +} diff --git a/src/Appwrite/Utopia/Response/Model/TemplateSite.php b/src/Appwrite/Utopia/Response/Model/TemplateSite.php index b9fe4704c7..fe123bde53 100644 --- a/src/Appwrite/Utopia/Response/Model/TemplateSite.php +++ b/src/Appwrite/Utopia/Response/Model/TemplateSite.php @@ -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,