Compare commits

...
Author SHA1 Message Date
Torsten Dittmann 224ee89fd0 Remove Traefik labels and unused ports in production 2025-04-07 20:26:41 +02:00
Torsten Dittmann 0f8efefea7 Remove unused service dependencies 2025-04-07 20:20:15 +02:00
Torsten Dittmann 3ba5a04951 Add production Docker Compose configuration file 2025-04-07 20:18:35 +02:00
Matej Bačo 89a0fb8c9b Update docker-compose.yml 2025-04-07 17:29:02 +02:00
Matej Bačo fb1a19747a Add documentation template 2025-04-04 10:44:03 +02:00
Matej Bačo 8fbc517b17 Fix rules cleanup 2025-04-02 16:47:37 +02:00
Matej Bačo 51d2deca5f Fix endpoint description 2025-04-01 20:44:49 +02:00
Matej Bačo 1bdbb8fce1 Fix failing tests 2025-04-01 16:50:11 +02:00
Matej Bačo d87c67eecb Fix failing tests 2025-04-01 15:29:07 +02:00
Matej Bačo 8a2ec0dcd7 Add 1.7.x specs 2025-04-01 15:19:18 +02:00
Matej Bačo 9c6551ff7b Update specs 2025-04-01 13:27:33 +02:00
Matej Bačo 066474010b Improve QR quality 2025-04-01 11:24:42 +02:00
Matej Bačo 7bbf04e7ac Merge branch '1.7.x' into feat-sites 2025-04-01 10:49:44 +02:00
Matej Bačo dd8b718985 Rollback unwanted changes 2025-03-31 18:52:46 +02:00
Matej Bačo ef39f8c2e2 Fix multiple small bugs
- VCS deployments stuck in waiting
- Fix tcp coroutine issues in builds worker, failing screenshots
- Fix command escaping with space and quotes
- Remove branding from already branded 404 page
- Fix css reset of preview banner
- Remove scope check from public endpoints
- Improve build logs colors
- Function logs say capturing screenshot
- Update GitHub comment design
2025-03-31 18:51:33 +02:00
Steven NguyenandGitHub e0a54a2ea6 Merge pull request #9503 from appwrite/pla-2610
feat: add query by role to memberships
2025-03-28 07:14:13 -07:00
Matej Bačo fbd0051118 Make env var public 2025-03-28 11:38:45 +01:00
Chirag Aggarwal 85edfc6af8 chore: added test for users.listmemberships 2025-03-28 05:47:18 +00:00
Matej Bačo cfe6b8d34b Add score to function templates 2025-03-27 14:23:15 +01:00
Matej BačoandGitHub aedfe0352d Merge pull request #9553 from appwrite/feat-fix-308-redirect
Feat fix 308 redirect
2025-03-27 14:05:36 +01:00
Khushboo Verma 9404e9225e Update test 2025-03-27 16:36:37 +05:30
Khushboo Verma e263d681d9 Resolve merge conflicts 2025-03-27 16:27:26 +05:30
Matej Bačo 6b8df26f84 Update libs 2025-03-27 11:56:09 +01:00
Matej Bačo e8cc95323e Generate specs 2025-03-27 11:34:38 +01:00
Matej Bačo 7c1fb0d945 Add template details 2025-03-27 11:32:03 +01:00
Matej Bačo 20f780a58b Add lynx 2025-03-27 09:55:04 +01:00
Matej BačoandGitHub 3d1ea6d330 Merge pull request #9580 from appwrite/fix-deployment-status
Fix: Deployment status
2025-03-27 09:31:17 +01:00
Matej Bačo 834872870f Linter fix 2025-03-27 09:17:15 +01:00
Matej BačoandGitHub 1946fca9f6 Merge branch 'feat-sites' into fix-deployment-status 2025-03-27 09:00:36 +01:00
Matej Bačo 6af57ca48e Fix async crashes 2025-03-26 17:51:39 +01:00
Matej Bačo 3c8e4da34d Update tests 2025-03-26 17:04:38 +01:00
Matej BačoandGitHub 3b1f2afbd5 Merge pull request #9569 from appwrite/chore-add-error-for-failed-builds
If build has failed, show correct error page
2025-03-26 15:44:39 +01:00
Matej Bačo 34ab21730d Linter fix 2025-03-26 15:22:19 +01:00
Matej Bačo 70fbf69c56 Fix fonts 2025-03-26 14:38:27 +01:00
Matej Bačo 62be0e1d00 Fix latest deployment attributes 2025-03-26 14:38:18 +01:00
Matej Bačo 2c55f5aaa6 Concurrency screenshots 2025-03-26 13:20:14 +01:00
Matej Bačo 681b6031aa Linter fix 2025-03-26 12:59:15 +01:00
Matej Bačo ab03b4e3ca Deployment ready after screenshot 2025-03-26 12:59:05 +01:00
Khushboo Verma 2e0a6f9b9e Update swoole version 2025-03-26 17:22:49 +05:30
Khushboo Verma 572544bf55 Merge branch 'feat-sites' into feat-fix-308-redirect 2025-03-26 17:17:23 +05:30
Khushboo Verma 583a18dcd7 Update assertion 2025-03-26 17:06:19 +05:30
Khushboo Verma 3b9318c2fc Add tests 2025-03-26 16:52:32 +05:30
Khushboo Verma e836df13ba Merge branch 'feat-sites' into chore-add-error-for-failed-builds 2025-03-26 16:11:03 +05:30
Matej Bačo 023597842e Merge branch 'feat-sites' of github.com:appwrite/appwrite into feat-sites 2025-03-26 10:34:57 +01:00
Matej Bačo c57ec57277 Upgrade Console 2025-03-26 10:22:58 +01:00
Matej Bačo 6978f6960e Add new templates; fix bash support during QA 2025-03-26 10:14:51 +01:00
Matej BačoandGitHub 95ba881ab7 Merge pull request #9575 from appwrite/feat-bash-support
Feat: Bash support
2025-03-25 23:55:33 +01:00
Khushboo Verma bd18edc197 If build has failed, show correct error page 2025-03-24 15:50:25 +05:30
Khushboo Verma 65aa218aac Add test for permanent redirect 2025-03-21 17:54:07 +05:30
Khushboo Verma df943c62ca Add test for permanent redirect 2025-03-21 17:53:22 +05:30
Chirag Aggarwal e80bc2bb39 chore: shifted migration to 1.7.0 2025-03-15 13:36:08 +00:00
Chirag AggarwalandGitHub 9a4220f2dd Merge branch '1.7.x' into pla-2610 2025-03-15 18:53:59 +05:30
Chirag Aggarwal 33ab1513d0 chore: update specs 2025-03-13 10:46:55 +00:00
Chirag Aggarwal d422f693f7 feat: add query by role to memberships 2025-03-13 09:48:39 +00:00
78 changed files with 164193 additions and 265 deletions
+7
View File
@@ -1417,6 +1417,13 @@ return [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_roles'),
'type' => Database::INDEX_KEY,
'attributes' => ['roles'],
'lengths' => [128],
'orders' => [],
],
],
],
+6 -1
View File
@@ -580,6 +580,11 @@ return [
'description' => 'Build with the requested ID is already completed and cannot be canceled.',
'code' => 400,
],
Exception::BUILD_FAILED => [
'name' => Exception::BUILD_FAILED,
'description' => 'Build with the requested ID failed. Please check the logs for more information.',
'code' => 400,
],
/** Deployments */
Exception::DEPLOYMENT_NOT_FOUND => [
@@ -832,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 => [
+18 -1
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',
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -6848,7 +6848,7 @@
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"schema": {
"type": "array",
+41 -3
View File
@@ -9362,7 +9362,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "functions.read",
"scope": "public",
"platforms": [
"server"
],
@@ -24718,6 +24718,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@@ -24917,7 +24918,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "sites.read",
"scope": "public",
"platforms": [
"server"
],
@@ -25353,6 +25354,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@@ -28893,7 +28895,7 @@
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"schema": {
"type": "array",
@@ -30986,6 +30988,30 @@
"x-example": "<USER_ID>"
},
"in": "path"
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"schema": {
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"in": "query"
},
{
"name": "search",
"description": "Search term to filter your list results. Max length: 256 chars.",
"required": false,
"schema": {
"type": "string",
"x-example": "<SEARCH>",
"default": ""
},
"in": "query"
}
]
}
@@ -37040,6 +37066,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 +37134,7 @@
"required": [
"key",
"name",
"tagline",
"demoUrl",
"screenshotDark",
"screenshotLight",
@@ -40882,6 +40914,11 @@
"description": "A domain to use for site URLs.",
"x-example": "sites.localhost"
},
"_APP_DOMAIN_FUNCTIONS": {
"type": "string",
"description": "A domain to use for function URLs.",
"x-example": "functions.localhost"
},
"_APP_OPTIONS_FORCE_HTTPS": {
"type": "string",
"description": "Defines if HTTPS is enforced for all requests.",
@@ -40902,6 +40939,7 @@
"_APP_DOMAIN_ENABLED",
"_APP_ASSISTANT_ENABLED",
"_APP_DOMAIN_SITES",
"_APP_DOMAIN_FUNCTIONS",
"_APP_OPTIONS_FORCE_HTTPS",
"_APP_DOMAINS_NAMESERVERS"
]
+29 -3
View File
@@ -8449,7 +8449,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "functions.read",
"scope": "public",
"platforms": [
"server"
],
@@ -16816,6 +16816,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@@ -17015,7 +17016,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "sites.read",
"scope": "public",
"platforms": [
"server"
],
@@ -17226,6 +17227,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@@ -20513,7 +20515,7 @@
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"schema": {
"type": "array",
@@ -22567,6 +22569,30 @@
"x-example": "<USER_ID>"
},
"in": "path"
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"schema": {
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"in": "query"
},
{
"name": "search",
"description": "Search term to filter your list results. Max length: 256 chars.",
"required": false,
"schema": {
"type": "string",
"x-example": "<SEARCH>",
"default": ""
},
"in": "query"
}
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -7060,7 +7060,7 @@
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"type": "array",
"collectionFormat": "multi",
+38 -3
View File
@@ -9530,7 +9530,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "functions.read",
"scope": "public",
"platforms": [
"server"
],
@@ -25221,6 +25221,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@@ -25436,7 +25437,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "sites.read",
"scope": "public",
"platforms": [
"server"
],
@@ -25871,6 +25872,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@@ -29426,7 +29428,7 @@
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"type": "array",
"collectionFormat": "multi",
@@ -31569,6 +31571,27 @@
"type": "string",
"x-example": "<USER_ID>",
"in": "path"
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"type": "array",
"collectionFormat": "multi",
"items": {
"type": "string"
},
"default": [],
"in": "query"
},
{
"name": "search",
"description": "Search term to filter your list results. Max length: 256 chars.",
"required": false,
"type": "string",
"x-example": "<SEARCH>",
"default": "",
"in": "query"
}
]
}
@@ -37623,6 +37646,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 +37716,7 @@
"required": [
"key",
"name",
"tagline",
"demoUrl",
"screenshotDark",
"screenshotLight",
@@ -41544,6 +41573,11 @@
"description": "A domain to use for site URLs.",
"x-example": "sites.localhost"
},
"_APP_DOMAIN_FUNCTIONS": {
"type": "string",
"description": "A domain to use for function URLs.",
"x-example": "functions.localhost"
},
"_APP_OPTIONS_FORCE_HTTPS": {
"type": "string",
"description": "Defines if HTTPS is enforced for all requests.",
@@ -41564,6 +41598,7 @@
"_APP_DOMAIN_ENABLED",
"_APP_ASSISTANT_ENABLED",
"_APP_DOMAIN_SITES",
"_APP_DOMAIN_FUNCTIONS",
"_APP_OPTIONS_FORCE_HTTPS",
"_APP_DOMAINS_NAMESERVERS"
]
+26 -3
View File
@@ -8614,7 +8614,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "functions.read",
"scope": "public",
"platforms": [
"server"
],
@@ -17285,6 +17285,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@@ -17500,7 +17501,7 @@
"rate-limit": 0,
"rate-time": 3600,
"rate-key": "url:{url},ip:{ip}",
"scope": "sites.read",
"scope": "public",
"platforms": [
"server"
],
@@ -17714,6 +17715,7 @@
"sveltekit",
"astro",
"remix",
"lynx",
"flutter",
"vite",
"other"
@@ -21021,7 +21023,7 @@
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"type": "array",
"collectionFormat": "multi",
@@ -23125,6 +23127,27 @@
"type": "string",
"x-example": "<USER_ID>",
"in": "path"
},
{
"name": "queries",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: userId, teamId, invited, joined, confirm, roles",
"required": false,
"type": "array",
"collectionFormat": "multi",
"items": {
"type": "string"
},
"default": [],
"in": "query"
},
{
"name": "search",
"description": "Search term to filter your list results. Max length: 256 chars.",
"required": false,
"type": "string",
"x-example": "<SEARCH>",
"default": "",
"in": "query"
}
]
}
+39
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' => [],
+199 -36
View File
@@ -16,6 +16,16 @@ if (System::getEnv('_APP_ENV') === 'development') {
$url = $protocol . '://' . $hostname;
class UseCases
{
public const PORTFOLIO = 'portfolio';
public const STARTER = 'starter';
public const EVENTS = 'events';
public const ECOMMERCE = 'ecommerce';
public const DOCUMENTATION = 'documentation';
public const BLOG = 'blog';
}
const TEMPLATE_FRAMEWORKS = [
'SVELTEKIT' => [
'key' => 'sveltekit',
@@ -77,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',
@@ -134,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)
@@ -143,10 +154,119 @@ function getFramework(string $frameworkEnum, array $overrides)
}
return [
[
'key' => 'template-for-documentation',
'name' => 'Documentation template',
'tagline' => 'Modern site to store your knowledge with a clean design, full-text search, dark mode, and more.',
'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/template-for-documentation-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-documentation-light.png',
'frameworks' => [
getFramework('ASTRO', [
'providerRootDirectory' => './',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'template-for-documentation',
'providerOwner' => 'appwrite',
'providerVersion' => '0.1.*',
'variables' => []
],
[
'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',
'frameworks' => [
getFramework('VITE', [
'providerRootDirectory' => './vite/vitepress',
'outputDirectory' => '404.html',
'installCommand' => 'npm i vitepress && npm install',
'buildCommand' => 'npm run docs:build',
'outputDirectory' => './.vitepress/dist',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => []
],
[
'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',
'frameworks' => [
getFramework('VUE', [
'providerRootDirectory' => './vue/vuepress',
'outputDirectory' => '404.html',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './src/.vuepress/dist',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => []
],
[
'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',
'frameworks' => [
getFramework('REACT', [
'providerRootDirectory' => './react/docusaurus',
'outputDirectory' => '404.html',
'installCommand' => 'npm install',
'buildCommand' => 'npm run build',
'outputDirectory' => './build',
]),
],
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.3.*',
'variables' => []
],
[
'key' => 'nxt-lnk',
'name' => 'Nxt Lnk',
'useCases' => ['portfolio'],
'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',
'frameworks' => [
@@ -160,11 +280,12 @@ return [
'providerVersion' => '0.3.*',
'variables' => []
],
[
'key' => 'magic-portfolio',
'name' => 'Magic Portfolio',
'useCases' => ['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',
'frameworks' => [
@@ -178,11 +299,12 @@ return [
'providerVersion' => '0.3.*',
'variables' => []
],
[
'key' => 'littlelink',
'name' => 'LittleLink',
'useCases' => ['portfolio'],
'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',
'frameworks' => [
@@ -196,11 +318,12 @@ return [
'providerVersion' => '0.3.*',
'variables' => []
],
[
'key' => 'logspot',
'name' => 'Logspot',
'useCases' => ['blog'],
'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',
'frameworks' => [
@@ -220,7 +343,9 @@ return [
[
'key' => 'astro-nano',
'name' => 'Astro Nano',
'useCases' => ['portfolio'],
'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',
'frameworks' => [
@@ -239,7 +364,9 @@ return [
[
'key' => 'astro-starlight',
'name' => 'Astro Starlight',
'useCases' => ['documentation'],
'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',
'frameworks' => [
@@ -258,7 +385,9 @@ return [
[
'key' => 'astro-sphere',
'name' => 'Astro Sphere',
'useCases' => ['portfolio'],
'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',
'frameworks' => [
@@ -277,7 +406,9 @@ return [
[
'key' => 'astro-starlog',
'name' => 'Astro Starlog',
'useCases' => ['blog'],
'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',
'frameworks' => [
@@ -296,7 +427,9 @@ return [
[
'key' => 'onelink',
'name' => 'Onelink',
'useCases' => ['portfolio'],
'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',
'frameworks' => [
@@ -316,7 +449,9 @@ return [
[
'key' => 'starter-for-flutter',
'name' => 'Flutter starter',
'useCases' => ['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' => [
@@ -359,7 +494,9 @@ return [
[
'key' => 'starter-for-js',
'name' => 'JavaScript starter',
'useCases' => ['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' => [
@@ -401,7 +538,9 @@ return [
[
'key' => 'starter-for-angular',
'name' => 'Angular starter',
'useCases' => ['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' => [
@@ -445,7 +584,9 @@ return [
[
'key' => 'starter-for-svelte',
'name' => 'Svelte starter',
'useCases' => ['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' => [
@@ -487,7 +628,9 @@ return [
[
'key' => 'starter-for-react',
'name' => 'React starter',
'useCases' => ['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' => [
@@ -529,7 +672,9 @@ return [
[
'key' => 'starter-for-vue',
'name' => 'Vue starter',
'useCases' => ['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' => [
@@ -571,7 +716,9 @@ return [
[
'key' => 'starter-for-react-native',
'name' => 'React Native starter',
'useCases' => ['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' => [
@@ -614,7 +761,9 @@ return [
[
'key' => 'starter-for-nextjs',
'name' => 'Next.js starter',
'useCases' => ['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' => [
@@ -656,7 +805,9 @@ return [
[
'key' => 'starter-for-nuxt',
'name' => 'Nuxt starter',
'useCases' => ['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' => [
@@ -698,7 +849,9 @@ return [
[
'key' => 'template-for-event',
'name' => 'Event template',
'useCases' => ['events'],
'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',
'frameworks' => [
@@ -734,7 +887,9 @@ return [
[
'key' => 'template-for-portfolio',
'name' => 'Portfolio template',
'useCases' => ['portfolio'],
'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',
'frameworks' => [
@@ -751,7 +906,9 @@ return [
[
'key' => 'template-for-store',
'name' => 'Store template',
'useCases' => ['ecommerce'],
'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',
'frameworks' => [
@@ -793,7 +950,9 @@ return [
[
'key' => 'template-for-blog',
'name' => 'Blog template',
'useCases' => ['blog'],
'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',
'frameworks' => [
@@ -810,7 +969,9 @@ return [
[
'key' => 'astro-starter',
'name' => 'Astro starter',
'useCases' => ['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',
'frameworks' => [
@@ -827,7 +988,9 @@ return [
[
'key' => 'remix-starter',
'name' => 'Remix starter',
'useCases' => ['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',
'frameworks' => [
+2 -1
View File
@@ -496,6 +496,7 @@ App::get('/v1/avatars/qr')
'addQuietzone' => true,
'quietzoneSize' => $margin,
'outputType' => QRCode::OUTPUT_IMAGICK,
'scale' => 15,
]);
$qrcode = new QRCode($options);
@@ -510,7 +511,7 @@ App::get('/v1/avatars/qr')
$response
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
->setContentType('image/png')
->send($image->output('png', 9));
->send($image->output('png', 90));
});
App::get('/v1/avatars/initials')
+1
View File
@@ -62,6 +62,7 @@ App::get('/v1/console/variables')
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
'_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'),
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
]);
+18 -2
View File
@@ -21,6 +21,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Identities;
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Targets;
use Appwrite\Utopia\Database\Validator\Queries\Users;
use Appwrite\Utopia\Request;
@@ -799,9 +800,11 @@ App::get('/v1/users/:userId/memberships')
]
))
->param('userId', '', new UID(), 'User ID.')
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $userId, Response $response, Database $dbForProject) {
->action(function (string $userId, array $queries, string $search, Response $response, Database $dbForProject) {
$user = $dbForProject->getDocument('users', $userId);
@@ -809,6 +812,19 @@ App::get('/v1/users/:userId/memberships')
throw new Exception(Exception::USER_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
// Set internal queries
$queries[] = Query::equal('userInternalId', [$user->getInternalId()]);
$memberships = array_map(function ($membership) use ($dbForProject, $user) {
$team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId'));
@@ -818,7 +834,7 @@ App::get('/v1/users/:userId/memberships')
->setAttribute('userEmail', $user->getAttribute('email'));
return $membership;
}, $user->getAttribute('memberships', []));
}, $dbForProject->find('memberships', $queries));
$response->dynamic(new Document([
'memberships' => $memberships,
+6 -6
View File
@@ -134,12 +134,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
} else {
$comment = new Comment();
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
$latestCommentId = \strval($github->createComment($owner, $repositoryName, $providerPullRequestId, $comment->generateComment()));
if (!empty($latestCommentId)) {
@@ -176,7 +176,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = $comment->getAttribute('providerCommentId', '');
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '', '');
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
}
@@ -219,7 +219,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$commands[] = $resource->getAttribute('commands', '');
}
$deployment = $dbForProject->createDocument('deployments', new Document([
$deployment = Authorization::skip(fn () => $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
@@ -253,14 +253,14 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'providerBranch' => $providerBranch,
'search' => implode(' ', [$deploymentId, $resource->getAttribute('entrypoint', '')]),
'activate' => $activate,
]));
])));
$resource = $resource
->setAttribute('latestDeploymentId', $deployment->getId())
->setAttribute('latestDeploymentInternalId', $deployment->getInternalId())
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource);
Authorization::skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
if ($resource->getCollection() === 'sites') {
$projectId = $project->getId();
+21 -12
View File
@@ -269,8 +269,13 @@ 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') {
throw new AppwriteException(AppwriteException::BUILD_NOT_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 {
throw new AppwriteException(AppwriteException::BUILD_NOT_READY);
}
}
if ($type === 'function') {
@@ -473,24 +478,28 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
);
// Branded 404 override
$isResponseBranded = false;
if ($executionResponse['statusCode'] === 404 && $deployment->getAttribute('adapter', '') === 'static') {
$layout = new View(__DIR__ . '/../views/general/404.phtml');
$executionResponse['body'] = $layout->render();
$executionResponse['headers']['content-length'] = \strlen($executionResponse['body']);
$isResponseBranded = true;
}
// Branded banner for previews
if (\is_null($apiKey) || $apiKey->isBannerDisabled() === false) {
$transformation = new Transformation();
$transformation->addAdapter(new Preview());
$transformation->setInput($executionResponse['body']);
$transformation->setTraits($executionResponse['headers']);
if ($isPreview && $transformation->transform()) {
$executionResponse['body'] = $transformation->getOutput();
if (!$isResponseBranded) {
if (\is_null($apiKey) || $apiKey->isBannerDisabled() === false) {
$transformation = new Transformation();
$transformation->addAdapter(new Preview());
$transformation->setInput($executionResponse['body']);
$transformation->setTraits($executionResponse['headers']);
if ($isPreview && $transformation->transform()) {
$executionResponse['body'] = $transformation->getOutput();
foreach ($executionResponse['headers'] as $key => $value) {
if (\strtolower($key) === 'content-length') {
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
foreach ($executionResponse['headers'] as $key => $value) {
if (\strtolower($key) === 'content-length') {
$executionResponse['headers'][$key] = \strlen($executionResponse['body']);
}
}
}
}
+6 -4
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, "Aeonik Fono");
font-family: var(--font-family-monospace, "Fira Code"), monospace;
font-size: var(--font-size-XS, 12px);
font-style: normal;
font-weight: 400;
Generated
+37 -37
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",
@@ -4205,16 +4205,16 @@
},
{
"name": "utopia-php/migration",
"version": "0.8.1",
"version": "0.8.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab"
"reference": "845fd04ccf5e0edb03c184b864e0596080a432b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab",
"reference": "36ec7af2e6bf78de5d86e1b0a953fd7dcdf69dab",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/845fd04ccf5e0edb03c184b864e0596080a432b8",
"reference": "845fd04ccf5e0edb03c184b864e0596080a432b8",
"shasum": ""
},
"require": {
@@ -4222,7 +4222,7 @@
"ext-curl": "*",
"ext-openssl": "*",
"php": ">=8.1",
"utopia-php/database": "0.61.*",
"utopia-php/database": "0.*.*",
"utopia-php/dsn": "0.2.*",
"utopia-php/framework": "0.33.*",
"utopia-php/storage": "0.18.*"
@@ -4255,9 +4255,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/0.8.1"
"source": "https://github.com/utopia-php/migration/tree/0.8.4"
},
"time": "2025-03-18T07:48:08+00:00"
"time": "2025-03-28T02:08:22+00:00"
},
{
"name": "utopia-php/mongo",
@@ -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",
@@ -7527,16 +7527,16 @@
},
{
"name": "symfony/console",
"version": "v7.2.1",
"version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3"
"reference": "e51498ea18570c062e7df29d05a7003585b19b88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3",
"url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88",
"reference": "e51498ea18570c062e7df29d05a7003585b19b88",
"shasum": ""
},
"require": {
@@ -7600,7 +7600,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.2.1"
"source": "https://github.com/symfony/console/tree/v7.2.5"
},
"funding": [
{
@@ -7616,7 +7616,7 @@
"type": "tidelift"
}
],
"time": "2024-12-11T03:49:26+00:00"
"time": "2025-03-12T08:11:12+00:00"
},
{
"name": "symfony/filesystem",
@@ -8131,16 +8131,16 @@
},
{
"name": "symfony/process",
"version": "v7.2.4",
"version": "v7.2.5",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf"
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
"reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf",
"url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d",
"reference": "87b7c93e57df9d8e39a093d32587702380ff045d",
"shasum": ""
},
"require": {
@@ -8172,7 +8172,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.2.4"
"source": "https://github.com/symfony/process/tree/v7.2.5"
},
"funding": [
{
@@ -8188,7 +8188,7 @@
"type": "tidelift"
}
],
"time": "2025-02-05T08:33:46+00:00"
"time": "2025-03-13T12:21:46+00:00"
},
{
"name": "symfony/string",
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -205,7 +205,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:5.3.0-sites-rc.30
image: appwrite/console:5.3.0-sites-rc.36
restart: unless-stopped
networks:
- appwrite
Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

+18
View File
@@ -0,0 +1,18 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-outside-1_6367_338401" maskUnits="userSpaceOnUse" x="0" y="0" width="30" height="30" fill="black">
<rect fill="white" width="30" height="30"/>
<path d="M1 10.6C1 7.23969 1 5.55953 1.65396 4.27606C2.2292 3.14708 3.14708 2.2292 4.27606 1.65396C5.55953 1 7.23969 1 10.6 1H19.4C22.7603 1 24.4405 1 25.7239 1.65396C26.8529 2.2292 27.7708 3.14708 28.346 4.27606C29 5.55953 29 7.23969 29 10.6V19.4C29 22.7603 29 24.4405 28.346 25.7239C27.7708 26.8529 26.8529 27.7708 25.7239 28.346C24.4405 29 22.7603 29 19.4 29H10.6C7.23969 29 5.55953 29 4.27606 28.346C3.14708 27.7708 2.2292 26.8529 1.65396 25.7239C1 24.4405 1 22.7603 1 19.4V10.6Z"/>
</mask>
<path d="M1 10.6C1 7.23969 1 5.55953 1.65396 4.27606C2.2292 3.14708 3.14708 2.2292 4.27606 1.65396C5.55953 1 7.23969 1 10.6 1H19.4C22.7603 1 24.4405 1 25.7239 1.65396C26.8529 2.2292 27.7708 3.14708 28.346 4.27606C29 5.55953 29 7.23969 29 10.6V19.4C29 22.7603 29 24.4405 28.346 25.7239C27.7708 26.8529 26.8529 27.7708 25.7239 28.346C24.4405 29 22.7603 29 19.4 29H10.6C7.23969 29 5.55953 29 4.27606 28.346C3.14708 27.7708 2.2292 26.8529 1.65396 25.7239C1 24.4405 1 22.7603 1 19.4V10.6Z" fill="#1D1D21"/>
<path d="M4.27606 28.346L4.73005 27.455L4.27606 28.346ZM1.65396 25.7239L0.762954 26.1779L1.65396 25.7239ZM28.346 25.7239L29.237 26.1779L28.346 25.7239ZM25.7239 28.346L26.1779 29.237L25.7239 28.346ZM28.346 4.27606L27.455 4.73005L28.346 4.27606ZM10.6 2H19.4V0H10.6V2ZM28 10.6V19.4H30V10.6H28ZM19.4 28H10.6V30H19.4V28ZM2 19.4V10.6H0V19.4H2ZM10.6 28C8.90334 28 7.69099 27.9992 6.74064 27.9216C5.80197 27.8449 5.20731 27.6982 4.73005 27.455L3.82207 29.237C4.62827 29.6478 5.51543 29.8281 6.57778 29.9149C7.62846 30.0008 8.93634 30 10.6 30V28ZM0 19.4C0 21.0637 -0.000777706 22.3715 0.0850662 23.4222C0.171864 24.4846 0.352173 25.3717 0.762954 26.1779L2.54497 25.27C2.30179 24.7927 2.15512 24.198 2.07842 23.2594C2.00078 22.309 2 21.0967 2 19.4H0ZM4.73005 27.455C3.78924 26.9757 3.02433 26.2108 2.54497 25.27L0.762954 26.1779C1.43407 27.4951 2.50493 28.5659 3.82207 29.237L4.73005 27.455ZM28 19.4C28 21.0967 27.9992 22.309 27.9216 23.2594C27.8449 24.198 27.6982 24.7927 27.455 25.27L29.237 26.1779C29.6478 25.3717 29.8281 24.4846 29.9149 23.4222C30.0008 22.3715 30 21.0637 30 19.4H28ZM19.4 30C21.0637 30 22.3715 30.0008 23.4222 29.9149C24.4846 29.8281 25.3717 29.6478 26.1779 29.237L25.27 27.455C24.7927 27.6982 24.198 27.8449 23.2594 27.9216C22.309 27.9992 21.0967 28 19.4 28V30ZM27.455 25.27C26.9757 26.2108 26.2108 26.9757 25.27 27.455L26.1779 29.237C27.4951 28.5659 28.5659 27.4951 29.237 26.1779L27.455 25.27ZM19.4 2C21.0967 2 22.309 2.00078 23.2594 2.07842C24.198 2.15512 24.7927 2.30179 25.27 2.54497L26.1779 0.762954C25.3717 0.352173 24.4846 0.171864 23.4222 0.0850662C22.3715 -0.000777706 21.0637 0 19.4 0V2ZM30 10.6C30 8.93634 30.0008 7.62846 29.9149 6.57778C29.8281 5.51543 29.6478 4.62827 29.237 3.82207L27.455 4.73005C27.6982 5.20731 27.8449 5.80197 27.9216 6.74064C27.9992 7.69099 28 8.90334 28 10.6H30ZM25.27 2.54497C26.2108 3.02433 26.9757 3.78924 27.455 4.73005L29.237 3.82207C28.5659 2.50493 27.4951 1.43407 26.1779 0.762954L25.27 2.54497ZM10.6 0C8.93634 0 7.62846 -0.000777706 6.57778 0.0850662C5.51543 0.171864 4.62827 0.352173 3.82207 0.762954L4.73005 2.54497C5.20731 2.30179 5.80197 2.15512 6.74064 2.07842C7.69099 2.00078 8.90334 2 10.6 2V0ZM2 10.6C2 8.90334 2.00078 7.69099 2.07842 6.74064C2.15512 5.80197 2.30179 5.20731 2.54497 4.73005L0.762954 3.82207C0.352173 4.62827 0.171864 5.51543 0.0850662 6.57778C-0.000777706 7.62846 0 8.93634 0 10.6H2ZM3.82207 0.762954C2.50493 1.43407 1.43407 2.50493 0.762954 3.82207L2.54497 4.73005C3.02433 3.78924 3.78924 3.02433 4.73005 2.54497L3.82207 0.762954Z" fill="#414146" mask="url(#path-1-outside-1_6367_338401)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 9C8 8.44772 8.44772 8 9 8H12C12.5523 8 13 8.44772 13 9V12C13 12.5523 12.5523 13 12 13H9C8.44772 13 8 12.5523 8 12V9ZM10 11V10H11V11H10Z" fill="#818186"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 18C8 17.4477 8.44772 17 9 17H12C12.5523 17 13 17.4477 13 18V21C13 21.5523 12.5523 22 12 22H9C8.44772 22 8 21.5523 8 21V18ZM10 20V19H11V20H10Z" fill="#818186"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 8C17.4477 8 17 8.44772 17 9V12C17 12.5523 17.4477 13 18 13H21C21.5523 13 22 12.5523 22 12V9C22 8.44772 21.5523 8 21 8H18ZM19 10V11H20V10H19Z" fill="#818186"/>
<path d="M16 9C16 8.44772 15.5523 8 15 8C14.4477 8 14 8.44772 14 9V10C14 10.5523 14.4477 11 15 11C15.5523 11 16 10.5523 16 10V9Z" fill="#818186"/>
<path d="M15 12C15.5523 12 16 12.4477 16 13V14H18C18.5523 14 19 14.4477 19 15C19 15.5523 18.5523 16 18 16H15C14.4477 16 14 15.5523 14 15V13C14 12.4477 14.4477 12 15 12Z" fill="#818186"/>
<path d="M21 14C20.4477 14 20 14.4477 20 15C20 15.5523 20.4477 16 21 16C21.5523 16 22 15.5523 22 15C22 14.4477 21.5523 14 21 14Z" fill="#818186"/>
<path d="M14 18C14 17.4477 14.4477 17 15 17H16C16.5523 17 17 17.4477 17 18C17 18.5523 16.5523 19 16 19V21C16 21.5523 15.5523 22 15 22C14.4477 22 14 21.5523 14 21V18Z" fill="#818186"/>
<path d="M12 16C12.5523 16 13 15.5523 13 15C13 14.4477 12.5523 14 12 14H9C8.44772 14 8 14.4477 8 15C8 15.5523 8.44772 16 9 16H12Z" fill="#818186"/>
<path d="M22 18C22 18.5523 21.5523 19 21 19H19C18.4477 19 18 18.5523 18 18C18 17.4477 18.4477 17 19 17H21C21.5523 17 22 17.4477 22 18Z" fill="#818186"/>
<path d="M21 22C21.5523 22 22 21.5523 22 21C22 20.4477 21.5523 20 21 20H18C17.4477 20 17 20.4477 17 21C17 21.5523 17.4477 22 18 22H21Z" fill="#818186"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

+18
View File
@@ -0,0 +1,18 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-outside-1_6367_338394" maskUnits="userSpaceOnUse" x="0" y="0" width="30" height="30" fill="black">
<rect fill="white" width="30" height="30"/>
<path d="M1 10.6C1 7.23969 1 5.55953 1.65396 4.27606C2.2292 3.14708 3.14708 2.2292 4.27606 1.65396C5.55953 1 7.23969 1 10.6 1H19.4C22.7603 1 24.4405 1 25.7239 1.65396C26.8529 2.2292 27.7708 3.14708 28.346 4.27606C29 5.55953 29 7.23969 29 10.6V19.4C29 22.7603 29 24.4405 28.346 25.7239C27.7708 26.8529 26.8529 27.7708 25.7239 28.346C24.4405 29 22.7603 29 19.4 29H10.6C7.23969 29 5.55953 29 4.27606 28.346C3.14708 27.7708 2.2292 26.8529 1.65396 25.7239C1 24.4405 1 22.7603 1 19.4V10.6Z"/>
</mask>
<path d="M1 10.6C1 7.23969 1 5.55953 1.65396 4.27606C2.2292 3.14708 3.14708 2.2292 4.27606 1.65396C5.55953 1 7.23969 1 10.6 1H19.4C22.7603 1 24.4405 1 25.7239 1.65396C26.8529 2.2292 27.7708 3.14708 28.346 4.27606C29 5.55953 29 7.23969 29 10.6V19.4C29 22.7603 29 24.4405 28.346 25.7239C27.7708 26.8529 26.8529 27.7708 25.7239 28.346C24.4405 29 22.7603 29 19.4 29H10.6C7.23969 29 5.55953 29 4.27606 28.346C3.14708 27.7708 2.2292 26.8529 1.65396 25.7239C1 24.4405 1 22.7603 1 19.4V10.6Z" fill="white"/>
<path d="M4.27606 28.346L4.73005 27.455L4.27606 28.346ZM1.65396 25.7239L0.762954 26.1779L1.65396 25.7239ZM28.346 25.7239L29.237 26.1779L28.346 25.7239ZM25.7239 28.346L26.1779 29.237L25.7239 28.346ZM28.346 4.27606L27.455 4.73005L28.346 4.27606ZM10.6 2H19.4V0H10.6V2ZM28 10.6V19.4H30V10.6H28ZM19.4 28H10.6V30H19.4V28ZM2 19.4V10.6H0V19.4H2ZM10.6 28C8.90334 28 7.69099 27.9992 6.74064 27.9216C5.80197 27.8449 5.20731 27.6982 4.73005 27.455L3.82207 29.237C4.62827 29.6478 5.51543 29.8281 6.57778 29.9149C7.62846 30.0008 8.93634 30 10.6 30V28ZM0 19.4C0 21.0637 -0.000777706 22.3715 0.0850662 23.4222C0.171864 24.4846 0.352173 25.3717 0.762954 26.1779L2.54497 25.27C2.30179 24.7927 2.15512 24.198 2.07842 23.2594C2.00078 22.309 2 21.0967 2 19.4H0ZM4.73005 27.455C3.78924 26.9757 3.02433 26.2108 2.54497 25.27L0.762954 26.1779C1.43407 27.4951 2.50493 28.5659 3.82207 29.237L4.73005 27.455ZM28 19.4C28 21.0967 27.9992 22.309 27.9216 23.2594C27.8449 24.198 27.6982 24.7927 27.455 25.27L29.237 26.1779C29.6478 25.3717 29.8281 24.4846 29.9149 23.4222C30.0008 22.3715 30 21.0637 30 19.4H28ZM19.4 30C21.0637 30 22.3715 30.0008 23.4222 29.9149C24.4846 29.8281 25.3717 29.6478 26.1779 29.237L25.27 27.455C24.7927 27.6982 24.198 27.8449 23.2594 27.9216C22.309 27.9992 21.0967 28 19.4 28V30ZM27.455 25.27C26.9757 26.2108 26.2108 26.9757 25.27 27.455L26.1779 29.237C27.4951 28.5659 28.5659 27.4951 29.237 26.1779L27.455 25.27ZM19.4 2C21.0967 2 22.309 2.00078 23.2594 2.07842C24.198 2.15512 24.7927 2.30179 25.27 2.54497L26.1779 0.762954C25.3717 0.352173 24.4846 0.171864 23.4222 0.0850662C22.3715 -0.000777706 21.0637 0 19.4 0V2ZM30 10.6C30 8.93634 30.0008 7.62846 29.9149 6.57778C29.8281 5.51543 29.6478 4.62827 29.237 3.82207L27.455 4.73005C27.6982 5.20731 27.8449 5.80197 27.9216 6.74064C27.9992 7.69099 28 8.90334 28 10.6H30ZM25.27 2.54497C26.2108 3.02433 26.9757 3.78924 27.455 4.73005L29.237 3.82207C28.5659 2.50493 27.4951 1.43407 26.1779 0.762954L25.27 2.54497ZM10.6 0C8.93634 0 7.62846 -0.000777706 6.57778 0.0850662C5.51543 0.171864 4.62827 0.352173 3.82207 0.762954L4.73005 2.54497C5.20731 2.30179 5.80197 2.15512 6.74064 2.07842C7.69099 2.00078 8.90334 2 10.6 2V0ZM2 10.6C2 8.90334 2.00078 7.69099 2.07842 6.74064C2.15512 5.80197 2.30179 5.20731 2.54497 4.73005L0.762954 3.82207C0.352173 4.62827 0.171864 5.51543 0.0850662 6.57778C-0.000777706 7.62846 0 8.93634 0 10.6H2ZM3.82207 0.762954C2.50493 1.43407 1.43407 2.50493 0.762954 3.82207L2.54497 4.73005C3.02433 3.78924 3.78924 3.02433 4.73005 2.54497L3.82207 0.762954Z" fill="#D8D8DB" mask="url(#path-1-outside-1_6367_338394)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 9C8 8.44772 8.44772 8 9 8H12C12.5523 8 13 8.44772 13 9V12C13 12.5523 12.5523 13 12 13H9C8.44772 13 8 12.5523 8 12V9ZM10 11V10H11V11H10Z" fill="#97979B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 18C8 17.4477 8.44772 17 9 17H12C12.5523 17 13 17.4477 13 18V21C13 21.5523 12.5523 22 12 22H9C8.44772 22 8 21.5523 8 21V18ZM10 20V19H11V20H10Z" fill="#97979B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 8C17.4477 8 17 8.44772 17 9V12C17 12.5523 17.4477 13 18 13H21C21.5523 13 22 12.5523 22 12V9C22 8.44772 21.5523 8 21 8H18ZM19 10V11H20V10H19Z" fill="#97979B"/>
<path d="M16 9C16 8.44772 15.5523 8 15 8C14.4477 8 14 8.44772 14 9V10C14 10.5523 14.4477 11 15 11C15.5523 11 16 10.5523 16 10V9Z" fill="#97979B"/>
<path d="M15 12C15.5523 12 16 12.4477 16 13V14H18C18.5523 14 19 14.4477 19 15C19 15.5523 18.5523 16 18 16H15C14.4477 16 14 15.5523 14 15V13C14 12.4477 14.4477 12 15 12Z" fill="#97979B"/>
<path d="M21 14C20.4477 14 20 14.4477 20 15C20 15.5523 20.4477 16 21 16C21.5523 16 22 15.5523 22 15C22 14.4477 21.5523 14 21 14Z" fill="#97979B"/>
<path d="M14 18C14 17.4477 14.4477 17 15 17H16C16.5523 17 17 17.4477 17 18C17 18.5523 16.5523 19 16 19V21C16 21.5523 15.5523 22 15 22C14.4477 22 14 21.5523 14 21V18Z" fill="#97979B"/>
<path d="M12 16C12.5523 16 13 15.5523 13 15C13 14.4477 12.5523 14 12 14H9C8.44772 14 8 14.4477 8 15C8 15.5523 8.44772 16 9 16H12Z" fill="#97979B"/>
<path d="M22 18C22 18.5523 21.5523 19 21 19H19C18.4477 19 18 18.5523 18 18C18 17.4477 18.4477 17 19 17H21C21.5523 17 22 17.4477 22 18Z" fill="#97979B"/>
<path d="M21 22C21.5523 22 22 21.5523 22 21C22 20.4477 21.5523 20 21 20H18C17.4477 20 17 20.4477 17 21C17 21.5523 17.4477 22 18 22H21Z" fill="#97979B"/>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

+9 -1
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(
+1
View File
@@ -174,6 +174,7 @@ class Exception extends \Exception
public const BUILD_NOT_READY = 'build_not_ready';
public const BUILD_IN_PROGRESS = 'build_in_progress';
public const BUILD_ALREADY_COMPLETED = 'build_already_completed';
public const BUILD_FAILED = 'build_failed';
/** Execution */
public const EXECUTION_NOT_FOUND = 'execution_not_found';
+1
View File
@@ -93,6 +93,7 @@ abstract class Migration
'1.6.0' => 'V21',
'1.6.1' => 'V21',
'1.6.2' => 'V22',
'1.7.0' => 'V23',
];
/**
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace Appwrite\Migration\Version;
use Appwrite\Migration\Migration;
use Exception;
use Throwable;
use Utopia\CLI\Console;
use Utopia\Database\Database;
class V23 extends Migration
{
/**
* @throws Throwable
*/
public function execute(): void
{
/**
* Disable SubQueries for Performance.
*/
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) {
Database::addFilter(
$name,
fn () => null,
fn () => []
);
}
Console::info('Migrating Collections');
$this->migrateCollections();
}
/**
* Migrate Collections.
*
* @return void
* @throws Exception|Throwable
*/
private function migrateCollections(): void
{
$internalProjectId = $this->project->getInternalId();
$collectionType = match ($internalProjectId) {
'console' => 'console',
default => 'projects',
};
$collections = $this->collections[$collectionType];
foreach ($collections as $collection) {
$id = $collection['$id'];
Console::log("Migrating Collection \"{$id}\"");
$this->projectDB->setNamespace("_$internalProjectId");
switch ($id) {
case 'memberships':
// Create roles index
try {
$this->createIndexFromCollection($this->projectDB, $id, '_key_roles');
} catch (Throwable $th) {
Console::warning("'_key_roles' from {$id}: {$th->getMessage()}");
}
break;
}
usleep(50000);
}
}
}
@@ -27,9 +27,9 @@ class XList extends Base
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/functions/runtimes')
->groups(['api', 'functions'])
->groups(['api'])
->desc('List runtimes')
->label('scope', 'functions.read')
->label('scope', 'public')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('sdk', new Method(
namespace: 'functions',
@@ -30,7 +30,7 @@ class XList extends Base
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/functions/templates')
->desc('List templates')
->groups(['api', 'functions'])
->groups(['api'])
->label('scope', 'public')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('sdk', new Method(
@@ -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);
}
}
@@ -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
@@ -646,7 +648,11 @@ class Builds extends Action
Co::join([
Co\go(function () use ($executor, &$response, $project, $deployment, $source, $resource, $runtime, $vars, $command, $cpus, $memory, $timeout, &$err, $version) {
try {
$command = $version === 'v2' ? 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh' : 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh "' . \trim(\escapeshellarg($command), "\'") . '"';
if ($version === 'v2') {
$command = 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh';
} else {
$command = 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh ' . \trim(\escapeshellarg($command));
}
$response = $executor->createRuntime(
deploymentId: $deployment->getId(),
@@ -783,7 +789,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 +797,21 @@ class Builds extends Action
foreach ($response['output'] as $log) {
$logs .= $log['content'];
}
if ($resource->getCollection() === 'sites') {
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Screenshot capturing started. \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,33 +849,53 @@ 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, $apiKey, $resource, $client, &$screenshotError) {
return function () use ($key, $configs, $apiKey, $resource, $client, &$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();
return ['key' => $key, 'screenshot' => $screenshot];
} catch (\Throwable $th) {
$screenshotError = $th->getMessage();
return;
}
};
}, \array_keys($configs)));
if (!\is_null($screenshotError)) {
throw new \Exception($screenshotError);
}
foreach ($screenshots as $data) {
$key = $data['key'];
$screenshot = $data['screenshot'];
$fileId = ID::unique();
$fileName = $fileId . '.png';
@@ -911,12 +932,17 @@ class Builds extends Action
'search' => implode(' ', [$fileId, $fileName]),
'metadata' => ['content_type' => $deviceForFiles->getFileMimeType($path)],
]);
$file = Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file));
Authorization::skip(fn () => $dbForPlatform->createDocument('bucket_' . $bucket->getInternalId(), $file));
$deployment->setAttribute($key, $fileId);
}
$dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$logs = $deployment->getAttribute('buildLogs', '');
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Screenshot capturing finished. \n";
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
->setPayload($deployment->getArrayCopy())
@@ -925,9 +951,39 @@ class Builds extends Action
Console::warning("Screenshot failed to generate:");
Console::warning($th->getMessage());
Console::warning($th->getTraceAsString());
$logs = $deployment->getAttribute('buildLogs', '');
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Screenshot capturing failed. Deployment will continue. \n";
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
}
}
$logs = $deployment->getAttribute('buildLogs', '');
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Deployment finished. \n";
$deployment->setAttribute('buildLogs', $logs);
/** 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 +1122,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;
@@ -1338,15 +1400,9 @@ class Builds extends Action
default => throw new \Exception('Invalid resource type')
};
$previweQrCode = match($resource->getCollection()) {
'functions' => '',
'sites' => 'https://cloud.appwrite.io/v1/avatars/qr?text=' . $previewUrl,
default => throw new \Exception('Invalid resource type')
};
$comment = new Comment();
$comment->parseComment($github->getComment($owner, $repositoryName, $commentId));
$comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl, $previweQrCode);
$comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl);
$github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment());
} finally {
$dbForPlatform->deleteDocument('vcsCommentLocks', $commentId);
@@ -39,7 +39,7 @@ class Create extends Base
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/sites/:siteId/deployments/template')
->desc('Create deployment')
->desc('Create template deployment')
->groups(['api', 'sites'])
->label('scope', 'sites.write')
->label('resourceType', RESOURCE_TYPE_SITES)
@@ -27,8 +27,8 @@ class XList extends Base
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/frameworks')
->desc('List frameworks')
->groups(['api', 'sites'])
->label('scope', 'sites.read')
->groups(['api'])
->label('scope', 'public')
->label('resourceType', RESOURCE_TYPE_SITES)
->label('sdk', new Method(
namespace: 'sites',
@@ -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(
@@ -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);
}
}
+3 -31
View File
@@ -831,7 +831,6 @@ class Deletes extends Action
$this->deleteBuildFiles($deviceForBuilds, $document);
$this->deleteDeploymentFiles($deviceForSites, $document);
$this->deleteDeploymentScreenshots($deviceForFiles, $dbForPlatform, $document);
$this->deleteDeploymentRules($dbForPlatform, $document, $project, $certificates);
});
/**
@@ -846,20 +845,6 @@ class Deletes extends Action
});
}
/**
* Delete rules for all deployments of the site
*/
foreach ($deploymentIds as $deploymentId) {
Console::info("Deleting rules for site " . $siteId . "'s deployment " . $deploymentId);
$this->deleteByGroup('rules', [
Query::equal('type', ['deployment']),
Query::equal('value', [$deploymentId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
});
}
/**
* Delete VCS Repositories and VCS Comments
*/
@@ -900,7 +885,7 @@ class Deletes extends Action
Console::info("Deleting rules for function " . $functionId);
$this->deleteByGroup('rules', [
Query::equal('type', ['deployment']),
Query::equal('deploymentResourceType', ['site']),
Query::equal('deploymentResourceType', ['function']),
Query::equal('deploymentResourceInternalId', [$functionInternalId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) {
@@ -928,7 +913,6 @@ class Deletes extends Action
$deploymentInternalIds[] = $document->getInternalId();
$this->deleteDeploymentFiles($deviceForFunctions, $document);
$this->deleteBuildFiles($deviceForBuilds, $document);
$this->deleteDeploymentRules($dbForPlatform, $document, $project, $certificates);
});
/**
@@ -963,18 +947,6 @@ class Deletes extends Action
$this->deleteRuntimes($getProjectDB, $document, $project);
}
private function deleteDeploymentRules(Database $dbForPlatform, Document $deployment, Document $project, CertificatesAdapter $certificates): void
{
Console::info("Deleting rules for site " . $deployment->getId());
$this->deleteByGroup('rules', [
Query::equal('type', ['deployment']),
Query::equal('deploymentInternalId', [$deployment->getInternalId()]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
});
}
private function deleteDeploymentScreenshots(Device $deviceForFiles, Database $dbForPlatform, Document $deployment): void
{
$screenshotIds = [];
@@ -1125,9 +1097,9 @@ class Deletes extends Action
*/
Console::info("Deleting rules for deployment " . $deploymentId);
$this->deleteByGroup('rules', [
Query::equal('trigger', ['manual']),
Query::equal('trigger', ['deployment']),
Query::equal('type', ['deployment']),
Query::equal('deploymentResourceInternalId', [$deploymentInternalId]),
Query::equal('deploymentInternalId', [$deploymentInternalId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
@@ -33,7 +33,15 @@ class Preview extends Adapter
$banner = <<<EOT
<style>
@import url(https://fonts.bunny.net/css?family=fira-code:400|inter:400);
#appwrite-preview {
min-width: auto;
min-height: auto;
max-width: none;
max-height: none;
width: auto;
height: auto;
padding: 0;
margin: 0;
position: fixed;
@@ -80,7 +88,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 +105,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 +128,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);
}
@@ -9,12 +9,12 @@ class Memberships extends Base
'teamId',
'invited',
'joined',
'confirm'
'confirm',
'roles',
];
/**
* Expression constructor
*
*/
public function __construct()
{
@@ -58,6 +58,12 @@ class ConsoleVariables extends Model
'default' => '',
'example' => 'sites.localhost',
])
->addRule('_APP_DOMAIN_FUNCTIONS', [
'type' => self::TYPE_STRING,
'description' => 'A domain to use for function URLs.',
'default' => '',
'example' => 'functions.localhost',
])
->addRule(
'_APP_OPTIONS_FORCE_HTTPS',
[
@@ -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.',
+78 -41
View File
@@ -9,10 +9,11 @@ use Utopia\System\System;
class Comment
{
// TODO: Add more tips
protected array $tips = [
'Appwrite has a Discord community with over 16 000 members. [Come join us!](https://appwrite.io/discord)',
'You can use [Avatars API](https://appwrite.io/docs/client/avatars?sdk=web-default#avatarsGetQR) to generate QR code for any text or URLs',
'[Cursor pagination](https://appwrite.io/docs/pagination#cursor-pagination) performs better than offset pagination when loading further pages',
'Appwrite has a Discord community with over 16 000 members.',
'You can use Avatars API to generate QR code for any text or URLs.',
'Cursor pagination performs better than offset pagination when loading further pages.',
];
protected string $statePrefix = '[appwrite]: #';
@@ -27,7 +28,7 @@ class Comment
return \count($this->builds) === 0;
}
public function addBuild(Document $project, Document $resource, string $resourceType, string $buildStatus, string $deploymentId, array $action, string $previewUrl, string $previewQrCode): void
public function addBuild(Document $project, Document $resource, string $resourceType, string $buildStatus, string $deploymentId, array $action, string $previewUrl): void
{
// Unique index
$id = $project->getId() . '_' . $resource->getId();
@@ -41,7 +42,6 @@ class Comment
'buildStatus' => $buildStatus,
'deploymentId' => $deploymentId,
'action' => $action,
'previewQrCode' => $previewQrCode,
'previewUrl' => $previewUrl,
];
}
@@ -70,7 +70,6 @@ class Comment
'deploymentId' => $build['deploymentId'],
'action' => $build['action'],
'previewUrl' => $build['previewUrl'],
'previewQrCode' => $build['previewQrCode']
];
} elseif ($build['resourceType'] === 'function') {
$projects[$build['projectId']]['function'][$build['resourceId']] = [
@@ -82,32 +81,36 @@ class Comment
}
}
$i = 0;
foreach ($projects as $projectId => $project) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$text .= "Project name: **{$project['name']}** \nProject ID: `{$projectId}`\n\n";
$text .= "## {$project['name']}\n\n";
$text .= "Project ID: `{$projectId}`\n\n";
$isOpen = $i === 0;
if (\count($project['site']) > 0) {
$text .= "<details" . ($isOpen ? ' open' : '') . ">\n";
$text .= "<summary>Sites (" . \count($project['site']) . ")</summary>\n\n";
$text .= "<br>\n\n";
$text .= "| Site | ID | Status | Previews | Action |\n";
$text .= "| Site | Status | Logs | Preview | QR\n";
$text .= "| :- | :- | :- | :- | :- |\n";
foreach ($project['site'] as $siteId => $site) {
$generateImage = function (string $status) use ($protocol, $hostname) {
$extention = $status === 'building' ? 'gif' : 'png';
$imagesUrl = $protocol . '://' . $hostname . '/console/images/vcs/';
$imageUrl = '<picture><source media="(prefers-color-scheme: dark)" srcset="' . $imagesUrl . 'status-' . $status . '-dark.' . $extention . '"><img alt="' . $status . '" height="25" align="center" src="' . $imagesUrl . 'status-' . $status . '-light.' . $extention . '"></picture>';
$extension = $site['status'] === 'building' ? 'gif' : 'png';
return $imageUrl;
};
$pathLight = '/images/vcs/status-' . $site['status'] . '-light.' . $extension;
$pathDark = '/images/vcs/status-' . $site['status'] . '-dark.' . $extension;
$status = match ($site['status']) {
'waiting' => $generateImage('waiting') . ' Waiting to build',
'processing' => $generateImage('processing') . ' Processing',
'building' => $generateImage('building') . ' Building',
'ready' => $generateImage('ready') . ' Ready',
'failed' => $generateImage('failed') . ' Failed',
'waiting' => $this->generatImage($pathLight, $pathDark, 'Queued', 85) . ' _Queued_',
'processing' => $this->generatImage($pathLight, $pathDark, 'Processing', 85) . ' _Processing_',
'building' => $this->generatImage($pathLight, $pathDark, 'Building', 85) . ' _Building_',
'ready' => $this->generatImage($pathLight, $pathDark, 'Ready', 85) . ' _Ready_',
'failed' => $this->generatImage($pathLight, $pathDark, 'Failed', 85) . ' _Failed_',
};
if ($site['action']['type'] === 'logs') {
@@ -116,33 +119,44 @@ class Comment
$action = '[Authorize](' . $site['action']['url'] . ')';
}
$previews = '[Preview URL](' . $site['previewUrl'] . ') [QR Code](' . $site['previewQrCode'] . ')';
$qrImagePathLight = '/images/vcs/qr-light.svg';
$qrImagePathDark = '/images/vcs/qr-dark.svg';
$text .= "| {$site['name']} | `{$siteId}` | {$status} | {$previews} | {$action} |\n";
$consoleUrl = $protocol . '://' . $hostname . '/v1/avatars/qr?text=' . \urlencode($site['previewUrl']);
$qr = '[' . $this->generatImage($qrImagePathLight, $qrImagePathDark, 'QR Code', 28) . '](' . $consoleUrl . ')';
$preview = '[Preview URL](' . $site['previewUrl'] . ')';
$text .= "| &nbsp;**{$site['name']}**<br>`$siteId`";
$text .= "| {$status}";
$text .= "| {$action}";
$text .= "| {$preview}";
$text .= "| {$qr}";
$text .= "|\n";
}
$text .= "\n\n";
$text .= "\n</details>\n\n";
}
if (\count($project['function']) > 0) {
$text .= "| Function | ID | Status | Action |\n";
$text .= "<details" . ($isOpen ? ' open' : '') . ">\n";
$text .= "<summary>Functions (" . \count($project['function']) . ")</summary>\n\n";
$text .= "<br>\n\n";
$text .= "| Function | ID | Status | Logs |\n";
$text .= "| :- | :- | :- | :- |\n";
foreach ($project['function'] as $functionId => $function) {
$generateImage = function (string $status) use ($protocol, $hostname) {
$extention = $status === 'building' ? 'gif' : 'png';
$imagesUrl = $protocol . '://' . $hostname . '/console/images/vcs/';
$imageUrl = '<picture><source media="(prefers-color-scheme: dark)" srcset="' . $imagesUrl . 'status-' . $status . '-dark.' . $extention . '"><img alt="' . $status . '" height="25" align="center" src="' . $imagesUrl . 'status-' . $status . '-light.' . $extention . '"></picture>';
return $imageUrl;
};
$extension = $site['status'] === 'building' ? 'gif' : 'png';
$status = match ($function['status']) {
'waiting' => $generateImage('waiting') . ' Waiting to build',
'processing' => $generateImage('processing') . ' Processing',
'building' => $generateImage('building') . ' Building',
'ready' => $generateImage('ready') . ' Ready',
'failed' => $generateImage('failed') . ' Failed',
$pathLight = '/images/vcs/status-' . $site['status'] . '-light.' . $extension;
$pathDark = '/images/vcs/status-' . $site['status'] . '-dark.' . $extension;
$status = match ($site['status']) {
'waiting' => $this->generatImage($pathLight, $pathDark, 'Queued', 85) . ' _Queued_',
'processing' => $this->generatImage($pathLight, $pathDark, 'Processing', 85) . ' _Processing_',
'building' => $this->generatImage($pathLight, $pathDark, 'Building', 85) . ' _Building_',
'ready' => $this->generatImage($pathLight, $pathDark, 'Ready', 85) . ' _Ready_',
'failed' => $this->generatImage($pathLight, $pathDark, 'Failed', 85) . ' _Failed_',
};
if ($function['action']['type'] === 'logs') {
@@ -151,22 +165,45 @@ class Comment
$action = '[Authorize](' . $function['action']['url'] . ')';
}
$text .= "| {$function['name']} | `{$functionId}` | {$status} | {$action} |\n";
$text .= "| &nbsp;**{$function['name']}**<br>`$functionId`";
$text .= "| {$status}";
$text .= "| {$action}";
$text .= "|\n";
}
$text .= "\n\n";
$text .= "</details>\n\n";
}
$text .= "</details>\n\n";
$isLast = $i === \count($projects) - 1;
if (\count($projects) > 1 && $isLast) {
$text .= "---\n\n";
}
$i++;
}
$text .= "Only deployments on the production branch are activated automatically. Learn more about Appwrite [Functions](https://appwrite.io/docs/functions) and [Sites](https://appwrite.io/docs/sites).\n\n";
$tip = $this->tips[array_rand($this->tips)];
$text .= "> **💡 Did you know?** \n " . $tip . "\n\n";
$text .= "\n<br>\n\n> [!NOTE]\n> $tip\n\n";
return $text;
}
public function generatImage(string $pathLight, string $pathDark, string $alt, int $width): string
{
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
$imageLight = $protocol . '://' . $hostname . $pathLight;
$imageDark = $protocol . '://' . $hostname . $pathDark;
$imageUrl = '<picture><source media="(prefers-color-scheme: dark)" srcset="' . $imageDark . '"><img alt="' . $alt . '" width="' . $width . '" align="center" src="' . $imageLight . '"></picture>';
return $imageUrl;
}
public function parseComment(string $comment): self
{
$state = \explode("\n", $comment)[0] ?? '';
@@ -24,7 +24,7 @@ class ConsoleConsoleClientTest extends Scope
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(10, $response['body']);
$this->assertCount(11, $response['body']);
$this->assertIsString($response['body']['_APP_DOMAIN_TARGET']);
$this->assertIsInt($response['body']['_APP_STORAGE_LIMIT']);
$this->assertIsInt($response['body']['_APP_COMPUTE_SIZE_LIMIT']);
@@ -33,6 +33,7 @@ class ConsoleConsoleClientTest extends Scope
$this->assertIsBool($response['body']['_APP_VCS_ENABLED']);
$this->assertIsBool($response['body']['_APP_ASSISTANT_ENABLED']);
$this->assertIsString($response['body']['_APP_DOMAIN_SITES']);
$this->assertIsString($response['body']['_APP_DOMAIN_FUNCTIONS']);
$this->assertIsString($response['body']['_APP_OPTIONS_FORCE_HTTPS']);
$this->assertIsString($response['body']['_APP_DOMAINS_NAMESERVERS']);
// When adding new keys, dont forget to update count a few lines above
@@ -321,7 +321,16 @@ class FunctionsCustomClientTest extends Scope
$this->assertContains($template['useCases'][0], ['starter', 'ai']);
}
$this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]);
$this->assertContains('bun-1.0', array_column($templates['body']['templates'][0]['runtimes'], 'name'));
foreach ($templates['body']['templates'] as $template) {
$this->assertThat(
\array_column($template['runtimes'], 'name'),
$this->logicalOr(
$this->containsEqual('bun-1.0'),
$this->containsEqual('dart-2.16'),
),
);
}
// List templates with pagination and filters
$templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([
@@ -1514,7 +1514,7 @@ class FunctionsCustomServerTest extends Scope
$runtimes = $this->client->call(Client::METHOD_GET, '/functions/runtimes', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]));
$this->assertEquals(200, $runtimes['headers']['status-code']);
$this->assertGreaterThan(0, $runtimes['body']['total']);
+1 -1
View File
@@ -134,7 +134,7 @@ class AvatarsTest extends Scope
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $graphQLPayload);
$this->assertEquals(29428, \strlen($qrCode['body']));
$this->assertEquals(9169, \strlen($qrCode['body']));
return $qrCode['body'];
}
@@ -252,7 +252,7 @@ class FunctionsServerTest extends Scope
$runtimes = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
], $this->getHeaders()), $gqlPayload);
]), $gqlPayload);
$this->assertIsArray($runtimes['body']['data']);
$this->assertArrayNotHasKey('errors', $runtimes['body']);
@@ -346,7 +346,7 @@ class ProxyCustomServerTest extends Scope
$ruleId = $rule['body']['$id'];
$rule = $this->updateRuleVerification($ruleId);
$this->assertEquals(401, $rule['headers']['status-code']);
$this->assertEquals(400, $rule['headers']['status-code']);
$this->cleanupRule($ruleId);
}
@@ -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();
}
}
@@ -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']);
@@ -1377,7 +1377,7 @@ class SitesCustomServerTest extends Scope
$frameworks = $this->client->call(Client::METHOD_GET, '/sites/frameworks', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
]));
$this->assertEquals(200, $frameworks['headers']['status-code']);
$this->assertGreaterThan(0, $frameworks['body']['total']);
@@ -1736,6 +1736,14 @@ class SitesCustomServerTest extends Scope
$this->assertStringContainsString("Preview by", $response['body']);
$this->assertGreaterThan($contentLength, $response['headers']['content-length']);
$response = $proxyClient->call(Client::METHOD_GET, '/non-existing-path', followRedirects: false, headers: [
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey,
]);
$this->assertEquals(404, $response['headers']['status-code']);
$this->assertStringContainsString("Page not found", $response['body']);
$this->assertStringNotContainsString("Preview by", $response['body']);
$this->assertGreaterThan($contentLength, $response['headers']['content-length']);
$this->cleanupSite($siteId);
}
@@ -2393,4 +2401,106 @@ class SitesCustomServerTest extends Scope
$this->cleanupSite($siteId);
}
public function testDomainForFailedDeloyment(): void
{
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'Test Site',
'framework' => 'astro',
'buildRuntime' => 'node-22',
'buildCommand' => 'cd random'
]);
$this->assertNotEmpty($siteId);
$domain = $this->setupSiteDomain($siteId);
$this->assertNotEmpty($domain);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
$deployment = $this->createDeployment($siteId, [
'code' => $this->packageSite('astro'),
'activate' => true
]);
$this->assertEquals(202, $deployment['headers']['status-code']);
$deploymentId = $deployment['body']['$id'];
$this->assertNotEmpty($deploymentId);
$this->assertEventually(function () use ($siteId, $deploymentId) {
$deployment = $this->getDeployment($siteId, $deploymentId);
$this->assertEquals('failed', $deployment['body']['status'], json_encode($deployment['body'], JSON_PRETTY_PRINT));
}, 100000, 500);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertStringContainsString('build_failed', $response['body']);
$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);
}
public function testDeploymentCommandEscaping(): void
{
$siteId = $this->setupSite([
'siteId' => ID::unique(),
'name' => 'A site',
'framework' => 'other',
'adapter' => 'static',
'buildRuntime' => 'static-1',
'outputDirectory' => './',
'buildCommand' => "echo 'Hello two'",
'installCommand' => 'echo "Hello one"',
'fallbackFile' => '',
]);
$this->assertNotEmpty($siteId);
$deploymentId = $this->setupDeployment($siteId, [
'code' => $this->packageSite('static'),
'activate' => 'true'
]);
$this->assertNotEmpty($deploymentId);
$deployment = $this->getDeployment($siteId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$this->assertStringContainsString('Hello one', $deployment['body']['buildLogs']);
$this->assertStringContainsString('Hello two', $deployment['body']['buildLogs']);
$this->cleanupSite($siteId);
}
}
+91
View File
@@ -801,6 +801,97 @@ trait UsersBase
return $data;
}
/**
* @depends testGetUser
*/
public function testListUserMemberships(array $data): array
{
/**
* Test for SUCCESS
*/
// create a new team
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'teamId' => 'unique()',
'name' => 'Test Team',
]);
// create a new membership
$membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => $data['userId'],
'roles' => ['new-role'],
]);
// list the memberships
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertEquals($response['body']['memberships'][0]['$id'], $membership['body']['$id']);
$this->assertEquals($response['body']['memberships'][0]['roles'], ['new-role']);
$this->assertEquals($response['body']['total'], 1);
// create another membership with a new role
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'teamId' => 'unique()',
'name' => 'Test Team 2',
]);
$membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'userId' => $data['userId'],
'roles' => ['new-role-2'],
]);
// list out memberships and query by role
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::contains('roles', ['new-role-2'])->toString()
]
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertEquals($response['body']['memberships'][0]['$id'], $membership['body']['$id']);
$this->assertEquals($response['body']['memberships'][0]['roles'], ['new-role-2']);
$this->assertEquals($response['body']['total'], 1);
/**
* Test for FAILURE
*/
// query using equal on array field
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('roles', ['new-role-2'])->toString()
]
]);
$this->assertEquals($response['body']['code'], 400);
$this->assertEquals($response['body']['message'], 'Invalid `queries` param: Invalid query: Cannot query equal on attribute "roles" because it is an array.');
$this->assertEquals($response['body']['type'], 'general_argument_invalid');
return $data;
}
/**
* @depends testGetUser
*/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

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