diff --git a/app/config/errors.php b/app/config/errors.php index 6337c3205a..dc6dcd5daf 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -529,6 +529,11 @@ return [ 'description' => 'Synchronous function execution timed out. Use asynchronous execution instead, or ensure the execution duration doesn\'t exceed 30 seconds.', 'code' => 408, ], + Exception::FUNCTION_TEMPLATE_NOT_FOUND => [ + 'name' => Exception::FUNCTION_TEMPLATE_NOT_FOUND, + 'description' => 'Function Template with the requested ID could not be found.', + 'code' => 404, + ], /** Builds */ Exception::BUILD_NOT_FOUND => [ diff --git a/app/config/function-templates.php b/app/config/function-templates.php new file mode 100644 index 0000000000..dddd8596f4 --- /dev/null +++ b/app/config/function-templates.php @@ -0,0 +1,2048 @@ + [ + 'name' => 'node', + 'versions' => ['21.0', '20.0', '19.0', '18.0', '16.0', '14.5'] + ], + 'PHP' => [ + 'name' => 'php', + 'versions' => ['8.3', '8.2', '8.1', '8.0'] + ], + 'RUBY' => [ + 'name' => 'ruby', + 'versions' => ['3.3', '3.2', '3.1', '3.0'] + ], + 'PYTHON' => [ + 'name' => 'python', + 'versions' => ['3.12', '3.11', '3.10', '3.9', '3.8'] + ], + 'DART' => [ + 'name' => 'dart', + 'versions' => ['3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16'] + ], + 'BUN' => [ + 'name' => 'bun', + 'versions' => ['1.0'] + ], + 'GO' => [ + 'name' => 'go', + 'versions' => ['1.22'] + ] +]; + +function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = []) +{ + return array_map(function ($version) use ($runtime, $commands, $entrypoint, $providerRootDirectory) { + return [ + 'name' => $runtime['name'] . '-' . $version, + 'commands' => $commands, + 'entrypoint' => $entrypoint, + 'providerRootDirectory' => $providerRootDirectory + ]; + }, array_filter($runtime['versions'], function ($version) use ($versionsDenyList) { + return !in_array($version, $versionsDenyList); + })); +} + +return [ + [ + 'icon' => 'icon-lightning-bolt', + 'id' => 'starter', + 'name' => 'Starter function', + 'tagline' => + 'A simple function to get started. Edit this function to explore endless possibilities with Appwrite Functions.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['starter'], + 'runtimes' => [ + ...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/starter'), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/starter' + ), + ...getRuntimes(TEMPLATE_RUNTIMES['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/starter' + ), + ...getRuntimes(TEMPLATE_RUNTIMES['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'), + ...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter'), + ...getRuntimes(TEMPLATE_RUNTIMES['GO'], '', 'main.go', 'go/starter') + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [], + 'scopes' => ["users.read"] + ], + [ + 'icon' => 'icon-upstash', + 'id' => 'query-upstash-vector', + 'name' => 'Query Upstash Vector', + 'tagline' => 'Vector database that stores text embeddings and context retrieval for LLMs', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-upstash-vector' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'UPSTASH_URL', + 'description' => 'The endpoint to connect to your Upstash Vector database. Learn more.', + 'value' => '', + 'placeholder' => 'https://resolved-mallard-84564-eu1-vector.upstash.io', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'UPSTASH_TOKEN', + 'description' => 'Authentication token to access your Upstash Vector database. Learn more.', + 'value' => '', + 'placeholder' => + 'oe4wNTbwHVLcDNa6oceZfhBEABsCNYh43ii6Xdq4bKBH7mq7qJkUmc4cs3ABbYyuVKWZTxVQjiNjYgydn2dkhABNes4NAuDpj7qxUAmZYqGJT78', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-redis', + 'id' => 'query-redis-labs', + 'name' => 'Query Redis Labs', + 'tagline' => 'Key-value database with advanced caching capabilities.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-redis-labs' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'REDIS_HOST', + 'description' => 'The endpoint to connect to your Redis database. Learn more.', + 'value' => '', + 'placeholder' => 'redis-13258.c35.eu-central-1-1.ec2.redns.redis-cloud.com', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'REDIS_PASSWORD', + 'description' => 'Authentication password to access your Redis database. Learn more.', + 'value' => '', + 'placeholder' => 'efNNehiACfcZiwsTAjcK6xiwPyu6Dpdq', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-neo4j', + 'id' => 'query-neo4j-auradb', + 'name' => 'Query Neo4j AuraDB', + 'tagline' => 'Graph database with focus on relations between data.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-neo4j-auradb' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'NEO4J_URI', + 'description' => 'The endpoint to connect to your Neo4j database. Learn more.', + 'value' => '', + 'placeholder' => 'neo4j+s://4tg4mddo.databases.neo4j.io', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'NEO4J_USER', + 'description' => 'Authentication user to access your Neo4j database. Learn more.', + 'value' => '', + 'placeholder' => 'neo4j', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'NEO4J_PASSWORD', + 'description' => 'Authentication password to access your Neo4j database. Learn more.', + 'value' => '', + 'placeholder' => 'mCUc4PbVUQN-_NkTLJLisb6ccnwzQKKhrkF77YMctzx', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-mongodb', + 'id' => 'query-mongo-atlas', + 'name' => 'Query MongoDB Atlas', + 'tagline' => + 'Realtime NoSQL document database with geospecial, graph, search, and vector suport.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-mongo-atlas' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'MONGO_URI', + 'description' => 'The endpoint to connect to your Mongo database. Learn more.', + 'value' => '', + 'placeholder' => + 'mongodb+srv://appwrite:Yx42hafg7Q4fgkxe@cluster0.7mslfog.mongodb.net/?retryWrites=true&w=majority&appName=Appwrite', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-neon', + 'id' => 'query-neon-postgres', + 'name' => 'Query Neon Postgres', + 'tagline' => + 'Reliable SQL database with replication, point-in-time recovery, and pgvector support.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-neon-postgres' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'PGHOST', + 'description' => 'The endpoint to connect to your Postgres database. Learn more.', + 'value' => '', + 'placeholder' => 'ep-still-sea-a792sh84.eu-central-1.aws.neon.tech', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'PGDATABASE', + 'description' => 'Name of our Postgres database. Learn more.', + 'value' => '', + 'placeholder' => 'main', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'PGUSER', + 'description' => 'Name of our Postgres user for authentication. Learn more.', + 'value' => '', + 'placeholder' => 'main_owner', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'PGPASSWORD', + 'description' => 'Password of our Postgres user for authentication. Learn more.', + 'value' => '', + 'placeholder' => 'iQCfaUaaWB3B', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'ENDPOINT_ID', + 'description' => 'Endpoint ID provided for your Postgres database. Learn more.', + 'value' => '', + 'placeholder' => 'ep-still-sea-a792sh84', + 'required' => true, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-open-ai', + 'id' => 'prompt-chatgpt', + 'name' => 'Prompt ChatGPT', + 'tagline' => 'Ask questions and let OpenAI GPT-3.5-turbo answer.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/prompt-chatgpt' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/prompt_chatgpt' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/prompt-chatgpt' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['DART'], + 'dart pub get', + 'lib/main.dart', + 'dart/prompt_chatgpt' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'OPENAI_API_KEY', + 'description' => 'A unique key used to authenticate with the OpenAI API. This is a paid service and you will be charged for each request made to the API. Learn more.', + 'value' => '', + 'placeholder' => 'sk-wzG...vcy', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'OPENAI_MAX_TOKENS', + 'description' => 'The maximum number of tokens that the OpenAI response should contain. Be aware that OpenAI models read and write a maximum number of tokens per API call, which varies depending on the model. For GPT-3.5-turbo, the limit is 4096 tokens. Learn more.', + 'value' => '512', + 'placeholder' => '512', + 'required' => false, + 'type' => 'number' + ] + ] + ], + [ + 'icon' => 'icon-discord', + 'id' => 'discord-command-bot', + 'name' => 'Discord Command Bot', + 'tagline' => 'Simple command using Discord Interactions.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['messaging'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/discord-command-bot' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt && python src/setup.py', + 'src/main.py', + 'python/discord_command_bot' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['GO'], + '', + 'main.go', + 'go/discord-command-bot' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'DISCORD_PUBLIC_KEY', + 'description' => 'Public Key of your application in Discord Developer Portal. Learn more.', + 'value' => '', + 'placeholder' => 'db9...980', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'DISCORD_APPLICATION_ID', + 'description' => 'ID of your application in Discord Developer Portal. Learn more.', + 'value' => '', + 'placeholder' => '427...169', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'DISCORD_TOKEN', + 'description' => 'Bot token of your application in Discord Developer Portal. Learn more.', + 'value' => '', + 'placeholder' => 'NDI...LUfg', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-perspective-api', + 'id' => 'analyze-with-perspectiveapi', + 'name' => 'Analyze with PerspectiveAPI', + 'tagline' => 'Automate moderation by getting toxicity of messages.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/analyze-with-perspectiveapi' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'PERSPECTIVE_API_KEY', + 'description' => 'Google Perspective API key. It authenticates your function, allowing it to interact with the API. Learn more.', + 'value' => '', + 'placeholder' => 'AIzaS...fk-fuM', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-pangea', + 'id' => 'censor-with-redact', + 'name' => 'Censor with Redact', + 'tagline' => + 'Censor sensitive information from a provided text string using Redact API by Pangea.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/censor-with-redact' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/censor_with_redact' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['DART'], + 'dart pub get', + 'lib/main.dart', + 'dart/censor_with_redact' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'PANGEA_REDACT_TOKEN', + 'description' => 'Access token for the Pangea Redact API. Learn more.', + 'value' => '', + 'placeholder' => 'pts_7p4...5wl4', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-document', + 'id' => 'generate-pdf', + 'name' => 'Generate PDF', + 'tagline' => 'Document containing sample invoice in PDF format.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['utilities'], + 'runtimes' => [ + ...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf') + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [] + ], + [ + 'icon' => 'icon-github', + 'id' => 'github-issue-bot', + 'name' => 'GitHub issue bot', + 'tagline' => + 'Automate the process of responding to newly opened issues in a GitHub repository.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['dev-tools'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/github-issue-bot' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'GITHUB_TOKEN', + 'description' => 'A personal access token from GitHub with the necessary permissions to post comments on issues. Learn more.', + 'value' => '', + 'placeholder' => 'ghp_1...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'GITHUB_WEBHOOK_SECRET', + 'description' => 'The secret used to verify that the webhook request comes from GitHub. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-bookmark', + 'id' => 'url-shortener', + 'name' => 'URL shortener', + 'tagline' => 'Generate URL with short ID and redirect to the original URL when visited.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/url-shortener' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database to store the short URLs. Learn more.', + 'value' => 'urlShortener', + 'placeholder' => 'urlShortener', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection to store the short URLs. Learn more.', + 'value' => 'urls', + 'placeholder' => 'urls', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'SHORT_BASE_URL', + 'description' => 'The domain to use for the short URLs. You can use your functions subdomain or a custom domain.', + 'value' => '', + 'placeholder' => 'https://shortdomain.io', + 'required' => true, + 'type' => 'url' + ] + ], + 'scopes' => ["databases.read", "databases.write", "collections.write", "attributes.write", "documents.read", "documents.write"] + ], + [ + 'icon' => 'icon-algolia', + 'id' => 'sync-with-algolia', + 'name' => 'Sync with Algolia', + 'tagline' => 'Intuitive search bar for any data in Appwrite Databases.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/sync-with-algolia' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/sync_with_algolia' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/sync-with-algolia' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the Appwrite database that contains the collection to sync. Learn more.', + 'placeholder' => '64a55...7b912', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection in the Appwrite database to sync. Learn more.', + 'placeholder' => '7c3e8...2a9f1', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'ALGOLIA_APP_ID', + 'description' => 'The ID of the application in Algolia. Learn more.', + 'placeholder' => 'OFCNCOG2CU', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'ALGOLIA_ADMIN_API_KEY', + 'description' => 'The admin API Key for your Algolia service. Learn more.', + 'placeholder' => 'fd0aa...136a8', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'ALGOLIA_INDEX_ID', + 'description' => 'The ID of the index in Algolia where the documents are to be synced. Learn more.', + 'placeholder' => 'my_index', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'ALGOLIA_SEARCH_API_KEY', + 'description' => 'The search API Key for your Algolia service. This key is used for searching the synced index. Learn more.', + 'placeholder' => 'bf2f5...df733', + 'required' => true, + 'type' => 'password' + ], + ], + 'scopes' => ["databases.read", "collections.read", "documents.read"] + ], + [ + 'icon' => 'icon-meilisearch', + 'id' => 'sync-with-meilisearch', + 'name' => 'Sync with Meilisearch', + 'tagline' => 'Intuitive search bar for any data in Appwrite Databases.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/sync-with-meilisearch' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/sync-with-meilisearch' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/sync-with-meilisearch' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['BUN'], + 'bun install', + 'src/main.ts', + 'bun/sync-with-meilisearch' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['RUBY'], + 'bundle install', + 'lib/main.rb', + 'ruby/sync-with-meilisearch' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the Appwrite database that contains the collection to sync. Learn more.', + 'placeholder' => '64a55...7b912', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection in the Appwrite database to sync. Learn more.', + 'placeholder' => '7c3e8...2a9f1', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'MEILISEARCH_ENDPOINT', + 'description' => 'The host URL of the Meilisearch server. Learn more.', + 'placeholder' => 'http://127.0.0.1:7700', + 'required' => true, + 'type' => 'url' + ], + [ + 'name' => 'MEILISEARCH_ADMIN_API_KEY', + 'description' => 'The admin API key for Meilisearch. Learn more.', + 'placeholder' => 'masterKey1234', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'MEILISEARCH_SEARCH_API_KEY', + 'description' => 'API Key for Meilisearch search operations. Learn more.', + 'placeholder' => 'searchKey1234', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'MEILISEARCH_INDEX_NAME', + 'description' => 'Name of the Meilisearch index to which the documents will be synchronized. Learn more.', + 'placeholder' => 'appwrite_index', + 'required' => true, + 'type' => 'text' + ], + ], + 'scopes' => ["databases.read", "collections.read", "documents.read"] + ], + [ + 'icon' => 'icon-vonage', + 'id' => 'whatsapp-with-vonage', + 'name' => 'WhatsApp with Vonage', + 'tagline' => 'Simple bot to answer WhatsApp messages.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['messaging'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/whatsapp-with-vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/whatsapp_with_vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/whatsapp-with-vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['DART'], + 'dart pub get', + 'lib/main.dart', + 'dart/whatsapp-with-vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['RUBY'], + 'bundle install', + 'lib/main.rb', + 'ruby/whatsapp-with-vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['BUN'], + 'bun install', + 'src/main.ts', + 'bun/whatsapp-with-vonage' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'VONAGE_API_KEY', + 'description' => 'API Key to use the Vonage API. Learn more.', + 'value' => '', + 'placeholder' => '62...97', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'VONAGE_API_SECRET', + 'description' => 'Secret to use the Vonage API. Learn more.', + 'placeholder' => 'Zjc...5PH', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'VONAGE_API_SIGNATURE_SECRET', + 'description' => 'Secret to verify the JWT token sent by Vonage. Learn more.', + 'placeholder' => 'NXOi3...IBHDa', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'VONAGE_WHATSAPP_NUMBER', + 'description' => 'Vonage WhatsApp number to send messages from. Learn more.', + 'placeholder' => '+14000000102', + 'required' => true, + 'type' => 'phone' + ] + ] + ], + [ + 'icon' => 'icon-bell', + 'id' => 'push-notification-with-fcm', + 'name' => 'Push notification with FCM', + 'tagline' => 'Send push notifications to your users using Firebase Cloud Messaging (FCM).', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['messaging'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/push-notification-with-fcm' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'FCM_PROJECT_ID', + 'description' => 'A unique identifier for your FCM project. Learn more.', + 'value' => '', + 'placeholder' => 'mywebapp-f6e57', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'FCM_CLIENT_EMAIL', + 'description' => 'Your FCM service account email. Learn more.', + 'placeholder' => 'fcm-adminsdk-2f0de@test-f7q57.iam.gserviceaccount.com', + 'required' => true, + 'type' => 'email' + ], + [ + 'name' => 'FCM_PRIVATE_KEY', + 'description' => 'A unique private key used to authenticate with FCM. Learn more.', + 'placeholder' => '0b683...75675', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'FCM_DATABASE_URL', + 'description' => 'URL of your FCM database. Learn more.', + 'placeholder' => 'https://my-app-f298e.firebaseio.com', + 'required' => true, + 'type' => 'url' + ] + ] + ], + [ + 'icon' => 'icon-mail', + 'id' => 'email-contact-form', + 'name' => 'Email contact form', + 'tagline' => 'Sends an email with the contents of a HTML form.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/email-contact-form' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/email_contact_form' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/email-contact-form' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'SMTP_HOST', + 'description' => 'The address of your SMTP server. Many STMP providers will provide this information in their documentation. Some popular providers include: Mailgun, SendGrid, and Gmail.', + 'value' => '', + 'placeholder' => 'smtp.mailgun.org', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'SMTP_PORT', + 'description' => 'The port of your STMP server. Commnly used ports include 25, 465, and 587.', + 'placeholder' => '25', + 'required' => true, + 'type' => 'number' + ], + [ + 'name' => 'SMTP_USERNAME', + 'description' => 'The username for your SMTP server. This is commonly your email address.', + 'placeholder' => 'no-reply@mywebapp.org', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'SMTP_PASSWORD', + 'description' => 'The password for your SMTP server.', + 'placeholder' => '5up3r5tr0ngP4ssw0rd', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'SUBMIT_EMAIL', + 'description' => 'The email address to send form submissions to.', + 'placeholder' => 'me@mywebapp.org', + 'required' => true, + 'type' => 'email' + ], + [ + 'name' => 'ALLOWED_ORIGINS', + 'description' => 'An optional comma-separated list of allowed origins for CORS (defaults to *). This is an important security measure to prevent malicious users from abusing your function.', + 'value' => '', + 'placeholder' => 'https://mywebapp.org,https://mywebapp.com', + 'required' => false, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-stripe', + 'id' => 'subscriptions-with-stripe', + 'name' => 'Subscriptions with Stripe', + 'tagline' => 'Receive recurring card payments and grant subscribers extra permissions.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/subscriptions-with-stripe' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'STRIPE_SECRET_KEY', + 'description' => 'Secret for sending requests to the Stripe API. Learn more.', + 'placeholder' => 'sk_test_51J...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'STRIPE_WEBHOOK_SECRET', + 'description' => 'Secret used to validate the Stripe Webhook signature. Learn more.', + 'placeholder' => 'whsec_...', + 'required' => true, + 'type' => 'password' + ] + ], + 'scopes' => ["users.read", "sessions.write", "users.write"] + ], + [ + 'icon' => 'icon-stripe', + 'id' => 'payments-with-stripe', + 'name' => 'Payments with Stripe', + 'tagline' => 'Receive card payments and store paid orders.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/payments-with-stripe' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'STRIPE_SECRET_KEY', + 'description' => 'Secret for sending requests to the Stripe API. Learn more.', + 'placeholder' => 'sk_test_51J...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'STRIPE_WEBHOOK_SECRET', + 'description' => 'Secret used to validate the Stripe Webhook signature. Learn more.', + 'placeholder' => 'whsec_...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database to store paid orders. Learn more.', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection to store paid orders. Learn more.', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ] + ], + 'scopes' => ["databases.read", "databases.write", "collections.write", "attributes.write", "documents.read", "documents.write"] + ], + [ + 'icon' => 'icon-chat', + 'id' => 'text-generation-with-huggingface', + 'name' => 'Text generation', + 'tagline' => 'Generate text using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 30, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/text-generation-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-translate', + 'id' => 'language-translation-with-huggingface', + 'name' => 'Language translation', + 'tagline' => 'Translate text using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 30, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/language-translation-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-eye', + 'id' => 'image-classification-with-huggingface', + 'name' => 'Image classification', + 'tagline' => 'Classify images using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => ['buckets.*.files.*.create'], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/image-classification-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'value' => 'ai', + 'placeholder' => 'ai', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'value' => 'image_classification', + 'placeholder' => 'image_classification', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where the images are stored. Learn more.', + 'value' => 'image_classification', + 'placeholder' => 'image_classification', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ], + 'scopes' => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"] + ], + [ + 'icon' => 'icon-eye', + 'id' => 'object-detection-with-huggingface', + 'name' => 'Object detection', + 'tagline' => 'Detect objects in images using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => ['buckets.*.files.*.create'], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/object-detection-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'value' => 'ai', + 'placeholder' => 'ai', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'value' => 'object_detection', + 'placeholder' => 'object_detection', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where the images are stored. Learn more.', + 'value' => 'object_detection', + 'placeholder' => 'object_detection', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ], + "scopes" => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"] + ], + [ + 'icon' => 'icon-text', + 'id' => 'speech-recognition-with-huggingface', + 'name' => 'Speech recognition', + 'tagline' => 'Transcribe audio to text using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => ['buckets.*.files.*.create'], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/speech-recognition-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'value' => 'ai', + 'placeholder' => 'ai', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'value' => 'speech_recognition', + 'placeholder' => 'speech_recognition', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'value' => 'speech_recognition', + 'placeholder' => 'speech_recognition', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ], + "scopes" => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"] + ], + [ + 'icon' => 'icon-chat', + 'id' => 'text-to-speech-with-huggingface', + 'name' => 'Text to speech', + 'tagline' => 'Convert text to speech using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => ['databases.*.collections.*.documents.*.create'], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/text-to-speech-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'value' => 'ai', + 'placeholder' => 'ai', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'value' => 'speech_recognition', + 'placeholder' => 'speech_recognition', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'value' => 'speech_recognition', + 'placeholder' => 'speech_recognition', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ], + "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'generate-with-replicate', + 'name' => 'Generate with Replicate', + 'tagline' => "Generate text, audio and images using Replicate's API.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 300, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/generate-with-replicate' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'REPLICATE_API_KEY', + 'description' => 'A unique key used to authenticate with the Replicate API. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'generate-with-together-ai', + 'name' => 'Generate with Together AI', + 'tagline' => "Generate text and images using Together AI's API.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 300, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/generate-with-together-ai' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'TOGETHER_API_KEY', + 'description' => 'A unique key used to authenticate with the Together AI API. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'placeholder' => 'generated_speech', + 'required' => true, + 'type' => 'text' + ] + ], + "scopes" => ["buckets.write", "files.read", "files.write"] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'chat-with-perplexity-ai', + 'name' => 'Chat with Perplexity AI', + 'tagline' => 'Create a chatbot using the Perplexity AI API.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/chat-with-perplexity-ai' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'PERPLEXITY_API_KEY', + 'description' => 'A unique key used to authenticate with the Perplexity API. Learn more.', + 'placeholder' => 'pplex-68...999', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PERPLEXITY_MAX_TOKENS', + 'description' => 'The maximum number of tokens to generate. Learn more.', + 'placeholder' => '512', + 'required' => false, + 'type' => 'number' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'generate-with-replicate', + 'name' => 'Generate with Replicate', + 'tagline' => "Generate text, audio and images using Replicate's API.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 300, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/generate-with-replicate' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'REPLICATE_API_KEY', + 'description' => 'A unique key used to authenticate with the Replicate API. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-document-search', + 'id' => 'sync-with-pinecone', + 'name' => 'Sync with Pinecone', + 'tagline' => "Sync your Appwrite database with Pinecone's vector database.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 30, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/sync-with-pinecone' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'OPENAI_API_KEY', + 'description' => 'A unique key used to authenticate with the OpenAI API. This is a paid service and you will be charged for each request made to the API. Learn more.', + 'value' => '', + 'placeholder' => 'sk-wzG...vcy', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PINECONE_API_KEY', + 'description' => 'A unique key used to authenticate with the Pinecone API. Learn more.', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PINECONE_INDEX_NAME', + 'description' => 'The name of the index in Pinecone. Learn more.', + 'placeholder' => 'my-index', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the documents are stored. Learn more.', + 'placeholder' => 'my-database', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the documents are stored. Learn more.', + 'placeholder' => 'my-collection', + 'required' => true, + 'type' => 'text' + ] + ], + "scopes" => ["databases.read", "collections.read", "documents.read"] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'rag-with-langchain', + 'name' => 'RAG with LangChain', + 'tagline' => 'Generate text using a LangChain RAG model', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 30, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/rag-with-langchain' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'OPENAI_API_KEY', + 'description' => 'A unique key used to authenticate with the OpenAI API. This is a paid service and you will be charged for each request made to the API. Learn more.', + 'value' => '', + 'placeholder' => 'sk-wzG...vcy', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PINECONE_API_KEY', + 'description' => 'A unique key used to authenticate with the Pinecone API. Learn more.', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PINECONE_INDEX_NAME', + 'description' => 'The name of the index in Pinecone. Learn more.', + 'placeholder' => 'my-index', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the documents are stored. Learn more.', + 'placeholder' => 'my-database', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the documents are stored. Learn more.', + 'placeholder' => 'my-collection', + 'required' => true, + 'type' => 'text' + ] + ], + "scopes" => ["databases.read", "collections.read", "documents.read"] + ], + [ + 'icon' => 'icon-chat', + 'id' => 'speak-with-elevenlabs', + 'name' => 'Speak with ElevenLabs', + 'tagline' => 'Convert text to speech using the ElevenLabs API.', + 'permissions' => ['any'], + 'cron' => '', + 'events' => [], + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/speak-with-elevenlabs' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'ELEVENLABS_API_KEY', + 'description' => 'A unique key used to authenticate with the ElevenLabs API. Learn more.', + 'placeholder' => 'd03xxxxxxxx26', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'placeholder' => 'my-database', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'placeholder' => 'my-collection', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'placeholder' => 'generated_speech', + 'required' => true, + 'type' => 'text' + ] + ], + "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'speak-with-lmnt', + 'name' => 'Speak with LMNT', + 'tagline' => 'Convert text to speech using the LMNT API.', + 'permissions' => ['any'], + 'cron' => '', + 'events' => [], + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/speak-with-lmnt' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'LMNT_API_KEY', + 'description' => 'A unique key used to authenticate with the LMNT API. Learn more.', + 'placeholder' => 'd03xxxxxxxx26', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'placeholder' => 'generated_speech', + 'required' => true, + 'type' => 'text' + ] + ], + "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'chat-with-anyscale', + 'name' => 'Chat with AnyScale', + 'tagline' => 'Create a chatbot using the AnyScale API.', + 'permissions' => ['any'], + 'cron' => '', + 'events' => [], + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/chat-with-anyscale' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'ANYSCALE_API_KEY', + 'description' => 'A unique key used to authenticate with the AnyScale API. Learn more.', + 'placeholder' => 'd03xxxxxxxx26', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'ANYSCALE_MAX_TOKENS', + 'description' => 'The maximum number of tokens that Anyscale responses should contain. Learn more.', + 'placeholder' => '', + 'required' => false, + 'type' => 'number' + ] + ] + ], + [ + 'icon' => 'icon-music-note', + 'id' => 'music-generation-with-huggingface', + 'name' => 'Music generation', + 'tagline' => 'Generate music from a text prompt using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/music-generation-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where generated music is stored. Learn more.', + 'value' => 'generated_music', + 'placeholder' => 'generated_music', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ], + "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'generate-with-fal-ai', + 'name' => 'Generate with fal.ai', + 'tagline' => "Generate images using fal.ai's API.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 300, + 'useCases' => ['ai'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/generate-with-fal-ai' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'FAL_API_KEY', + 'description' => 'A unique key used to authenticate with the fal.ai API. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-currency-dollar', + 'id' => 'subscriptions-with-lemon-squeezy', + 'name' => 'Subscriptions with Lemon Squeezy', + 'tagline' => 'Receive recurring card payments and grant subscribers extra permissions.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/subscriptions-with-lemon-squeezy' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'LEMON_SQUEEZY_API_KEY', + 'description' => 'API key for sending requests to the Lemon Squeezy API. Learn more.', + 'placeholder' => 'eyJ0eXAiOiJ...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'LEMON_SQUEEZY_WEBHOOK_SECRET', + 'description' => 'Secret used to validate the Lemon Squuezy Webhook signature. Learn more.', + 'placeholder' => 'abcd...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'LEMON_SQUEEZY_STORE_ID', + 'description' => 'Store ID required to create a checkout using the Lemon Squeezy API. Learn more.', + 'placeholder' => '123456', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'LEMON_SQUEEZY_VARIANT_ID', + 'description' => 'Variant ID of a product required to create a checkout using the Lemon Squeezy API. Learn more.', + 'placeholder' => 'abcd...', + 'required' => true, + 'type' => 'text' + ] + ], + "scopes" => ["users.read", "users.write"] + ], + [ + 'icon' => 'icon-currency-dollar', + 'id' => 'payments-with-lemon-squeezy', + 'name' => 'Payments with Lemon Squeezy', + 'tagline' => 'Receive card payments and store paid orders.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'useCases' => ['utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/payments-with-lemon-squeezy' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database to store paid orders. Learn more.', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection to store paid orders. Learn more.', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'LEMON_SQUEEZY_API_KEY', + 'description' => 'API key for sending requests to the Lemon Squeezy API. Learn more.', + 'placeholder' => 'eyJ0eXAiOiJ...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'LEMON_SQUEEZY_WEBHOOK_SECRET', + 'description' => 'Secret used to validate the Lemon Squuezy Webhook signature. Learn more.', + 'placeholder' => 'abcd...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'LEMON_SQUEEZY_STORE_ID', + 'description' => 'Store ID required to create a checkout using the Lemon Squeezy API. Learn more.', + 'placeholder' => '123456', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'LEMON_SQUEEZY_VARIANT_ID', + 'description' => 'Variant ID of a product required to create a checkout using the Lemon Squeezy API. Learn more.', + 'placeholder' => 'abcd...', + 'required' => true, + 'type' => 'text' + ] + ], + "scopes" => ["users.read", "users.write"] + ] +]; diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index e4a031383f..a61db99bdf 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2356,3 +2356,64 @@ App::delete('/v1/functions/:functionId/variables/:variableId') $response->noContent(); }); + +App::get('/v1/functions/templates') + ->desc('List function templates') + ->label('scope', 'public') + ->label('sdk.namespace', 'functions') + ->label('sdk.method', 'listTemplates') + ->label('sdk.description', '/docs/references/functions/list-templates.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST) + ->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true) + ->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true) + ->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true) + ->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true) + ->inject('response') + ->action(function (array $runtimes, array $usecases, int $limit, int $offset, Response $response) { + $templates = Config::getParam('function-templates', []); + + if (!empty($runtimes)) { + $templates = \array_filter($templates, function ($template) use ($runtimes) { + return \count(\array_intersect($runtimes, \array_column($template['runtimes'], 'name'))) > 0; + }); + } + + if (!empty($usecases)) { + $templates = \array_filter($templates, function ($template) use ($usecases) { + return \count(\array_intersect($usecases, $template['useCases'])) > 0; + }); + } + + $responseTemplates = \array_slice($templates, $offset, $limit); + $response->dynamic(new Document([ + 'templates' => $responseTemplates, + 'total' => \count($responseTemplates), + ]), Response::MODEL_TEMPLATE_FUNCTION_LIST); + }); + +App::get('/v1/functions/templates/:templateId') + ->desc('Get function template') + ->label('scope', 'public') + ->label('sdk.namespace', 'functions') + ->label('sdk.method', 'getTemplate') + ->label('sdk.description', '/docs/references/functions/get-template.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION) + ->param('templateId', '', new Text(128), 'Template ID.') + ->inject('response') + ->action(function (string $templateId, Response $response) { + $templates = Config::getParam('function-templates', []); + + $template = array_shift(\array_filter($templates, function ($template) use ($templateId) { + return $template['id'] === $templateId; + })); + + if (empty($template)) { + throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND); + } + + $response->dynamic(new Document($template), Response::MODEL_TEMPLATE_FUNCTION); + }); diff --git a/app/init.php b/app/init.php index c6a0680a9d..5ecd78cf00 100644 --- a/app/init.php +++ b/app/init.php @@ -303,6 +303,7 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php'); Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php'); Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php'); Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php'); +Config::load('function-templates', __DIR__ . '/config/function-templates.php'); /** * New DB Filters diff --git a/docs/references/functions/get-template.md b/docs/references/functions/get-template.md new file mode 100644 index 0000000000..ccdcce7352 --- /dev/null +++ b/docs/references/functions/get-template.md @@ -0,0 +1 @@ +Get a function template using ID. You can use template details in [createFunction](/docs/references/cloud/server-nodejs/functions#create) method. \ No newline at end of file diff --git a/docs/references/functions/list-templates.md b/docs/references/functions/list-templates.md new file mode 100644 index 0000000000..ed43b9cbf4 --- /dev/null +++ b/docs/references/functions/list-templates.md @@ -0,0 +1 @@ +List available function templates. You can use template details in [createFunction](/docs/references/cloud/server-nodejs/functions#create) method. \ No newline at end of file diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index dbc7d9425e..884296ff67 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -156,6 +156,7 @@ class Exception extends \Exception public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported'; public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing'; public const FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout'; + public const FUNCTION_TEMPLATE_NOT_FOUND = 'function_template_not_found'; /** Deployments */ public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found'; diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 128fce62c8..d2e78bb310 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -87,7 +87,10 @@ use Appwrite\Utopia\Response\Model\Subscriber; use Appwrite\Utopia\Response\Model\Target; use Appwrite\Utopia\Response\Model\Team; use Appwrite\Utopia\Response\Model\TemplateEmail; +use Appwrite\Utopia\Response\Model\TemplateFunction; +use Appwrite\Utopia\Response\Model\TemplateRuntime; use Appwrite\Utopia\Response\Model\TemplateSMS; +use Appwrite\Utopia\Response\Model\TemplateVariable; use Appwrite\Utopia\Response\Model\Token; use Appwrite\Utopia\Response\Model\Topic; use Appwrite\Utopia\Response\Model\UsageBuckets; @@ -251,6 +254,10 @@ class Response extends SwooleResponse public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet public const MODEL_FUNC_PERMISSIONS = 'funcPermissions'; public const MODEL_HEADERS = 'headers'; + public const MODEL_TEMPLATE_FUNCTION = 'templateFunction'; + public const MODEL_TEMPLATE_FUNCTION_LIST = 'templateFunctionList'; + public const MODEL_TEMPLATE_RUNTIME = 'templateRuntime'; + public const MODEL_TEMPLATE_VARIABLE = 'templateVariable'; // Proxy public const MODEL_PROXY_RULE = 'proxyRule'; @@ -340,6 +347,7 @@ class Response extends SwooleResponse ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION)) + ->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION)) ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION)) ->setModel(new BaseList('Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_LIST, 'providerRepositories', self::MODEL_PROVIDER_REPOSITORY)) ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH)) @@ -409,6 +417,9 @@ class Response extends SwooleResponse ->setModel(new Team()) ->setModel(new Membership()) ->setModel(new Func()) + ->setModel(new TemplateFunction()) + ->setModel(new TemplateRuntime()) + ->setModel(new TemplateVariable()) ->setModel(new Installation()) ->setModel(new ProviderRepository()) ->setModel(new Detection()) diff --git a/src/Appwrite/Utopia/Response/Model/TemplateFunction.php b/src/Appwrite/Utopia/Response/Model/TemplateFunction.php new file mode 100644 index 0000000000..7a3a4cfbd5 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/TemplateFunction.php @@ -0,0 +1,136 @@ +addRule('icon', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template Icon.', + 'default' => '', + 'example' => 'icon-lightning-bolt', + ]) + ->addRule('id', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template ID.', + 'default' => '', + 'example' => 'starter', + ]) + ->addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template Name.', + 'default' => '', + 'example' => 'Starter function', + ]) + ->addRule('tagline', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template Tagline.', + 'default' => '', + 'example' => 'A simple function to get started.', + ]) + ->addRule('permissions', [ + 'type' => self::TYPE_STRING, + 'description' => 'Execution permissions.', + 'default' => [], + 'example' => 'any', + 'array' => true, + ]) + ->addRule('events', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function trigger events.', + 'default' => [], + 'example' => 'account.create', + 'array' => true, + ]) + ->addRule('cron', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function execution schedult in CRON format.', + 'default' => '', + 'example' => '0 0 * * *', + ]) + ->addRule('timeout', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Function execution timeout in seconds.', + 'default' => 15, + 'example' => 300, + ]) + ->addRule('useCases', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function use cases.', + 'default' => [], + 'example' => 'Starter', + 'array' => true, + ]) + ->addRule('runtimes', [ + 'type' => Response::MODEL_TEMPLATE_RUNTIME, + 'description' => 'List of runtimes that can be used with this template.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('instructions', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template Instructions.', + 'default' => '', + 'example' => 'For documentation and instructions check out .', + ]) + ->addRule('vcsProvider', [ + 'type' => self::TYPE_STRING, + 'description' => 'VCS (Version Control System) Provider.', + 'default' => '', + 'example' => 'github', + ]) + ->addRule('providerRepositoryId', [ + 'type' => self::TYPE_STRING, + 'description' => 'VCS (Version Control System) Repository ID', + 'default' => '', + 'example' => 'templates', + ]) + ->addRule('providerOwner', [ + 'type' => self::TYPE_STRING, + 'description' => 'VCS (Version Control System) Owner.', + 'default' => '', + 'example' => 'appwrite', + ]) + ->addRule('providerBranch', [ + 'type' => self::TYPE_STRING, + 'description' => 'VCS (Version Control System) branch name', + 'default' => '', + 'example' => 'main', + ]) + ->addRule('variables', [ + 'type' => Response::MODEL_TEMPLATE_VARIABLE, + 'description' => 'Function variables.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string + { + return 'Template Function'; + } + + /** + * Get Type + * + * @return string + */ + public function getType(): string + { + return Response::MODEL_TEMPLATE_FUNCTION; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/TemplateRuntime.php b/src/Appwrite/Utopia/Response/Model/TemplateRuntime.php new file mode 100644 index 0000000000..c08ea9b32a --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/TemplateRuntime.php @@ -0,0 +1,59 @@ +addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'Runtime Name.', + 'default' => '', + 'example' => 'node-19.0', + ]) + ->addRule('commands', [ + 'type' => self::TYPE_STRING, + 'description' => 'The build command used to build the deployment.', + 'default' => '', + 'example' => 'npm install', + ]) + ->addRule('entrypoint', [ + 'type' => self::TYPE_STRING, + 'description' => 'The entrypoint file used to execute the deployment.', + 'default' => '', + 'example' => 'index.js', + ]) + ->addRule('providerRootDirectory', [ + 'type' => self::TYPE_STRING, + 'description' => 'Path to function in VCS (Version Control System) repository', + 'default' => '', + 'example' => 'node/starter', + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string + { + return 'Template Runtime'; + } + + /** + * Get Type + * + * @return string + */ + public function getType(): string + { + return Response::MODEL_TEMPLATE_RUNTIME; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/TemplateVariable.php b/src/Appwrite/Utopia/Response/Model/TemplateVariable.php new file mode 100644 index 0000000000..b0fd919dbf --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/TemplateVariable.php @@ -0,0 +1,65 @@ +addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'Variable Name.', + 'default' => '', + 'example' => 'APPWRITE_DATABASE_ID', + ]) + ->addRule('description', [ + 'type' => self::TYPE_STRING, + 'description' => 'Variable Description.', + 'default' => '', + 'example' => 'The ID of the Appwrite database that contains the collection to sync.', + ]) + ->addRule('placeholder', [ + 'type' => self::TYPE_STRING, + 'description' => 'Variable Placeholder.', + 'default' => '', + 'example' => '64a55...7b912', + ]) + ->addRule('required', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Is the variable required?', + 'default' => false, + 'example' => false, + ]) + ->addRule('type', [ + 'type' => self::TYPE_STRING, + 'description' => 'Variable Type.', + 'default' => '', + 'example' => 'password', + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string + { + return 'Template Variable'; + } + + /** + * Get Type + * + * @return string + */ + public function getType(): string + { + return Response::MODEL_TEMPLATE_VARIABLE; + } +} diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 4e501692fa..f94cb3744a 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -8,6 +8,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\Config\Config; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -963,4 +964,131 @@ class FunctionsCustomClientTest extends Scope return []; } + + public function testListTemplates() + { + /** + * Test for SUCCESS + */ + $expectedTemplates = array_slice(Config::getParam('function-templates', []), 0, 25); + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders())); + + $this->assertEquals(200, $templates['headers']['status-code']); + $this->assertGreaterThan(0, $templates['body']['total']); + $this->assertIsArray($templates['body']['templates']); + $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + $this->assertArrayHasKey('useCases', $templates['body']['templates'][0]); + for ($i = 0; $i < 25; $i++) { + $this->assertEquals($expectedTemplates[$i]['name'], $templates['body']['templates'][$i]['name']); + $this->assertEquals($expectedTemplates[$i]['id'], $templates['body']['templates'][$i]['id']); + $this->assertEquals($expectedTemplates[$i]['icon'], $templates['body']['templates'][$i]['icon']); + $this->assertEquals($expectedTemplates[$i]['tagline'], $templates['body']['templates'][$i]['tagline']); + $this->assertEquals($expectedTemplates[$i]['useCases'], $templates['body']['templates'][$i]['useCases']); + $this->assertEquals($expectedTemplates[$i]['vcsProvider'], $templates['body']['templates'][$i]['vcsProvider']); + $this->assertEquals($expectedTemplates[$i]['runtimes'], $templates['body']['templates'][$i]['runtimes']); + $this->assertEquals($expectedTemplates[$i]['variables'], $templates['body']['templates'][$i]['variables']); + } + + $templates_offset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders()), [ + 'limit' => 1, + 'offset' => 2 + ]); + + $this->assertEquals(200, $templates_offset['headers']['status-code']); + $this->assertEquals(1, $templates_offset['body']['total']); + // assert that offset works as expected + $this->assertEquals($templates['body']['templates'][2]['id'], $templates_offset['body']['templates'][0]['id']); + + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders()), [ + 'useCases' => ['starter', 'ai'], + 'runtimes' => ['bun-1.0', 'dart-2.16'] + ]); + + $this->assertEquals(200, $templates['headers']['status-code']); + $this->assertGreaterThanOrEqual(3, $templates['body']['total']); + $this->assertIsArray($templates['body']['templates']); + foreach ($templates['body']['templates'] as $template) { + $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')); + + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 5, + 'offset' => 2, + 'useCases' => ['databases'], + 'runtimes' => ['node-16.0'] + ]); + + $this->assertEquals(200, $templates['headers']['status-code']); + $this->assertEquals(5, $templates['body']['total']); + $this->assertIsArray($templates['body']['templates']); + $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + foreach ($templates['body']['templates'] as $template) { + $this->assertContains($template['useCases'][0], ['databases']); + } + $this->assertContains('node-16.0', array_column($templates['body']['templates'][0]['runtimes'], 'name')); + + /** + * Test for FAILURE + */ + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders()), [ + 'limit' => 5001, + 'offset' => 10, + ]); + + $this->assertEquals(400, $templates['headers']['status-code']); + $this->assertEquals('Invalid `limit` param: Value must be a valid range between 1 and 5,000', $templates['body']['message']); + + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 5, + 'offset' => 5001, + ]); + + $this->assertEquals(400, $templates['headers']['status-code']); + $this->assertEquals('Invalid `offset` param: Value must be a valid range between 0 and 5,000', $templates['body']['message']); + } + + public function testGetTemplate() + { + /** + * Test for SUCCESS + */ + $template = $this->client->call(Client::METHOD_GET, '/functions/templates/query-neo4j-auradb', array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders()), []); + + $this->assertEquals(200, $template['headers']['status-code']); + $this->assertIsArray($template['body']); + $this->assertEquals('query-neo4j-auradb', $template['body']['id']); + $this->assertEquals('Query Neo4j AuraDB', $template['body']['name']); + $this->assertEquals('icon-neo4j', $template['body']['icon']); + $this->assertEquals('Graph database with focus on relations between data.', $template['body']['tagline']); + $this->assertEquals(['databases'], $template['body']['useCases']); + $this->assertEquals('github', $template['body']['vcsProvider']); + + /** + * Test for FAILURE + */ + $template = $this->client->call(Client::METHOD_GET, '/functions/templates/invalid-template-id', array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders()), []); + + $this->assertEquals(404, $template['headers']['status-code']); + $this->assertEquals('Function Template with the requested ID could not be found.', $template['body']['message']); + } }