Resolve merge conflicts

This commit is contained in:
Khushboo Verma 2025-03-28 13:35:53 +05:30
commit ace7a6baa1
25 changed files with 389 additions and 124 deletions

View file

@ -837,7 +837,7 @@ return [
Exception::RULE_VERIFICATION_FAILED => [
'name' => Exception::RULE_VERIFICATION_FAILED,
'description' => 'Domain verification failed. Please check if your DNS records are correct and try again.',
'code' => 401,
'code' => 400,
'publish' => true
],
Exception::PROJECT_SMTP_CONFIG_INVALID => [

View file

@ -138,7 +138,7 @@ return [
'vue' => [
'key' => 'vue',
'name' => 'Vue.js',
'screenshotSleep' => 3000,
'screenshotSleep' => 5000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
@ -227,6 +227,23 @@ return [
]
]
],
'lynx' => [
'key' => 'lynx',
'name' => 'Lynx',
'screenshotSleep' => 5000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'adapters' => [
'static' => [
'key' => 'static',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './dist',
'startCommand' => 'bash helpers/server.sh',
'fallbackFile' => 'index.html'
]
]
],
'flutter' => [
'key' => 'flutter',
'name' => 'Flutter',

View file

@ -24718,6 +24718,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@ -25353,6 +25354,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@ -37040,6 +37042,11 @@
"description": "Site Template Name.",
"x-example": "Starter site"
},
"tagline": {
"type": "string",
"description": "Short description of template",
"x-example": "Minimal web app integrating with Appwrite."
},
"demoUrl": {
"type": "string",
"description": "URL hosting a template demo.",
@ -37103,6 +37110,7 @@
"required": [
"key",
"name",
"tagline",
"demoUrl",
"screenshotDark",
"screenshotLight",

View file

@ -16816,6 +16816,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@ -17226,6 +17227,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"

View file

@ -25221,6 +25221,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@ -25871,6 +25872,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@ -37623,6 +37625,11 @@
"description": "Site Template Name.",
"x-example": "Starter site"
},
"tagline": {
"type": "string",
"description": "Short description of template",
"x-example": "Minimal web app integrating with Appwrite."
},
"demoUrl": {
"type": "string",
"description": "URL hosting a template demo.",
@ -37688,6 +37695,7 @@
"required": [
"key",
"name",
"tagline",
"demoUrl",
"screenshotDark",
"screenshotLight",

View file

@ -17285,6 +17285,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@ -17714,6 +17715,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"

View file

@ -23,6 +23,7 @@ return [
'icon' => 'icon-lightning-bolt',
'id' => 'starter',
'name' => 'Starter function',
'score' => 5,
'tagline' =>
'A simple function to get started. Edit this function to explore endless possibilities with Appwrite Functions.',
'permissions' => ['any'],
@ -62,6 +63,7 @@ return [
'icon' => 'icon-upstash',
'id' => 'query-upstash-vector',
'name' => 'Query Upstash Vector',
'score' => 4,
'tagline' => 'Vector database that stores text embeddings and context retrieval for LLMs',
'permissions' => ['any'],
'events' => [],
@ -106,6 +108,7 @@ return [
'icon' => 'icon-redis',
'id' => 'query-redis-labs',
'name' => 'Query Redis Labs',
'score' => 4,
'tagline' => 'Key-value database with advanced caching capabilities.',
'permissions' => ['any'],
'events' => [],
@ -149,6 +152,7 @@ return [
'icon' => 'icon-neo4j',
'id' => 'query-neo4j-auradb',
'name' => 'Query Neo4j AuraDB',
'score' => 4,
'tagline' => 'Graph database with focus on relations between data.',
'permissions' => ['any'],
'events' => [],
@ -200,6 +204,7 @@ return [
'icon' => 'icon-mongodb',
'id' => 'query-mongo-atlas',
'name' => 'Query MongoDB Atlas',
'score' => 4,
'tagline' =>
'Realtime NoSQL document database with geospecial, graph, search, and vector suport.',
'permissions' => ['any'],
@ -237,6 +242,7 @@ return [
'icon' => 'icon-neon',
'id' => 'query-neon-postgres',
'name' => 'Query Neon Postgres',
'score' => 4,
'tagline' =>
'Reliable SQL database with replication, point-in-time recovery, and pgvector support.',
'permissions' => ['any'],
@ -305,6 +311,7 @@ return [
'icon' => 'icon-open-ai',
'id' => 'prompt-chatgpt',
'name' => 'Prompt ChatGPT',
'score' => 7,
'tagline' => 'Ask questions and let OpenAI GPT-3.5-turbo answer.',
'permissions' => ['any'],
'events' => [],
@ -366,6 +373,7 @@ return [
'icon' => 'icon-discord',
'id' => 'discord-command-bot',
'name' => 'Discord Command Bot',
'score' => 6,
'tagline' => 'Simple command using Discord Interactions.',
'permissions' => ['any'],
'events' => [],
@ -429,6 +437,7 @@ return [
'icon' => 'icon-perspective-api',
'id' => 'analyze-with-perspectiveapi',
'name' => 'Analyze with PerspectiveAPI',
'score' => 5,
'tagline' => 'Automate moderation by getting toxicity of messages.',
'permissions' => ['any'],
'events' => [],
@ -464,6 +473,7 @@ return [
'icon' => 'icon-pangea',
'id' => 'censor-with-redact',
'name' => 'Censor with Redact',
'score' => 5,
'tagline' =>
'Censor sensitive information from a provided text string using Redact API by Pangea.',
'permissions' => ['any'],
@ -512,6 +522,7 @@ return [
'icon' => 'icon-document',
'id' => 'generate-pdf',
'name' => 'Generate PDF',
'score' => 7,
'tagline' => 'Document containing sample invoice in PDF format.',
'permissions' => ['any'],
'events' => [],
@ -533,6 +544,7 @@ return [
'icon' => 'icon-github',
'id' => 'github-issue-bot',
'name' => 'GitHub issue bot',
'score' => 4,
'tagline' =>
'Automate the process of responding to newly opened issues in a GitHub repository.',
'permissions' => ['any'],
@ -577,6 +589,7 @@ return [
'icon' => 'icon-bookmark',
'id' => 'url-shortener',
'name' => 'URL shortener',
'score' => 3,
'tagline' => 'Generate URL with short ID and redirect to the original URL when visited.',
'permissions' => ['any'],
'events' => [],
@ -628,6 +641,7 @@ return [
'icon' => 'icon-algolia',
'id' => 'sync-with-algolia',
'name' => 'Sync with Algolia',
'score' => 4,
'tagline' => 'Intuitive search bar for any data in Appwrite Databases.',
'permissions' => ['any'],
'events' => [],
@ -709,6 +723,7 @@ return [
'icon' => 'icon-meilisearch',
'id' => 'sync-with-meilisearch',
'name' => 'Sync with Meilisearch',
'score' => 4,
'tagline' => 'Intuitive search bar for any data in Appwrite Databases.',
'permissions' => ['any'],
'events' => [],
@ -802,6 +817,7 @@ return [
'icon' => 'icon-vonage',
'id' => 'whatsapp-with-vonage',
'name' => 'WhatsApp with Vonage',
'score' => 6,
'tagline' => 'Simple bot to answer WhatsApp messages.',
'permissions' => ['any'],
'events' => [],
@ -888,6 +904,7 @@ return [
'icon' => 'icon-bell',
'id' => 'push-notification-with-fcm',
'name' => 'Push notification with FCM',
'score' => 4,
'tagline' => 'Send push notifications to your users using Firebase Cloud Messaging (FCM).',
'permissions' => ['any'],
'events' => [],
@ -944,6 +961,7 @@ return [
'icon' => 'icon-mail',
'id' => 'email-contact-form',
'name' => 'Email contact form',
'score' => 7,
'tagline' => 'Sends an email with the contents of a HTML form.',
'permissions' => ['any'],
'events' => [],
@ -1027,6 +1045,7 @@ return [
'icon' => 'icon-stripe',
'id' => 'subscriptions-with-stripe',
'name' => 'Subscriptions with Stripe',
'score' => 6,
'tagline' => 'Receive recurring card payments and grant subscribers extra permissions.',
'permissions' => ['any'],
'events' => [],
@ -1068,6 +1087,7 @@ return [
'icon' => 'icon-stripe',
'id' => 'payments-with-stripe',
'name' => 'Payments with Stripe',
'score' => 8,
'tagline' => 'Receive card payments and store paid orders.',
'permissions' => ['any'],
'events' => [],
@ -1125,6 +1145,7 @@ return [
'icon' => 'icon-chat',
'id' => 'text-generation-with-huggingface',
'name' => 'Text generation',
'score' => 5,
'tagline' => 'Generate text using the Hugging Face inference API.',
'permissions' => ['any'],
'events' => [],
@ -1159,6 +1180,7 @@ return [
'icon' => 'icon-translate',
'id' => 'language-translation-with-huggingface',
'name' => 'Language translation',
'score' => 5,
'tagline' => 'Translate text using the Hugging Face inference API.',
'permissions' => ['any'],
'events' => [],
@ -1193,6 +1215,7 @@ return [
'icon' => 'icon-eye',
'id' => 'image-classification-with-huggingface',
'name' => 'Image classification',
'score' => 5,
'tagline' => 'Classify images using the Hugging Face inference API.',
'permissions' => ['any'],
'events' => ['buckets.*.files.*.create'],
@ -1251,6 +1274,7 @@ return [
'icon' => 'icon-eye',
'id' => 'object-detection-with-huggingface',
'name' => 'Object detection',
'score' => 5,
'tagline' => 'Detect objects in images using the Hugging Face inference API.',
'permissions' => ['any'],
'events' => ['buckets.*.files.*.create'],
@ -1309,6 +1333,7 @@ return [
'icon' => 'icon-text',
'id' => 'speech-recognition-with-huggingface',
'name' => 'Speech recognition',
'score' => 5,
'tagline' => 'Transcribe audio to text using the Hugging Face inference API.',
'permissions' => ['any'],
'events' => ['buckets.*.files.*.create'],
@ -1367,6 +1392,7 @@ return [
'icon' => 'icon-chat',
'id' => 'text-to-speech-with-huggingface',
'name' => 'Text to speech',
'score' => 5,
'tagline' => 'Convert text to speech using the Hugging Face inference API.',
'permissions' => ['any'],
'events' => ['databases.*.collections.*.documents.*.create'],
@ -1425,6 +1451,7 @@ return [
'icon' => 'icon-chip',
'id' => 'generate-with-replicate',
'name' => 'Generate with Replicate',
'score' => 5,
'tagline' => "Generate text, audio and images using Replicate's API.",
'permissions' => ['any'],
'events' => [],
@ -1460,6 +1487,7 @@ return [
'icon' => 'icon-chip',
'id' => 'generate-with-together-ai',
'name' => 'Generate with Together AI',
'score' => 5,
'tagline' => "Generate text and images using Together AI's API.",
'permissions' => ['any'],
'events' => [],
@ -1502,6 +1530,7 @@ return [
'icon' => 'icon-chip',
'id' => 'chat-with-perplexity-ai',
'name' => 'Chat with Perplexity AI',
'score' => 5,
'tagline' => 'Create a chatbot using the Perplexity AI API.',
'permissions' => ['any'],
'events' => [],
@ -1543,6 +1572,7 @@ return [
'icon' => 'icon-chip',
'id' => 'generate-with-replicate',
'name' => 'Generate with Replicate',
'score' => 5,
'tagline' => "Generate text, audio and images using Replicate's API.",
'permissions' => ['any'],
'events' => [],
@ -1578,6 +1608,7 @@ return [
'icon' => 'icon-document-search',
'id' => 'sync-with-pinecone',
'name' => 'Sync with Pinecone',
'score' => 4,
'tagline' => "Sync your Appwrite database with Pinecone's vector database.",
'permissions' => ['any'],
'events' => [],
@ -1641,6 +1672,7 @@ return [
'icon' => 'icon-chip',
'id' => 'rag-with-langchain',
'name' => 'RAG with LangChain',
'score' => 6,
'tagline' => 'Generate text using a LangChain RAG model',
'permissions' => ['any'],
'events' => [],
@ -1704,6 +1736,7 @@ return [
'icon' => 'icon-chat',
'id' => 'speak-with-elevenlabs',
'name' => 'Speak with ElevenLabs',
'score' => 5,
'tagline' => 'Convert text to speech using the ElevenLabs API.',
'permissions' => ['any'],
'cron' => '',
@ -1759,6 +1792,7 @@ return [
'icon' => 'icon-chip',
'id' => 'speak-with-lmnt',
'name' => 'Speak with LMNT',
'score' => 5,
'tagline' => 'Convert text to speech using the LMNT API.',
'permissions' => ['any'],
'cron' => '',
@ -1800,6 +1834,7 @@ return [
'icon' => 'icon-chip',
'id' => 'chat-with-anyscale',
'name' => 'Chat with AnyScale',
'score' => 5,
'tagline' => 'Create a chatbot using the AnyScale API.',
'permissions' => ['any'],
'cron' => '',
@ -1841,6 +1876,7 @@ return [
'icon' => 'icon-music-note',
'id' => 'music-generation-with-huggingface',
'name' => 'Music generation',
'score' => 4,
'tagline' => 'Generate music from a text prompt using the Hugging Face inference API.',
'permissions' => ['any'],
'events' => [],
@ -1883,6 +1919,7 @@ return [
'icon' => 'icon-chip',
'id' => 'generate-with-fal-ai',
'name' => 'Generate with fal.ai',
'score' => 5,
'tagline' => "Generate images using fal.ai's API.",
'permissions' => ['any'],
'events' => [],
@ -1918,6 +1955,7 @@ return [
'icon' => 'icon-currency-dollar',
'id' => 'subscriptions-with-lemon-squeezy',
'name' => 'Subscriptions with Lemon Squeezy',
'score' => 6,
'tagline' => 'Receive recurring card payments and grant subscribers extra permissions.',
'permissions' => ['any'],
'events' => [],
@ -1973,6 +2011,7 @@ return [
'icon' => 'icon-currency-dollar',
'id' => 'payments-with-lemon-squeezy',
'name' => 'Payments with Lemon Squeezy',
'score' => 6,
'tagline' => 'Receive card payments and store paid orders.',
'permissions' => ['any'],
'events' => [],

View file

@ -87,15 +87,6 @@ const TEMPLATE_FRAMEWORKS = [
'adapter' => 'static',
'fallbackFile' => '',
],
'OTHER' => [
'key' => 'other',
'name' => 'Other',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'buildRuntime' => 'node-22',
'adapter' => 'static',
'fallbackFile' => 'index.html',
],
'VITE' => [
'key' => 'vite',
'name' => 'Vite',
@ -144,6 +135,16 @@ const TEMPLATE_FRAMEWORKS = [
'adapter' => 'static',
'outputDirectory' => './',
],
'LYNX' => [
'key' => 'lynx',
'name' => 'Lynx',
'installCommand' => 'npm install && cd web && npm install && cd ..',
'buildCommand' => 'npm run build && cd web && npm run build && cd ..',
'buildRuntime' => 'node-22',
'adapter' => 'static',
'outputDirectory' => './web/dist',
'fallbackFile' => 'index.html',
],
];
function getFramework(string $frameworkEnum, array $overrides)
@ -153,9 +154,30 @@ function getFramework(string $frameworkEnum, array $overrides)
}
return [
[
'key' => 'lynx-starter',
'name' => 'Lynx Starter',
'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/lynx-starter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/lynx-starter-light.png',
'frameworks' => [
getFramework('LYNX', [
'providerRootDirectory' => './lynx/starter',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => []
],
[
'key' => 'vitepress',
'name' => 'Vitepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vite.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vitepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vitepress-light.png',
@ -177,6 +199,8 @@ return [
[
'key' => 'vuepress',
'name' => 'Vuepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vue.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vuepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vuepress-light.png',
@ -198,6 +222,8 @@ return [
[
'key' => 'docusaurus',
'name' => 'Docusaurus',
'tagline' => 'Platform for documentation and knowledge sharing powered by React.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/docusaurus-dark.png',
'screenshotLight' => $url . '/images/sites/templates/docusaurus-light.png',
@ -219,6 +245,8 @@ return [
[
'key' => 'nxt-lnk',
'name' => 'Nxt Lnk',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/nxt-lnk-dark.png',
'screenshotLight' => $url . '/images/sites/templates/nxt-lnk-light.png',
@ -236,6 +264,8 @@ return [
[
'key' => 'magic-portfolio',
'name' => 'Magic Portfolio',
'tagline' => 'Complex personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/magic-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/magic-portfolio-light.png',
@ -253,6 +283,8 @@ return [
[
'key' => 'littlelink',
'name' => 'LittleLink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/littlelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/littlelink-light.png',
@ -270,6 +302,8 @@ return [
[
'key' => 'logspot',
'name' => 'Logspot',
'tagline' => 'Website to publish changelogs of your application.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/logspot-dark.png',
'screenshotLight' => $url . '/images/sites/templates/logspot-light.png',
@ -290,6 +324,8 @@ return [
[
'key' => 'astro-nano',
'name' => 'Astro Nano',
'tagline' => 'Minimal personal website to showcase your projects, articles, and more.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-nano-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-nano-light.png',
@ -309,6 +345,8 @@ return [
[
'key' => 'astro-starlight',
'name' => 'Astro Starlight',
'tagline' => 'Platform for documentation and knowledge sharing powered by Astro.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/astro-starlight-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlight-light.png',
@ -328,6 +366,8 @@ return [
[
'key' => 'astro-sphere',
'name' => 'Astro Sphere',
'tagline' => 'Modern personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-sphere-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-sphere-light.png',
@ -347,6 +387,8 @@ return [
[
'key' => 'astro-starlog',
'name' => 'Astro Starlog',
'tagline' => 'Platform for publishing written content and media powered by Astro.',
'score' => 5, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/astro-starlog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlog-light.png',
@ -366,6 +408,8 @@ return [
[
'key' => 'onelink',
'name' => 'Onelink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/onelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/onelink-light.png',
@ -387,6 +431,8 @@ return [
'key' => 'starter-for-flutter',
'name' => 'Flutter starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Flutter application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-flutter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-flutter-light.png',
'frameworks' => [
@ -430,6 +476,8 @@ return [
'key' => 'starter-for-js',
'name' => 'JavaScript starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple JavaScript application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-js-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-js-light.png',
'frameworks' => [
@ -472,6 +520,8 @@ return [
'key' => 'starter-for-angular',
'name' => 'Angular starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Angular application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-angular-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-angular-light.png',
'frameworks' => [
@ -516,6 +566,8 @@ return [
'key' => 'starter-for-svelte',
'name' => 'Svelte starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Svelte application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-svelte-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-svelte-light.png',
'frameworks' => [
@ -558,6 +610,8 @@ return [
'key' => 'starter-for-react',
'name' => 'React starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple React application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-react-light.png',
'frameworks' => [
@ -600,6 +654,8 @@ return [
'key' => 'starter-for-vue',
'name' => 'Vue starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Vue application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-vue-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-vue-light.png',
'frameworks' => [
@ -642,6 +698,8 @@ return [
'key' => 'starter-for-react-native',
'name' => 'React Native starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple React Native application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-react-native-light.png',
'frameworks' => [
@ -685,6 +743,8 @@ return [
'key' => 'starter-for-nextjs',
'name' => 'Next.js starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Next.js application integrated with Appwrite SDK.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nextjs-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-nextjs-light.png',
'frameworks' => [
@ -727,6 +787,8 @@ return [
'key' => 'starter-for-nuxt',
'name' => 'Nuxt starter',
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Nuxt application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nuxt-dark.png',
'screenshotLight' => $url . '/images/sites/templates/starter-for-nuxt-light.png',
'frameworks' => [
@ -768,6 +830,8 @@ return [
[
'key' => 'template-for-event',
'name' => 'Event template',
'tagline' => 'Hackathon landing page with support for project submissions.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::EVENTS],
'screenshotDark' => $url . '/images/sites/templates/template-for-event-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-event-light.png',
@ -804,6 +868,8 @@ return [
[
'key' => 'template-for-portfolio',
'name' => 'Portfolio template',
'tagline' => 'Simple personal website to showcase your projects, articles, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/template-for-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-portfolio-light.png',
@ -821,6 +887,8 @@ return [
[
'key' => 'template-for-store',
'name' => 'Store template',
'tagline' => 'E-commerce platform for selling products with Stripe integration.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::ECOMMERCE],
'screenshotDark' => $url . '/images/sites/templates/template-for-store-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-store-light.png',
@ -863,6 +931,8 @@ return [
[
'key' => 'template-for-blog',
'name' => 'Blog template',
'tagline' => 'Platform for publishing written content and media.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/template-for-blog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-blog-light.png',
@ -880,6 +950,8 @@ return [
[
'key' => 'astro-starter',
'name' => 'Astro starter',
'tagline' => 'Sample application built with Astro, a content-driven web framework.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/astro-starter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starter-light.png',
@ -897,6 +969,8 @@ return [
[
'key' => 'remix-starter',
'name' => 'Remix starter',
'tagline' => 'Sample application built with Remix, a React meta-framework.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/remix-starter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/remix-starter-light.png',

View file

@ -269,7 +269,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
}
if ($deployment->getAttribute('status') !== 'ready') {
$allowAnyStatus = !\is_null($apiKey) && $apiKey->isDeploymentStatusIgnored();
if (!$allowAnyStatus && $deployment->getAttribute('status') !== 'ready') {
if ($deployment->getAttribute('status') === 'failed') {
throw new AppwriteException(AppwriteException::BUILD_FAILED);
} else {

View file

@ -7,6 +7,8 @@
<title>404 Not Found</title>
<style>
@import url(https://fonts.bunny.net/css?family=fira-code:400|inter:400);
* {
margin: 0;
padding: 0;
@ -36,7 +38,7 @@
background: var(--color-overlay-on-neutral, rgba(0, 0, 0, 0.06));
color: var(--color-fgColor-neutral-secondary, #56565C);
text-align: center;
font-family: var(--font-family-sansSerif, Inter);
font-family: var(--font-family-sansSerif, Inter), sans-serif;
font-size: var(--font-size-S, 14px);
font-style: normal;
font-weight: 400;
@ -47,7 +49,7 @@
h1 {
color: var(--color-fgColor-neutral-primary, #2D2D31);
text-align: center;
font-family: var(--font-family-sansSerif, Inter);
font-family: var(--font-family-sansSerif, Inter), sans-serif;
font-size: var(--font-size-XXXL, 32px);
font-style: normal;
font-weight: 400;
@ -59,7 +61,7 @@
button {
border-radius: var(--border-radius-S, 8px);
font-family: var(--font-family-sansSerif, Inter);
font-family: var(--font-family-sansSerif, Inter), sans-serif;
font-size: var(--font-size-S, 14px);
font-style: normal;
font-weight: 500;
@ -88,7 +90,7 @@
}
.brand p {
font-family: var(--font-family-monospace, "Fira Code");
font-family: var(--font-family-monospace, "Fira Code"), monospace;
font-size: var(--font-size-XS, 12px);
font-style: normal;
font-weight: 400;

40
composer.lock generated
View file

@ -709,16 +709,16 @@
},
{
"name": "google/protobuf",
"version": "v4.30.1",
"version": "v4.30.2",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24"
"reference": "a4c4d8565b40b9f76debc9dfeb221412eacb8ced"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/f29ba8a30dfd940efb3a8a75dc44446539101f24",
"reference": "f29ba8a30dfd940efb3a8a75dc44446539101f24",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/a4c4d8565b40b9f76debc9dfeb221412eacb8ced",
"reference": "a4c4d8565b40b9f76debc9dfeb221412eacb8ced",
"shasum": ""
},
"require": {
@ -747,9 +747,9 @@
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.1"
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.30.2"
},
"time": "2025-03-13T21:08:17+00:00"
"time": "2025-03-26T18:01:50+00:00"
},
{
"name": "jean85/pretty-package-versions",
@ -4700,16 +4700,16 @@
},
{
"name": "utopia-php/swoole",
"version": "0.8.2",
"version": "0.8.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/swoole.git",
"reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4"
"reference": "1af73dd3e73987cf729c7db399054e4a70befd99"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4",
"reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/1af73dd3e73987cf729c7db399054e4a70befd99",
"reference": "1af73dd3e73987cf729c7db399054e4a70befd99",
"shasum": ""
},
"require": {
@ -4745,9 +4745,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/swoole/issues",
"source": "https://github.com/utopia-php/swoole/tree/0.8.2"
"source": "https://github.com/utopia-php/swoole/tree/0.8.3"
},
"time": "2024-02-01T14:54:12+00:00"
"time": "2025-03-26T10:09:05+00:00"
},
{
"name": "utopia-php/system",
@ -5089,16 +5089,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "0.40.10",
"version": "0.40.11",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "054ac96285caf4f77879087b2416a5ddb8263051"
"reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/054ac96285caf4f77879087b2416a5ddb8263051",
"reference": "054ac96285caf4f77879087b2416a5ddb8263051",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0ec5f4a60c15e33e208bc3444ba6148b1d0f0027",
"reference": "0ec5f4a60c15e33e208bc3444ba6148b1d0f0027",
"shasum": ""
},
"require": {
@ -5134,9 +5134,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.10"
"source": "https://github.com/appwrite/sdk-generator/tree/0.40.11"
},
"time": "2025-03-25T13:44:16+00:00"
"time": "2025-03-26T10:53:16+00:00"
},
{
"name": "doctrine/annotations",
@ -8507,7 +8507,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@ -8531,5 +8531,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

View file

@ -24,6 +24,7 @@ class Key
protected bool $bannerDisabled = false,
protected bool $projectCheckDisabled = false,
protected bool $previewAuthDisabled = false,
protected bool $deploymentStatusIgnored = false,
) {
}
@ -79,6 +80,11 @@ class Key
return $this->previewAuthDisabled;
}
public function isDeploymentStatusIgnored(): bool
{
return $this->deploymentStatusIgnored;
}
public function isProjectCheckDisabled(): bool
{
return $this->projectCheckDisabled;
@ -139,6 +145,7 @@ class Key
$bannerDisabled = $payload['bannerDisabled'] ?? false;
$projectCheckDisabled = $payload['projectCheckDisabled'] ?? false;
$previewAuthDisabled = $payload['previewAuthDisabled'] ?? false;
$deploymentStatusIgnored = $payload['deploymentStatusIgnored'] ?? false;
$scopes = \array_merge($payload['scopes'] ?? [], $scopes);
if (!$projectCheckDisabled && $projectId !== $project->getId()) {
@ -156,7 +163,8 @@ class Key
$hostnameOverride,
$bannerDisabled,
$projectCheckDisabled,
$previewAuthDisabled
$previewAuthDisabled,
$deploymentStatusIgnored
);
case API_KEY_STANDARD:
$key = $project->find(

View file

@ -71,10 +71,14 @@ class XList extends Base
});
}
$responseTemplates = \array_slice($templates, $offset, $limit);
\usort($templates, function ($a, $b) {
return $b['score'] <=> $a['score'];
});
$templates = \array_slice($templates, $offset, $limit);
$response->dynamic(new Document([
'templates' => $responseTemplates,
'total' => \count($responseTemplates),
'templates' => $templates,
'total' => \count($templates),
]), Response::MODEL_TEMPLATE_FUNCTION_LIST);
}
}

View file

@ -41,6 +41,8 @@ use Utopia\Storage\Device\Local;
use Utopia\System\System;
use Utopia\VCS\Adapter\Git\GitHub;
use function Swoole\Coroutine\batch;
class Builds extends Action
{
public static function getName(): string
@ -783,7 +785,6 @@ class Builds extends Action
$deployment->setAttribute('buildStartAt', DateTime::format((new \DateTime())->setTimestamp(floor($response['startTime']))));
$deployment->setAttribute('buildEndAt', $endTime);
$deployment->setAttribute('buildDuration', \intval(\ceil($durationEnd - $durationStart)));
$deployment->setAttribute('status', 'ready');
$deployment->setAttribute('buildPath', $response['path']);
$deployment->setAttribute('buildSize', $response['size']);
$deployment->setAttribute('totalSize', $deployment->getAttribute('buildSize', 0) + $deployment->getAttribute('sourceSize', 0));
@ -792,25 +793,16 @@ class Builds extends Action
foreach ($response['output'] as $log) {
$logs .= $log['content'];
}
$logs .= "Capturing screenshots ...\n";
$deployment->setAttribute('buildLogs', $logs);
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
if ($deployment->getInternalId() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
}
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
if ($isVcsEnabled) {
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
}
Console::success("Build id: $deploymentId created");
/** Screenshot site */
if ($resource->getCollection() === 'sites') {
try {
$rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
@ -848,72 +840,89 @@ class Builds extends Action
'bannerDisabled' => true,
'projectCheckDisabled' => true,
'previewAuthDisabled' => true,
'deploymentStatusIgnored' => true
]);
// TODO: @Meldiron if becomes too slow, do concurrently
foreach ($configs as $key => $config) {
$config['headers'] = \array_merge($config['headers'] ?? [], [
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey
]);
$screenshotError = null;
$screenshots = batch(\array_map(function ($key) use ($configs, $deviceForFiles, $apiKey, $resource, $client, $bucket, $project, $dbForPlatform, &$screenshotError) {
return function () use ($key, $configs, $deviceForFiles, $apiKey, $resource, $client, $bucket, $project, $dbForPlatform, &$screenshotError) {
try {
$config = $configs[$key];
$config['sleep'] = 3000;
$config['headers'] = \array_merge($config['headers'] ?? [], [
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey
]);
$config['sleep'] = 3000;
$frameworks = Config::getParam('frameworks', []);
$framework = $frameworks[$resource->getAttribute('framework', '')] ?? null;
if (!is_null($framework)) {
$config['sleep'] = $framework['screenshotSleep'];
}
$frameworks = Config::getParam('frameworks', []);
$framework = $frameworks[$resource->getAttribute('framework', '')] ?? null;
if (!is_null($framework)) {
$config['sleep'] = $framework['screenshotSleep'];
}
$response = $client->fetch(
url: 'http://appwrite-browser:3000/v1/screenshots',
method: 'POST',
body: $config
);
$fetchResponse = $client->fetch(
url: 'http://appwrite-browser:3000/v1/screenshots',
method: 'POST',
body: $config
);
if ($response->getStatusCode() >= 400) {
throw new \Exception($response->getBody());
}
if ($fetchResponse->getStatusCode() >= 400) {
throw new \Exception($fetchResponse->getBody());
}
$screenshot = $response->getBody();
$screenshot = $fetchResponse->getBody();
$fileId = ID::unique();
$fileName = $fileId . '.png';
$path = $deviceForFiles->getPath($fileName);
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
$success = $deviceForFiles->write($path, $screenshot, "image/png");
$fileId = ID::unique();
$fileName = $fileId . '.png';
$path = $deviceForFiles->getPath($fileName);
$path = str_ireplace($deviceForFiles->getRoot(), $deviceForFiles->getRoot() . DIRECTORY_SEPARATOR . $bucket->getId(), $path); // Add bucket id to path after root
$success = $deviceForFiles->write($path, $screenshot, "image/png");
if (!$success) {
throw new \Exception("Screenshot failed to save");
}
if (!$success) {
throw new \Exception("Screenshot failed to save");
}
$teamId = $project->getAttribute('teamId', '');
$file = new Document([
'$id' => $fileId,
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
],
'bucketId' => $bucket->getId(),
'bucketInternalId' => $bucket->getInternalId(),
'name' => $fileName,
'path' => $path,
'signature' => $deviceForFiles->getFileHash($path),
'mimeType' => $deviceForFiles->getFileMimeType($path),
'sizeOriginal' => \strlen($screenshot),
'sizeActual' => $deviceForFiles->getFileSize($path),
'algorithm' => Compression::GZIP,
'comment' => '',
'chunksTotal' => 1,
'chunksUploaded' => 1,
'openSSLVersion' => null,
'openSSLCipher' => null,
'openSSLTag' => null,
'openSSLIV' => null,
'search' => implode(' ', [$fileId, $fileName]),
'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)],
]);
$file = Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file));
$teamId = $project->getAttribute('teamId', '');
$file = new Document([
'$id' => $fileId,
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
],
'bucketId' => $bucket->getId(),
'bucketInternalId' => $bucket->getInternalId(),
'name' => $fileName,
'path' => $path,
'signature' => $deviceForFiles->getFileHash($path),
'mimeType' => $deviceForFiles->getFileMimeType($path),
'sizeOriginal' => \strlen($screenshot),
'sizeActual' => $deviceForFiles->getFileSize($path),
'algorithm' => Compression::GZIP,
'comment' => '',
'chunksTotal' => 1,
'chunksUploaded' => 1,
'openSSLVersion' => null,
'openSSLCipher' => null,
'openSSLTag' => null,
'openSSLIV' => null,
'search' => implode(' ', [$fileId, $fileName]),
'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)],
]);
$file = Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file));
$deployment->setAttribute($key, $fileId);
return [ 'key' => $key, 'fileId' => $fileId ];
} catch (\Throwable $th) {
$screenshotError = $th->getMessage();
return;
}
};
}, \array_keys($configs)));
if (!\is_null($screenshotError)) {
throw new \Exception($screenshotError);
}
foreach ($screenshots as $screenshot) {
$deployment->setAttribute($screenshot['key'], $screenshot['fileId']);
}
$dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
@ -928,6 +937,25 @@ class Builds extends Action
}
}
/** Update the status */
$deployment->setAttribute('status', 'ready');
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
if ($deployment->getInternalId() === $resource->getAttribute('latestDeploymentInternalId', '')) {
$resource = $resource->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
}
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
if ($isVcsEnabled) {
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
}
Console::success("Build id: $deploymentId created");
/** Set auto deploy */
if ($deployment->getAttribute('activate') === true) {
$resource->setAttribute('live', true);
@ -1066,6 +1094,12 @@ class Builds extends Action
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
}
} catch (\Throwable $th) {
Console::warning('Build failed:');
Console::error($th->getMessage());
Console::error($th->getFile());
Console::error($th->getLine());
Console::error($th->getTraceAsString());
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
return;

View file

@ -29,7 +29,7 @@ class Get extends Base
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/templates/:templateId')
->desc('Get site template')
->groups(['api', 'sites'])
->groups(['api'])
->label('scope', 'public')
->label('resourceType', RESOURCE_TYPE_SITES)
->label('sdk', new Method(

View file

@ -30,7 +30,7 @@ class XList extends Base
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/templates')
->desc('List templates')
->groups(['api', 'sites'])
->groups(['api'])
->label('scope', 'public')
->label('resourceType', RESOURCE_TYPE_SITES)
->label('sdk', new Method(
@ -76,10 +76,15 @@ class XList extends Base
});
}
$responseTemplates = \array_slice($templates, $offset, $limit);
\usort($templates, function ($a, $b) {
return $b['score'] <=> $a['score'];
});
$templates = \array_slice($templates, $offset, $limit);
$response->dynamic(new Document([
'templates' => $responseTemplates,
'total' => \count($responseTemplates),
'templates' => $templates,
'total' => \count($templates),
]), Response::MODEL_TEMPLATE_SITE_LIST);
}
}

View file

@ -33,6 +33,8 @@ class Preview extends Adapter
$banner = <<<EOT
<style>
@import url(https://fonts.bunny.net/css?family=fira-code:400|inter:400);
#appwrite-preview {
padding: 0;
margin: 0;
@ -80,7 +82,7 @@ class Preview extends Adapter
padding: 0;
margin: 0;
color: var(--color-fgColor-neutral-secondary, #56565C);
font-family: var(--font-family-sansSerif, Inter);
font-family: var(--font-family-sansSerif, Inter), sans-serif;
font-size: var(--font-size-XS, 12px);
font-style: normal;
font-weight: 500;
@ -97,7 +99,7 @@ class Preview extends Adapter
padding: var(--space-1, 2px) var(--space-2, 4px);
color: var(--color-fgColor-neutral-secondary, #56565C);
text-align: center;
font-family: var(--font-family-sansSerif, Inter);
font-family: var(--font-family-sansSerif, Inter), sans-serif;
font-size: var(--font-size-XS, 12px);
font-style: normal;
font-weight: 400;
@ -120,7 +122,7 @@ class Preview extends Adapter
#appwrite-preview-text {
color: var(--color-fgColor-neutral-secondary, #C3C3C6);
font-family: var(--font-family-sansSerif, Inter);
font-family: var(--font-family-sansSerif, Inter), sans-serif;
font-size: var(--font-size-XS, 12px);
}

View file

@ -22,6 +22,12 @@ class TemplateSite extends Model
'default' => '',
'example' => 'Starter site',
])
->addRule('tagline', [
'type' => self::TYPE_STRING,
'description' => 'Short description of template',
'default' => '',
'example' => 'Minimal web app integrating with Appwrite.',
])
->addRule('demoUrl', [
'type' => self::TYPE_STRING,
'description' => 'URL hosting a template demo.',

View file

@ -614,19 +614,26 @@ class RealtimeConsoleClientTest extends Scope
$previousBuildLogs = $response['data']['payload']['buildLogs'];
$this->assertThat(
$response['data']['payload']['status'],
$this->logicalOr(
$this->equalTo('building'),
$this->equalTo('ready'),
),
);
$this->assertEquals('building', $response['data']['payload']['status']);
if ($response['data']['payload']['status'] === 'ready') {
if (!empty($response['data']['payload']['buildEndAt'])) {
$this->assertNotEmpty($response['data']['payload']['buildEndAt']);
$this->assertNotEmpty($response['data']['payload']['buildStartAt']);
$this->assertNotEmpty($response['data']['payload']['buildDuration']);
$this->assertNotEmpty($response['data']['payload']['buildPath']);
$this->assertNotEmpty($response['data']['payload']['buildSize']);
$this->assertNotEmpty($response['data']['payload']['totalSize']);
$this->assertNotEmpty($response['data']['payload']['buildLogs']);
break;
}
}
$response = json_decode($client->receive(), true);
$this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.update", $response['data']['events']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertEquals("ready", $response['data']['payload']['status']);
$client->close();
}
}

View file

@ -37,6 +37,7 @@ class SitesCustomClientTest extends Scope
$this->assertArrayHasKey('variables', $template);
$this->assertArrayHasKey('screenshotDark', $template);
$this->assertArrayHasKey('screenshotLight', $template);
$this->assertArrayHasKey('tagline', $template);
}
// List templates with pagination
@ -126,6 +127,7 @@ class SitesCustomClientTest extends Scope
$this->assertEquals('React starter', $template['body']['name']);
$this->assertEquals(['starter'], $template['body']['useCases']);
$this->assertEquals('github', $template['body']['vcsProvider']);
$this->assertEquals('Simple React application integrated with Appwrite SDK.', $template['body']['tagline']);
$this->assertIsArray($template['body']['frameworks']);
$this->assertEquals('http://localhost/images/sites/templates/starter-for-react-dark.png', $template['body']['screenshotDark']);
$this->assertEquals('http://localhost/images/sites/templates/starter-for-react-light.png', $template['body']['screenshotLight']);

View file

@ -2430,4 +2430,38 @@ class SitesCustomServerTest extends Scope
$this->cleanupSite($siteId);
}
public function testPermanentRedirect(): void
{
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'Sub project site',
'framework' => 'other',
'buildRuntime' => 'node-22',
'outputDirectory' => './'
]);
$this->assertNotEmpty($siteId);
$domain = $this->setupSiteDomain($siteId);
$this->assertNotEmpty($domain);
$deploymentId = $this->setupDeployment($siteId, [
'code' => $this->packageSite('sub-directories'),
'activate' => 'true'
]);
$this->assertNotEmpty($deploymentId);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString('Sub-directory index', $response['body']);
$response1 = $proxyClient->call(Client::METHOD_GET, '/project1');
$this->assertEquals(200, $response1['headers']['status-code']);
$this->assertStringContainsString('Sub-directory project1', $response1['body']);
$response2 = $proxyClient->call(Client::METHOD_GET, '/project1/');
$this->assertEquals(200, $response2['headers']['status-code']);
$this->assertStringContainsString('Sub-directory project1', $response2['body']);
$this->cleanupSite($siteId);
}
}

View file

@ -0,0 +1,5 @@
<html lang="en">
<body>
<p>Sub-directory index</p>
</body>
</html>

View file

@ -0,0 +1,5 @@
<html lang="en">
<body>
<p>Sub-directory project1</p>
</body>
</html>