Merge branch 'main' into tailwind-integration

This commit is contained in:
Jesse Winton
2024-08-26 09:04:09 -04:00
28 changed files with 1469 additions and 730 deletions

5
.gitignore vendored
View File

@@ -15,4 +15,7 @@ package-lock.json
.vscode
# Sentry Config File
.sentryclirc
.history
.history
terraform/**/.t*
terraform/**/.env
terraform/**/**/*.tfstate*

192
STYLE.md
View File

@@ -6,17 +6,17 @@ Read this document carefully before making PRs to the Appwrite Website repo.
The Appwrite documentation is meant to provide general guidance that's:
- Unopinionated
- Focused on the correct use of Appwrite product
- Includes examples for all relevant and applicable SDKs
- Agnostic to the user's implementation and stack.
- Unopinionated
- Focused on the correct use of Appwrite product
- Includes examples for all relevant and applicable SDKs
- Agnostic to the user's implementation and stack.
Examples of things not fit for docs, and better as a blog or video:
- General programming advice
- Opinionated implementation patterns like MVVM, factory methods, etc.
- Examples that only include a select subset of Appwrite SDKs.
- Examples that do not work for all developers using Appwrite, but specific to Appwrite + technology.
- General programming advice
- Opinionated implementation patterns like MVVM, factory methods, etc.
- Examples that only include a select subset of Appwrite SDKs.
- Examples that do not work for all developers using Appwrite, but specific to Appwrite + technology.
Note that the tutorials and blogs available on the Appwrite blog and docs are meant for these types of information.
@@ -28,39 +28,39 @@ Appwrite's navigation increases in complexity from top down. We expect users to
Introduction Section:
- [Homes](https://appwrite.io/docs)
- [Quick start](https://appwrite.io/docs/quick-start)
- [Tutorial](https://appwrite.io/docs/tutorial)
- [SDKs](https://appwrite.io/docs/sdks)
- [API references](https://appwrite.io/docs/references)
- [Homes](https://appwrite.io/docs)
- [Quick start](https://appwrite.io/docs/quick-start)
- [Tutorial](https://appwrite.io/docs/tutorial)
- [SDKs](https://appwrite.io/docs/sdks)
- [API references](https://appwrite.io/docs/references)
Products section:
- [Auth](https://appwrite.io/docs/products/auth)
- [Databases](https://appwrite.io/docs/products/databases)
- [Functions](https://appwrite.io/docs/products/functions)
- [Storage](https://appwrite.io/docs/products/storage)
- [Messaging](https://appwrite.io/docs/products/messaging)
- [AI](https://appwrite.io/docs/products/ai)
- [Auth](https://appwrite.io/docs/products/auth)
- [Databases](https://appwrite.io/docs/products/databases)
- [Functions](https://appwrite.io/docs/products/functions)
- [Storage](https://appwrite.io/docs/products/storage)
- [Messaging](https://appwrite.io/docs/products/messaging)
- [AI](https://appwrite.io/docs/products/ai)
APIs section:
- [GraphQL](https://appwrite.io/docs/apis/graphql)
- [REST](https://appwrite.io/docs/apis/rest)
- [Realtime](https://appwrite.io/docs/apis/realtime)
- [GraphQL](https://appwrite.io/docs/apis/graphql)
- [REST](https://appwrite.io/docs/apis/rest)
- [Realtime](https://appwrite.io/docs/apis/realtime)
Tooling section:
- [Command Line](https://appwrite.io/docs/command-line)
- [Command center](https://appwrite.io/docs/tooling/command-center)
- [Assistant](https://appwrite.io/docs/tooling/assistant)
- [CLI](https://appwrite.io/docs/command-line)
- [Command center](https://appwrite.io/docs/tooling/command-center)
- [Assistant](https://appwrite.io/docs/tooling/assistant)
Advanced section:
- [Platform](https://appwrite.io/docs/advanced/platform)
- [Migrations](https://appwrite.io/docs/advanced/migrations)
- [Self-hosting](https://appwrite.io/docs/advanced/self-hosting)
- [Security](https://appwrite.io/docs/advanced/security)
- [Platform](https://appwrite.io/docs/advanced/platform)
- [Migrations](https://appwrite.io/docs/advanced/migrations)
- [Self-hosting](https://appwrite.io/docs/advanced/self-hosting)
- [Security](https://appwrite.io/docs/advanced/security)
Here's the intended purpose and structure of each section.
@@ -70,10 +70,10 @@ This section is focused on introducing what Appwrite is and giving examples to t
Documentation here is focused on a **single flow** which means a single platform/framework + Appwrite.
Content here is not specific to a specific product, but usually covers multiple Appwrite products.
- If your tutorial can be followed in about 15 minutes and fits on one page, write it under quick start
- If you're writing a long piece of documentation that integrates Appwrite with another technology, with lots of details that's opinionated or isn't relevant for all use cases, write it under tutorial. This is similar to "cook book" at other organizations.
- If you have information like helpers and methods that are only on SDKs but not the API, they go under SDK
- API references are generated from source from the appwrite/appwrite repo
- If your tutorial can be followed in about 15 minutes and fits on one page, write it under quick start
- If you're writing a long piece of documentation that integrates Appwrite with another technology, with lots of details that's opinionated or isn't relevant for all use cases, write it under tutorial. This is similar to "cook book" at other organizations.
- If you have information like helpers and methods that are only on SDKs but not the API, they go under SDK
- API references are generated from source from the appwrite/appwrite repo
### Products
@@ -82,17 +82,17 @@ Code examples should cover **all available SDKs**.
Each product page has three main sections
- Introduction
- Overview - Describes at a high level, why you might need this product
- Quick start - Shows the most basic and quickest example to make something happen with a product. Keep it really short.
- Concept
- These pages usually align with sections shown in the product in the Appwrite Console.
- Focused on describing concepts a user should know, but not actions you might take.
- Cover all the details
- Journeys
- These pages focus on common actions and work flows
- Detailed examples that span many concepts
- Like cookbook at other organizations' documentation.
- Introduction
- Overview - Describes at a high level, why you might need this product
- Quick start - Shows the most basic and quickest example to make something happen with a product. Keep it really short.
- Concept
- These pages usually align with sections shown in the product in the Appwrite Console.
- Focused on describing concepts a user should know, but not actions you might take.
- Cover all the details
- Journeys
- These pages focus on common actions and work flows
- Detailed examples that span many concepts
- Like cookbook at other organizations' documentation.
### APIs section
@@ -106,10 +106,10 @@ Describes tools that help you work with Appwrite, but are usually non-essential
For information that's not used commonly during the development cycle.
- Platform: covers concepts that apply to the entire Appwrite Cloud platform, like API keys, rate limits, etc.
- Migrations: covers migrations feature of Appwrite that helps you move data around.
- Security: purely information about measures Appwrite use to ensure security of the platform and data.
- Self-hosting: The Appwrite self-hosted platform is meant to behave identically to Cloud after being configured corrrectly. This section focuses on how to configure Appwrite self-hosted such that it behaves like Cloud.
- Platform: covers concepts that apply to the entire Appwrite Cloud platform, like API keys, rate limits, etc.
- Migrations: covers migrations feature of Appwrite that helps you move data around.
- Security: purely information about measures Appwrite use to ensure security of the platform and data.
- Self-hosting: The Appwrite self-hosted platform is meant to behave identically to Cloud after being configured corrrectly. This section focuses on how to configure Appwrite self-hosted such that it behaves like Cloud.
## Documentation sources
@@ -117,22 +117,22 @@ The Appwrite docs are compiled from different repositories. Here are the signifi
[appwrite/website](https://github.com/appwrite/website):
- Tutorials
- Quick starts
- Product, API, Tooling and Advanced sections
- Tutorials
- Quick starts
- Product, API, Tooling and Advanced sections
[appwrite/appwrite](https://github.com/appwrite/appwrite):
- [API Reference](https://appwrite.io/docs/references) pages
- API specification
- API description
- API endpoint description
- API request parameters
- API response model
- [API Reference](https://appwrite.io/docs/references) pages
- API specification
- API description
- API endpoint description
- API request parameters
- API response model
[appwrite/sdk-generator](https://github.com/appwrite/sdk-generator):
- Generated examples
- Generated examples
## Markdown Style guidelines
@@ -141,10 +141,10 @@ the tone and voice remains consistent.
### Headings
- All titles, headings, buttons, and labels should be written in **sentence case**. If you're not sure what sentence case should look like, check [APA's style guide](https://apastyle.apa.org/style-grammar-guidelines/capitalization/sentence-case) or check with ChatGPT and other LLMs which reliably converts titles to sentence case.
- All headings in a docs page begin with `# Heading` then `## Heading` and `### Heading`. Internally, they're converted to H2 to H4 tags.
- All headings should have an ID label, for example `# Cool heading {% #cool-heading %}` the `#cool-heading` ID will be used to generate the table of contents and add links to the heading.
- Prefer verbs over gerunds, for example, say "Create documents" not "Creating documents".
- All titles, headings, buttons, and labels should be written in **sentence case**. If you're not sure what sentence case should look like, check [APA's style guide](https://apastyle.apa.org/style-grammar-guidelines/capitalization/sentence-case) or check with ChatGPT and other LLMs which reliably converts titles to sentence case.
- All headings in a docs page begin with `# Heading` then `## Heading` and `### Heading`. Internally, they're converted to H2 to H4 tags.
- All headings should have an ID label, for example `# Cool heading {% #cool-heading %}` the `#cool-heading` ID will be used to generate the table of contents and add links to the heading.
- Prefer verbs over gerunds, for example, say "Create documents" not "Creating documents".
### Extended Markdoc components
@@ -152,14 +152,14 @@ Appwrite's documentation uses extended markdown syntax. You can find all of the
### Screenshots
- When contributing upload original screenshots. The Appwrite design team will edit the screenshot to be consistent with other screenshots in the docs.
- Screenshots must be 16:9
- Screnshots should be taken in a 1400 x 900 view port on 3x DPR in browser developer tools.
- Use generic and sensible organization, project, and resource names. Avoid names like `test`, `demo`, or `sdlkfj`.
- All screenshot should be take from a user named Walter O'Brien. You can change the name of your current user by going to your Appwrite Console and clicking the **top right profile icon** > **Your Account** > **Name**.
- Screenshots are stored in the `/images/docs/` folder, in a parent folder that is consistent with the path of the docs that reference the image.
- All screenshots must be both dark and light mode, with `/path/` holding the lightmode version and `/path/dark/` holding the dark mode version.
- Screenshots should be uploaded as un-edited original. Request help from the Appwrite design team to help you edit and refine your photos according to our guidelines.
- When contributing upload original screenshots. The Appwrite design team will edit the screenshot to be consistent with other screenshots in the docs.
- Screenshots must be 16:9
- Screnshots should be taken in a 1400 x 900 view port on 3x DPR in browser developer tools.
- Use generic and sensible organization, project, and resource names. Avoid names like `test`, `demo`, or `sdlkfj`.
- All screenshot should be take from a user named Walter O'Brien. You can change the name of your current user by going to your Appwrite Console and clicking the **top right profile icon** > **Your Account** > **Name**.
- Screenshots are stored in the `/images/docs/` folder, in a parent folder that is consistent with the path of the docs that reference the image.
- All screenshots must be both dark and light mode, with `/path/` holding the lightmode version and `/path/dark/` holding the dark mode version.
- Screenshots should be uploaded as un-edited original. Request help from the Appwrite design team to help you edit and refine your photos according to our guidelines.
```md
{% only_dark %}
@@ -210,41 +210,41 @@ Split content such that each piece makes sense without reading dependents or exp
### Release prep
- [ ] Add new version to [src/lib/utils/references.ts](src/lib/utils/references.ts)
- [ ] Point Cloud to new version in [src/routes/docs/references/[version]/[platform]/[service]/+page.server.ts](src/routes/docs/references/[version]/[platform]/[service]/+page.server.ts)
- [ ] Update install command in [/workspaces/website/src/routes/docs/advanced/self-hosting/+page.markdoc](/workspaces/website/src/routes/docs/advanced/self-hosting/+page.markdoc)
- [ ] Update events [src/partials/[product]-events.md](src/partials/)
- [ ] Update response code [src/routes/docs/advanced/platform/response-codes/+page.markdoc](src/routes/docs/advanced/platform/response-codes/+page.markdoc)
- [ ] Bump latest SDK versions in SDKs page, quick start, and tutorials
- [ ] Create new sections for new products
- [ ] Create new concept and journey pages for new features
- [ ] Update docs for breaking changes
- [ ] Add new version to [src/lib/utils/references.ts](src/lib/utils/references.ts)
- [ ] Point Cloud to new version in [src/routes/docs/references/[version]/[platform]/[service]/+page.server.ts](src/routes/docs/references/[version]/[platform]/[service]/+page.server.ts)
- [ ] Update install command in [/workspaces/website/src/routes/docs/advanced/self-hosting/+page.markdoc](/workspaces/website/src/routes/docs/advanced/self-hosting/+page.markdoc)
- [ ] Update events [src/partials/[product]-events.md](src/partials/)
- [ ] Update response code [src/routes/docs/advanced/platform/response-codes/+page.markdoc](src/routes/docs/advanced/platform/response-codes/+page.markdoc)
- [ ] Bump latest SDK versions in SDKs page, quick start, and tutorials
- [ ] Create new sections for new products
- [ ] Create new concept and journey pages for new features
- [ ] Update docs for breaking changes
### Documenting a new API
- Add a new .md file describing the new API here: <https://github.com/appwrite/appwrite/tree/main/docs/references>
- Add descriptions for methods and parameters in the controller code: <https://github.com/appwrite/appwrite/tree/main/app/controllers/api>
- Check new response models have meaningful descriptions
- Add a new .md file describing the new API here: <https://github.com/appwrite/appwrite/tree/main/docs/references>
- Add descriptions for methods and parameters in the controller code: <https://github.com/appwrite/appwrite/tree/main/app/controllers/api>
- Check new response models have meaningful descriptions
### Adding a new quickstart
- Copy a quick start from the [src/routes/docs/quick-starts](src/routes/docs/quick-starts) folder.
- Add a new entry and logo to [src/routes/docs/quick-starts/+page.svelte](src/routes/docs/quick-starts/+page.svelte)
- If you need a new logo, contact the Appwrite team to add one to Pink design.
- Update the content of your tutorial. Remember to update the front matter!
- Try to be consistent in both the quickstart's content and format when compared to existing quick starts
- Add the quick start to the footer and front page of Appwrite
- Use sections for steps on your page
- Copy a quick start from the [src/routes/docs/quick-starts](src/routes/docs/quick-starts) folder.
- Add a new entry and logo to [src/routes/docs/quick-starts/+page.svelte](src/routes/docs/quick-starts/+page.svelte)
- If you need a new logo, contact the Appwrite team to add one to Pink design.
- Update the content of your tutorial. Remember to update the front matter!
- Try to be consistent in both the quickstart's content and format when compared to existing quick starts
- Add the quick start to the footer and front page of Appwrite
- Use sections for steps on your page
### Adding a new tutorial
- Copy a tutorial from the [src/routes/docs/tutorials](src/routes/docs/tutorials) folder.
- Update the `+page.ts`'s redirect, for example, the Android tutorial has this: [src/routes/docs/tutorials/android/+page.ts](src/routes/docs/tutorials/android/+page.ts)
- Update [src/routes/docs/tutorials/+page.svelte](src/routes/docs/tutorials/+page.svelte) and add your new tutorial
- Update [src/routes/docs/tutorials/android/+layout.ts](src/routes/docs/tutorials/android/+layout.ts) and add your new tutorial
- Add the content of your tutorial. Keep pages short, separated by a different distinct feature for each step.
- If you need a new logo, contact the Appwrite team to add one to Pink design.
- Add the tutorial to the footer and front page of Appwrite
- Copy a tutorial from the [src/routes/docs/tutorials](src/routes/docs/tutorials) folder.
- Update the `+page.ts`'s redirect, for example, the Android tutorial has this: [src/routes/docs/tutorials/android/+page.ts](src/routes/docs/tutorials/android/+page.ts)
- Update [src/routes/docs/tutorials/+page.svelte](src/routes/docs/tutorials/+page.svelte) and add your new tutorial
- Update [src/routes/docs/tutorials/android/+layout.ts](src/routes/docs/tutorials/android/+layout.ts) and add your new tutorial
- Add the content of your tutorial. Keep pages short, separated by a different distinct feature for each step.
- If you need a new logo, contact the Appwrite team to add one to Pink design.
- Add the tutorial to the footer and front page of Appwrite
## Language and diction

View File

@@ -10,6 +10,7 @@
body: JSON.stringify({
name,
email,
cloud: true /* not optional on the growth endpoint. */,
}),
},
);

View File

@@ -1,5 +1,5 @@
export const GITHUB_STARS = '42.8K';
export const BANNER_KEY: Banners = 'init-banner-02'; // Change key to force banner to show again
export const BANNER_KEY: Banners = 'discord-banner-01'; // Change key to force banner to show again
export const SENTRY_DSN =
'https://27d41dc8bb67b596f137924ab8599e59@o1063647.ingest.us.sentry.io/4507497727000576';

View File

@@ -29,6 +29,7 @@
export let timeToRead: string;
export let cover: string;
export let category: string;
export let lastUpdated: string;
const authors = getContext<AuthorData[]>("authors");
const authorData = authors.find((a) => a.slug === author);
@@ -246,12 +247,21 @@
</div>
{/if}
<div class="web-article-content mt-8">
<div class="web-article-content u-margin-block-start-32">
{#if lastUpdated}
<span class="web-main-body-500 last-updated-text">
Updated:
<time dateTime={lastUpdated}>
{formatDate(lastUpdated)}
</time>
</span>
{/if}
<slot />
</div>
</article>
<!-- {#if categories?.length}
<div class="flex gap-4">
<div class="u-flex u-gap-16">
{#each categories as cat}
<a href={cat.href} class="web-tag">{cat.name}</a>
{/each}
@@ -330,5 +340,9 @@
.web-icon-copy {
font-size: 24px;
}
.last-updated-text {
color: var(--primary, #e4e4e7);
}
}
</style>

View File

@@ -22,6 +22,7 @@ export function load() {
description: frontmatter.description,
featured: frontmatter?.featured ?? false,
date: new Date(frontmatter.date),
lastUpdated: new Date(frontmatter.lastUpdated),
cover: frontmatter.cover,
timeToRead: frontmatter.timeToRead,
author: frontmatter.author,

View File

@@ -20,6 +20,7 @@ export type PostsData = {
title: string;
description: string;
date: Date;
lastUpdated: Date;
cover: string;
timeToRead: number;
author: string;
@@ -51,6 +52,7 @@ export const posts = Object.entries(postsGlob)
title: frontmatter.title,
description: frontmatter.description,
date: new Date(frontmatter.date),
lastUpdated: new Date(frontmatter.lastUpdated),
cover: frontmatter.cover,
timeToRead: frontmatter.timeToRead,
author: frontmatter.author,

View File

@@ -3,6 +3,7 @@ layout: post
title: "Announcing: A new Init. Faster. Smoother. Better."
description: Init is about to begin, and were bringing you many new features and products to be excited about. Experience everything new with Appwrite during the week of August 19 to 23.
date: 2024-08-07
lastUpdated: 2024-08-10
cover: /images/blog/announcing-init-faster-smoother-better/init-cover.png
timeToRead: 4
author: eldad-fux

View File

@@ -97,7 +97,7 @@
label: 'Tooling',
items: [
{
label: 'Command Line',
label: 'CLI',
href: '/docs/tooling/command-line/installation',
icon: 'icon-terminal',
isParent: true

View File

@@ -40,7 +40,7 @@ Deligate access for a user through passing JWT tokens.
{% cards_item href="/docs/products/auth/server-side-rendering" title="Server-side rendering (SSR)" %}
Authenticate users in server-side rendered applications.
{% /cards_item %}
{% cards_item href="/docs/products/auth/custom-token" title="Custom Token" %}
{% cards_item href="/docs/products/auth/custom-token" title="Custom token" %}
Implement custom authentication methods like biometric and passkey login by generating custom tokens.
{% /cards_item %}
{% cards_item href="/docs/products/auth/mfa" title="Multifactor authentication (MFA)" %}

View File

@@ -206,6 +206,54 @@ export default ({ req, res, log, error }: any) => {
});
};
```
```go
package handler
import (
"fmt"
"os"
"github.com/appwrite/sdk-for-go/appwrite"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
type Response struct {
Motto string `json:"motto"`
Learn string `json:"learn"`
Connect string `json:"connect"`
GetInspired string `json:"getInspired"`
}
func Main(Context openruntimes.Context) openruntimes.Response {
// This is your Appwrite function
// It's executed each time we get a request service
var _ = appwrite.NewClient(
appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")),
appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]),
)
// You can log messages to the console
fmt.Println("Hello, Logs!")
fmt.Fprintln(os.Stderr, "Error:", "Hello, Errors!")
// The `Context.Req` object contains the request data
if Context.Req.Method == "GET" {
// Send a response with the Context.Res object helpers
// `Context.Res.Text()` dispatches a string back to the client
return Context.Res.Text("Hello, World!")
}
// `res.json()` is a handy helper for sending JSON
return Context.Res.Json(
Response{
Motto: "Build like a team of hundreds_",
Learn: "https://appwrite.io/docs",
Connect: "https://appwrite.io/discord",
GetInspired: "https://builtwith.appwrite.io",
})
}
```
```dart
import 'dart:async';
import 'package:dart_appwrite/dart_appwrite.dart';
@@ -437,7 +485,7 @@ You'll see us use destructuring in examples, which has the following syntax.
[Learn more about destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment).
{% multicode %}
```js
```server-nodejs
// before destructuring
export default async function (context) {
context.log("This is a log!");
@@ -477,9 +525,6 @@ Explore the request object with the following function, which logs all request p
* Request
* Description
---
* `req.bodyRaw`
* Returns the raw body text data.
---
* `req.bodyText`
* Returns text that has been converted from binary data.
---
@@ -492,9 +537,9 @@ Explore the request object with the following function, which logs all request p
{% /table %}
{% multicode %}
```js
```server-nodejs
export default async ({ req, res, log }) => {
log(req.bodyJson); // Raw request body, contains request data
log(req.bodyText); // Raw request body, contains request data
log(JSON.stringify(req.bodyJson)); // Object from parsed JSON request body, otherwise string
log(JSON.stringify(req.headers)); // String key-value pairs of all request headers, keys are lowercase
log(req.scheme); // Value of the x-forwarded-proto header, usually http or https
@@ -512,8 +557,7 @@ export default async ({ req, res, log }) => {
```php
<?php
return function ($context) {
$context->log($context->req->bodyRaw); // Raw request body, contains request data
$context->log(json_encode($context->req->body)); // Object from parsed JSON request body, otherwise string
$context->log(json_encode($context->req->bodyJson));// Object from parsed JSON request body, otherwise string
$context->log(json_encode($context->req->headers)); // String key-value pairs of all request headers, keys are lowercase
$context->log($context->req->scheme); // Value of the x-forwarded-proto header, usually http or https
$context->log($context->req->method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.
@@ -531,7 +575,7 @@ return function ($context) {
import json
def main(context):
context.log(context.req.body_raw) # Raw request body, contains request data
context.log(context.req.bodyText) # Raw request body, contains request data
context.log(json.dumps(context.req.bodyJson)) # Object from parsed JSON request body, otherwise string
context.log(json.dumps(context.req.headers)) # String key-value pairs of all request headers, keys are lowercase
context.log(context.req.scheme) # Value of the x-forwarded-proto header, usually http or https
@@ -549,7 +593,7 @@ def main(context):
require 'json'
def main(context)
context.log(context.req.bodyJson) # Raw request body, contains request data
context.log(context.req.bodyText) # Raw request body, contains request data
context.log(JSON.generate(context.req.bodyJson)) # Object from parsed JSON request body, otherwise string
context.log(JSON.generate(context.req.headers)) # String key-value pairs of all request headers, keys are lowercase
context.log(context.req.scheme) # Value of the x-forwarded-proto header, usually http or https
@@ -566,7 +610,7 @@ end
```
```deno
export default async ({ req, res, log }: any) => {
log(req.bodyJson); // Raw request body, contains request data
log(req.bodyText); // Raw request body, contains request data
log(JSON.stringify(req.bodyJson)); // Object from parsed JSON request body, otherwise string
log(JSON.stringify(req.headers)); // String key-value pairs of all request headers, keys are lowercase
log(req.scheme); // Value of the x-forwarded-proto header, usually http or https
@@ -581,12 +625,37 @@ export default async ({ req, res, log }: any) => {
return res.text("All the request parameters are logged to the Appwrite Console.");
}
```
```go
package handler
import (
"encoding/json"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
func Main(Context openruntimes.Context) openruntimes.Response {
Context.Log(Context.Req.BodyText) // Raw request body, contains request data
Context.Log(json.Marshal(Context.Req.BodyJson)) // Object from parsed JSON request body, otherwise string
Context.Log(json.Marshal(Context.Req.Headers)) // String key-value pairs of all request headers, keys are lowercase
Context.Log(Context.Req.Scheme) // Value of the x-forwarded-proto header, usually http or https
Context.Log(Context.Req.Method) // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.
Context.Log(Context.Req.Url) // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50
Context.Log(Context.Req.Host) // Hostname from the host header, such as awesome.appwrite.io
Context.Log(Context.Req.Port) // Port from the host header, for example 8000
Context.Log(Context.Req.Path) // Path part of URL, for example /v1/hooks
Context.Log(Context.Req.QueryString) // Raw query params string. For example "limit=12&offset=50"
Context.Log(json.Marshal(Context.Req.Query)) // Parsed query params. For example, req.query.limit
return Context.Res.Text("All the request parameters are logged to the Appwrite Console.")
}
```
```dart
import 'dart:async';
import 'dart:convert';
Future<dynamic> main(final context) async {
context.log(context.req.bodyJson); // Raw request body, contains request data
context.log(context.req.bodyText); // Raw request body, contains request data
context.log(json.encode(context.req.bodyJson)); // Object from parsed JSON request body, otherwise string
context.log(json.encode(context.req.headers)); // String key-value pairs of all request headers, keys are lowercase
context.log(context.req.scheme); // Value of the x-forwarded-proto header, usually http or https
@@ -629,8 +698,7 @@ using System.Text.Json;
public class Handler {
public async Task<RuntimeOutput> Main(RuntimeContext Context)
{
Context.Log(Context.Req.BodyRaw); // Raw request body, contains request data
Context.Log(JsonSerializer.Serialize<object>(Context.Req.Body)); // Object from parsed JSON request body, otherwise string
Context.Log(JsonSerializer.Serialize<object>(Context.Req.BodyJson)); // Object from parsed JSON request body, otherwise string
Context.Log(JsonSerializer.Serialize<object>(Context.Req.Headers)); // String key-value pairs of all request headers, keys are lowercase
Context.Log(Context.Req.Scheme); // Value of the x-forwarded-proto header, usually http or https
Context.Log(Context.Req.Method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.
@@ -683,7 +751,6 @@ public class Main {
public RuntimeOutput main(RuntimeContext context) {
Gson gson = new Gson();
context.log(context.getReq().getBodyRaw()); // Raw request body, contains request data
context.log(gson.toString(context.getReq().getBody())); // Object from parsed JSON request body, otherwise string
context.log(gson.toString(context.getReq().getHeaders())); // String key-value pairs of all request headers, keys are lowercase
context.log(context.getReq().getScheme()); // Value of the x-forwarded-proto header, usually http or https
@@ -718,7 +785,7 @@ These are provided alongside any custom headers sent to the function.
| `x-appwrite-continent-eu` | Describes if the configured local is within the EU. |
## Response {% #response %}
If you need to send a response to the invoker of the function, such as a user, client app, or an integration, use the response object.
Use the response object to send a response to the function caller. This could be a user, client app, or an integration.
The response information **will not be logged** to the Appwrite Console.
There are several possible ways to send a response, explore them in the following Appwrite Function.
@@ -745,7 +812,9 @@ There are several possible ways to send a response, explore them in the followin
{% /table %}
{% multicode %}
```js
```server-nodejs
const fs = require('fs');
export default async ({ req, res, log }) => {
switch (req.query.type) {
@@ -753,6 +822,8 @@ export default async ({ req, res, log }) => {
return res.empty();
case 'json':
return res.json({"type": "This is a JSON response"});
case 'binary':
return res.binary(InputFile.fromPath('/path/to/file', 'filename'));
case 'redirect':
return res.redirect("https://appwrite.io", 301);
case 'html':
@@ -767,6 +838,7 @@ export default async ({ req, res, log }) => {
```
```php
<?php
use Appwrite\InputFile;
return function ($context) {
switch ($context->req->query['type']) {
@@ -774,6 +846,8 @@ return function ($context) {
return $context->res->empty();
case 'json':
return $context->res->json(["type" => "This is a JSON response"]);
case 'binary':
return $context->res->binary(InputFile::withPath('file.png'));
case 'redirect':
return $context->res->redirect("https://appwrite.io", 301);
case 'html':
@@ -786,13 +860,17 @@ return function ($context) {
};
```
```python
from appwrite.input_file import InputFile
def main(context):
type = context.req.query['type']
if type == 'empty':
return context.res.empty()
elif type =='json':
elif type == 'json':
return context.res.json({"type": "This is a JSON response"})
elif type == 'binary':
return context.res.binary(InputFile.from_path('file.png'))
elif type == 'redirect':
return context.res.redirect("https://appwrite.io", 301)
elif type == 'html':
@@ -809,6 +887,8 @@ def main(context)
return context.res.empty()
when 'json'
return context.res.json({"type": "This is a JSON response"})
when 'binary'
return context.res.binary(InputFile.from_path('dir/file.png'))
when 'redirect'
return context.res.redirect("https://appwrite.io", 301)
when 'html'
@@ -828,6 +908,8 @@ export default async ({ req, res, log }) => {
return res.empty();
case 'json':
return res.json({type: "This is a JSON response"});
case 'binary':
return res.binary(InputFile.fromPath('/path/to/file.png', 'file.png'));
case 'redirect':
return res.redirect("https://appwrite.io", 301);
case 'html':
@@ -840,6 +922,35 @@ export default async ({ req, res, log }) => {
}
}
```
```go
package handler
import (
"io"
"os"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
func Main(Context openruntimes.Context) openruntimes.Response {
switch Context.Req.Query["type"] {
case "empty":
return Context.Res.Empty()
case "json":
return Context.Res.Json(map[string]string{"type": "This is a JSON response"})
case "binary":
file, _ := os.Open("./destiny.png")
imageData, _ := io.ReadAll(file)
return Context.Res.Binary(imageData)
case "redirect":
return Context.Res.Redirect("https://appwrite.io")
case "html":
return Context.Res.Text("<h1>This is an HTML response</h1>")
default:
return Context.Res.Text("This is a text response")
}
}
```
```dart
import 'dart:async';
@@ -849,6 +960,8 @@ Future<dynamic> main(final context) async {
return context.res.empty();
case 'json':
return context.res.json({'type': 'This is a JSON response'});
case 'binary':
return context.res.binary(InputFile(path: './path-to-files/image.jpg', filename: 'image.jpg'));
case 'redirect':
return context.res.redirect('https://appwrite.io', 301);
case 'html':
@@ -868,6 +981,8 @@ func main(context: RuntimeContext) async throws -> RuntimeOutput {
return context.res.empty()
case "json":
return context.res.text(["type": "This is a JSON response"])
case "binary":
return context.res.binary(InputFile.fromPath("file.png"))
case "redirect":
return context.res.redirect("https://appwrite.io", 301)
case "html":
@@ -889,6 +1004,8 @@ public class Handler {
return Context.Res.Empty();
case "json":
return Context.Res.Text(new Dictionary<string, object>() { { "type", "This is a JSON response" } });
case "binary":
return Context.Res.Binary(InputFile.FromPath("./path-to-files/image.jpg"));
case "redirect":
return Context.Res.Redirect("https://appwrite.io", 301);
case "html":
@@ -906,12 +1023,14 @@ package io.openruntimes.kotlin.src
import io.openruntimes.kotlin.RuntimeContext
import io.openruntimes.kotlin.RuntimeOutput
import io.appwrite.models.InputFile
class Main {
fun main(context: RuntimeContext): RuntimeOutput {
when (context.req.query["type"]) {
"empty" -> return context.res.empty()
"json" -> return context.res.text(mapOf("type" to "This is a JSON response"))
"binary" -> return context.res.binary(InputFile.fromPath("file.png"))
"redirect" -> return context.res.redirect("https://appwrite.io", 301)
"html" -> return context.res.text("<h1>This is an HTML response</h1>", 200, mapOf("content-type" to "text/html"))
else -> return context.res.text("This is a text response")
@@ -924,6 +1043,7 @@ package io.openruntimes.java.src;
import io.openruntimes.java.RuntimeContext;
import io.openruntimes.java.RuntimeOutput;
import io.appwrite.models.InputFile;
import java.util.Map;
import java.util.HashMap;
@@ -936,6 +1056,8 @@ public class Main {
HashMap<String, Object> data = new HashMap<>();
data.put("type", "This is a JSON response");
return context.getRes().text(data);
case "binary"
return context.getRes().binary(InputFile.fromPath("file.png"));
case "redirect":
return context.getRes().redirect("https://appwrite.io", 301);
case "html":
@@ -964,6 +1086,8 @@ namespace runtime {
Json::Value data;
data["type"] = "This is a JSON response";
return context.res.text(data);
} else if (type == "binary") {
return context.res.binary(InputFile.fromPath("file.png"))
} else if (type == "redirect") {
return context.res.redirect("https://appwrite.io", 301);
} else if (type == "html") {
@@ -991,17 +1115,22 @@ To get the different response types, set one of the following query parameters i
## Logging {% #logging %}
To protect user privacy, the request and response objects are not logged to the Appwrite Console by default.
This means, to see logs or debug function executions you need to use the `log()` and `error()` methods.
We support the spread operator across most of the languages, meaning you can write code that is more concise and flexible.
This means, to see logs or debug function executions you need to use the `log()` and `error()` methods.
These logs are only visible to developers with access to the Appwrite Console.
Here's an example of using logs and errors.
{% multicode %}
```js
```server-nodejs
export default async ({ req, res, log, error }) => {
log("This is a log, use for logging information to console");
const message = "This is a log, use for logging information to console";
log("Message: ", message);
log(`This function was called with ${req.method} method`);
error("This is an error, use for logging errors to console");
const errorMessage = "This is an error, use for logging errors to console"
error("Error: ", errorMessage);
return res.text("Check the Appwrite Console to see logs and errors!");
};
@@ -1010,46 +1139,75 @@ export default async ({ req, res, log, error }) => {
<?php
return function ($context) {
$context->log("This is a log, use for logging information to console");
$message = "This is a log, use for logging information to console";
$context->log("Message: ", message);
$context->log("This function was called with " . $context->req->method . " method");
$context->error("This is an error, use for logging errors to console");
$errorMessage = "Check the Appwrite Console to see logs and errors!"
$context->error("Error: ", errorMessage);
return $context->text("Check the Appwrite Console to see logs and errors!");
};
```
```python
def main(context):
context.log("This is a log, use for logging information to console")
message = "This is a log, use for logging information to console"
context.log("Message: ", message)
context.log(f"This function was called with {context.req.method} method")
context.error("This is an error, use for logging errors to console")
errorMessage = "This is an error, use for logging errors to console"
context.error("Error: ", errorMessage)
return context.res.text("Check the Appwrite Console to see logs and errors!")
```
```ruby
def main(context)
context.log("This is a log, use for logging information to console")
message = "This is a log, use for logging information to console"
context.log("Message: ", message)
context.log("This function was called with #{context.req.method} method")
context.error("This is an error, use for logging errors to console")
errorMessage = "This is an error, use for logging errors to console"
context.error("Error: ", errorMessage)
return context.res.text("Check the Appwrite Console to see logs and errors!")
end
```
```deno
export default async ({ res, log, error }: any) => {
log("This is a log, use for logging information to console");
let message = "This is a log, use for logging information to console";
log("Message: ", message);
log(`This function was called with ${context.req.method} method`);
error("This is an error, use for logging errors to console");
let errorMessage = "This is an error, use for logging errors to console";
error("Error: ", errorMessage);
return res.text("Check the Appwrite Console to see logs and errors!");
};
```
```go
package handler
import (
"fmt"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
func Main(Context openruntimes.Context) openruntimes.Response {
message := "This is a log, use for logging information to console"
Context.Log("Message: ", message)
Context.Log(fmt.Sprintf("This function was called with %s method", Context.Req.Method))
errorMessage := "This is an error, use for logging errors to console"
Context.Error("Error: ", errorMessage)
return Context.Res.Text("Check the Appwrite Console to see logs and errors!")
}
```
```dart
import 'dart:async';
Future<dynamic> main(final context) async {
context.log("This is a log, use for logging information to console");
var message = "This is a log, use for logging information to console";
context.log("message: ", var);
context.log("This function was called with ${context.req.method} method");
context.error("This is an error, use for logging errors to console");
var errorMessage = "This is an error, use for logging errors to console";
context.error("Error: ", errorMessage);
return context.res.text("Check the Appwrite Console to see logs and errors!");
}
@@ -1058,9 +1216,11 @@ Future<dynamic> main(final context) async {
import Foundation
func main(context: RuntimeContext) async throws -> RuntimeOutput {
context.log("This is a log, use for logging information to console")
var message: String = "This is a log, use for logging information to console"
context.log("Message: ", message)
context.log("This function was called with \(context.req.method) method")
context.error("This is an error, use for logging errors to console")
var message: String = "This is an error, use for logging errors to console"
context.error("Error: ", message)
return context.res.text("Check the Appwrite Console to see logs and errors!")
}
@@ -1071,9 +1231,11 @@ namespace DotNetRuntime;
public class Handler {
public async Task<RuntimeOutput> Main(RuntimeContext Context)
{
Context.Log("This is a log, use for logging information to console");
string message = "This is a log, use for logging information to console";
Context.Log("Message: ", message);
Context.Log($"This function was called with {Context.Req.Method} method");
Context.Error("This is an error, use for logging errors to console");
string errorMessage = "This is an error, use for logging errors to console";
Context.Error("Error: ", errorMessage);
return Context.Res.Text("Check the Appwrite Console to see logs and errors!");
}
@@ -1087,9 +1249,11 @@ import io.openruntimes.kotlin.RuntimeOutput
class Main {
fun main(context: RuntimeContext): RuntimeOutput {
context.log("This is a log, use for logging information to console")
var message: String = "This is a log, use for logging information to console"
context.log("Message: ", message)
context.log("This function was called with ${context.req.method} method")
context.error("This is an error, use for logging errors to console")
var errorMessage: String = "This is an error, use for logging errors to console"
context.error("Error: ", errorMessage)
return context.res.text("Check the Appwrite Console to see logs and errors!")
}
@@ -1103,9 +1267,11 @@ import io.openruntimes.java.RuntimeOutput;
public class Main {
public RuntimeOutput main(RuntimeContext context) throws Exception {
context.log("This is a log, use for logging information to console");
String message = "This is a log, use for logging information to console";
context.log("Message: ", message);
context.log("This function was called with " + context.req.method + " method");
context.error("This is an error, use for logging errors to console");
string errorMessage = "This is an error, use for logging errors to console";
context.error("Error: ", errorMessage);
return context.getRes().text("Check the Appwrite Console to see logs and errors!");
}
@@ -1121,9 +1287,11 @@ namespace runtime {
class Handler {
public:
static RuntimeOutput main(RuntimeContext &context) {
context.log("This is a log, use for logging information to console");
const std::string message = "This is a log, use for logging information to console";
context.log("Message: ", message);
context.log("This function was called with " + context.req.method + " method");
context.error("This is an error, use for logging errors to console");
const std::string errorMessage = "This is an error, use for logging errors to console";
context.error("Error: ", errorMessage);
return context.res.text("Check the Appwrite Console to see logs and errors!");
}
@@ -1142,14 +1310,18 @@ You can access these logs through the following steps.
# Accessing environment variables {% #environment-variables %}
If you need to pass constants or secrets to Appwrite Functions, you can use environment variables.
| Variable | Description |
|-----------------------------------|------------------------------------------------|
| `APPWRITE_FUNCTION_ID` | The ID of the running function. |
| `APPWRITE_FUNCTION_NAME` | The Name of the running function. |
| `APPWRITE_FUNCTION_DEPLOYMENT` | The deployment ID of the running function. |
| `APPWRITE_FUNCTION_PROJECT_ID` | The project ID of the running function. |
| `APPWRITE_FUNCTION_RUNTIME_NAME` | The runtime of the running function. |
| `APPWRITE_FUNCTION_RUNTIME_VERSION` | The runtime version of the running function. |
| Variable | Description | Available at Build and/or Run Time |
|-----------------------------------|------------------------------------------------|-----------------------------------------------------|
| `APPWRITE_FUNCTION_API_ENDPOINT` | The API endpoint of the running function | Both |
| `APPWRITE_VERSION` | The Appwrite version used to run the function | Both |
| `APPWRITE_REGION` | The region where the function will run from | Both |
| `APPWRITE_FUNCTION_API_KEY` | The function API key is used for server authentication | Build time |
| `APPWRITE_FUNCTION_ID` | The ID of the running function. | Both |
| `APPWRITE_FUNCTION_NAME` | The Name of the running function. | Both |
| `APPWRITE_FUNCTION_DEPLOYMENT` | The deployment ID of the running function. | Both |
| `APPWRITE_FUNCTION_PROJECT_ID` | The project ID of the running function. | Both |
| `APPWRITE_FUNCTION_RUNTIME_NAME` | The runtime of the running function. | Both |
| `APPWRITE_FUNCTION_RUNTIME_VERSION` | The runtime version of the running function. | Both |
{% arrow_link href="/docs/products/functions/functions#environment-variables" %}
Learn to add variables to you function
@@ -1158,7 +1330,7 @@ Learn to add variables to you function
You can access the environment variables through the systems library of each language.
{% multicode %}
```js
```server-nodejs
export default async ({ req, res, log }) => {
return res.text(process.env.MY_VAR);
}
@@ -1184,6 +1356,19 @@ export default async ({ req, res, log }) => {
return res.text(Deno.env.get('MY_VAR'));
}
```
```go
package handler
import (
"os"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
func Main(Context openruntimes.Context) openruntimes.Response {
return res.text(os.Getenv(MY_VAR))
}
```
```dart
import 'dart:io';
import 'dart:async';
@@ -1298,6 +1483,12 @@ By default, we include the following package managers in each runtime.
* deno
* `deno cache <ENTRYPOINT_FILE>`
---
* {% only_dark %}{% icon_image src="/images/platforms/dark/go.svg" alt="Go logo" size="m" /%}{% /only_dark %}
{% only_light %}{% icon_image src="/images/platforms/go.svg" alt="Go logo" size="m" /%}{% /only_light %}
* Go
* Go Modules
* N/A
---
* {% only_dark %}{% icon_image src="/images/platforms/dark/dart.svg" alt="Dart logo" size="m" /%}{% /only_dark %}
{% only_light %}{% icon_image src="/images/platforms/dart.svg" alt="Dart logo" size="m" /%}{% /only_light %}
* Dart
@@ -1375,7 +1566,7 @@ export default async ({ req, res, log, error }) => {
try {
await databases.createDocument(
'<DATABASE_ID>',
'[COLLECTION_ID]',
'<COLLECTION_ID>',
ID.unique(),
{}
)
@@ -1408,7 +1599,7 @@ return function ($context) {
try {
$databases->createDocument(
databaseId: '<DATABASE_ID>',
collectionId: '[COLLECTION_ID]',
collectionId: '<COLLECTION_ID>',
documentId: ID::unique(),
data: []
);
@@ -1466,7 +1657,7 @@ def main(context)
begin
databases.create_document(
databaseId: '<DATABASE_ID>',
collectionId: '[COLLECTION_ID]',
collectionId: '<COLLECTION_ID>',
documentId: ID.unique(),
data: {}
)
@@ -1504,6 +1695,42 @@ export default function ({req, res, error}: any){
return res.text("Document created");
}
```
```go
package handler
import (
"fmt"
"os"
"github.com/appwrite/sdk-for-go/appwrite"
"github.com/appwrite/sdk-for-go/id"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
func Main(Context openruntimes.Context) openruntimes.Response {
// Set project and set API key
client := appwrite.NewClient(
appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")),
appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]),
)
databases := appwrite.NewDatabases(client)
_, err := databases.CreateDocument(
"<DATABASE_ID>",
"<COLLECTION_ID>",
id.Unique(),
map[string]interface{}{},
)
if err != nil {
Context.Log(fmt.Sprintf("Failed to create document: %v", err))
return Context.Res.Text("Failed to create document")
}
return Context.Res.Text("Document created")
}
```
```dart
import 'dart:io';
import 'dart:async';
@@ -1520,7 +1747,7 @@ Future<dynamic> main(final context) async {
try {
await databases.createDocument(
databaseId: '<DATABASE_ID>',
collectionId: '[COLLECTION_ID]',
collectionId: '<COLLECTION_ID>',
documentId: ID.unique(),
data: {}
);
@@ -1683,7 +1910,7 @@ export default async ({ req, res, log }) => {
if (req.headers['x-appwrite-user-jwt']) {
client.setJWT(req.headers['x-appwrite-user-jwt'])
} else {
return res.text("Please sign in, JWT not found")
return res.text("Access denied: This function requires authentication. Please sign in to continue.");
}
const databases = new Databases(client);
@@ -1691,7 +1918,7 @@ export default async ({ req, res, log }) => {
try {
await databases.createDocument(
'<DATABASE_ID>',
'[COLLECTION_ID]',
'<COLLECTION_ID>',
ID.unique(),
{}
)
@@ -1720,7 +1947,7 @@ return function ($context) {
if (isset($context->req->headers['x-appwrite-user-jwt'])) {
$client->setJWT($context->req->headers['x-appwrite-user-jwt']);
} else {
return $context->res->text("Please sign in, JWT not found");
return $context->res->text("Access denied: This function requires authentication. Please sign in to continue.");
}
$databases = new Databases($client);
@@ -1728,7 +1955,7 @@ return function ($context) {
try {
$databases->createDocument(
databaseId: '<DATABASE_ID>',
collectionId: '[COLLECTION_ID]',
collectionId: '<COLLECTION_ID>',
documentId: ID::unique(),
data: []
);
@@ -1756,7 +1983,7 @@ def main(context):
if "x-appwrite-user-jwt" in context.req.headers:
client.set_jwt(context.req.headers["x-appwrite-user-jwt"])
else:
return context.res.text("Please sign in, JWT not found")
return context.res.text("Access denied: This function requires authentication. Please sign in to continue.")
databases = Databases(client)
@@ -1785,13 +2012,13 @@ def main(context)
if context.request.headers['x-appwrite-user-jwt']
client.set_jwt(context.request.headers['x-appwrite-user-jwt'])
else
return context.response.text("Please sign in, JWT not found")
return context.response.text("Access denied: This function requires authentication. Please sign in to continue.")
end
databases = Appwrite::Databases.new(client)
begin
databases.create_document('<DATABASE_ID>', '[COLLECTION_ID]', Appwrite::ID.unique(), {})
databases.create_document('<DATABASE_ID>', '<COLLECTION_ID>', Appwrite::ID.unique(), {})
rescue Appwrite::Exception => e
context.error("Failed to create document: " + e.message)
return context.response.text("Failed to create document")
@@ -1810,7 +2037,7 @@ export default function ({req, res, error}: any){
if (req.headers["x-appwrite-user-jwt"]) {
client.setJWT(req.headers["x-appwrite-user-jwt"]);
} else {
return res.text("Please sign in, JWT not found");
return res.text("Access denied: This function requires authentication. Please sign in to continue.");
}
const databases = new Databases(client);
@@ -1830,6 +2057,47 @@ export default function ({req, res, error}: any){
return res.text("Document created");
}
```
```go
package handler
import (
"fmt"
"log"
"github.com/appwrite/sdk-for-go/appwrite"
"github.com/appwrite/sdk-for-go/id"
"github.com/open-runtimes/types-for-go/v4/openruntimes"
)
func Main(Context openruntimes.Context) openruntimes.Response {
client := appwrite.NewClient(
appwrite.WithProject("APPWRITE_FUNCTION_PROJECT_ID"),
)
jwt, exists := Context.Req.Headers["x-appwrite-user-jwt"]
if !exists || len(jwt) == 0 {
appwrite.WithJWT(Context.Req.Headers["x-appwrite-user-jwt"])
} else {
return Context.Res.Text("Access denied: This function requires authentication. Please sign in to continue.")
}
databases := appwrite.NewDatabases(client)
_, err := databases.CreateDocument(
"<DATABASE_ID>",
"<COLLECTION_ID>",
id.Unique(),
map[string]interface{}{},
)
if err != nil {
Context.Log(fmt.Sprintf("Failed to create document: %v", err))
return Context.Res.Text(str)
}
return Context.Res.Text("Document created")
}
```
```dart
import 'dart:io';
import 'dart:async';
@@ -1842,7 +2110,7 @@ Future<dynamic> main(final context) async {
if (context.req.headers['x-appwrite-user-jwt'] != null) {
client.setJWT(context.req.headers['x-appwrite-user-jwt']);
} else {
return context.res.text("Please sign in, JWT not found");
return context.res.text("Access denied: This function requires authentication. Please sign in to continue.");
}
final databases = Databases(client);
@@ -1850,7 +2118,7 @@ Future<dynamic> main(final context) async {
try {
await databases.createDocument(
databaseId: '<DATABASE_ID>',
collectionId: '[COLLECTION_ID]',
collectionId: '<COLLECTION_ID>',
documentId: ID.unique(),
data: {}
);
@@ -1874,7 +2142,7 @@ func main(context: RuntimeContext) async throws -> RuntimeOutput {
if let jwt = context.req.headers["x-appwrite-user-jwt"] {
client.setJWT(jwt)
} else {
return context.res.text("Please sign in, JWT not found")
return context.res.text("Access denied: This function requires authentication. Please sign in to continue.")
}
let databases = Databases(client: client)
@@ -1911,7 +2179,7 @@ namespace DotNetRuntime
if (Context.Req.Headers.ContainsKey("x-appwrite-user-jwt")) {
client.SetJWT(Context.Req.Headers["x-appwrite-user-jwt"]);
} else {
return Context.Res.Text("Please sign in, JWT not found");
return Context.Res.Text("Access denied: This function requires authentication. Please sign in to continue");
}
var databases = new Databases(client);
@@ -1950,7 +2218,7 @@ class Main {
if (context.req.headers["x-appwrite-user-jwt"] != null) {
client.setJWT(context.req.headers["x-appwrite-user-jwt"])
} else {
return context.res.text("Please sign in, JWT not found")
return context.res.text("Access denied: This function requires authentication. Please sign in to continue.")
}
val databases = Databases(client)
@@ -1987,7 +2255,7 @@ public class Main {
if (context.req.headers.containsKey("x-appwrite-user-jwt")) {
client.setJWT(context.req.headers.get("x-appwrite-user-jwt"));
} else {
return context.res.text("Please sign in, JWT not found");
return context.res.text("Access denied: This function requires authentication. Please sign in to continue.");
}
Databases databases = new Databases(client);
@@ -2017,13 +2285,13 @@ As your functions grow, you may find yourself needing to split your code into mu
{% tabs %}
{% tabsitem #nodejs title="Node.js" %}
```js
```server-nodejs
// src/utils.js
export function add(a, b) {
return a + b;
}
```
```js
```server-nodejs
// src/main.js
import { add } from './utils.js';
@@ -2101,6 +2369,45 @@ export default function ({res}: {res: any}) {
```
{% /tabsitem %}
{% tabsitem #go title="Go" %}
```go
// src/utils/go.mod
module example.com/utils
go 1.23.0
```
```go
// src/utils/utils.go
package utils
func Add(a int, b int) int {
return a + b
}
```
```go
// src/main/go.mod
module example.com/main
go 1.23.0
replace example.com/utils => ../utils // Run go mod edit -replace example.com/go=../go
require example.com/utils v0.0.0-00010101000000-000000000000 // Run go mod tidy
```
```go
// src/main/main.go
package main
import "example.com/utils"
func main() {
// Get a greeting message and print it.
message := utils.Add(5, 4)
print(message)
}
```
{% /tabsitem %}
{% tabsitem #dart title="Dart" %}
```dart
// lib/utils.dart

View File

@@ -157,14 +157,16 @@ import { Client, Account } from "appwrite";
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
```client-flutter
import 'package:appwrite/appwrite.dart';
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
```
@@ -173,16 +175,18 @@ import Appwrite
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
```client-android-kotlin
import io.appwrite.ID
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
{% /multicode %}
{% /tabsitem %}
@@ -194,16 +198,18 @@ const sdk = require('node-appwrite');
// Generate a unique ID
sdk.ID.unique()
// Generate a custom ID
sdk.ID.custom()
sdk.ID.custom("my-custom-id")
```
```deno
import * as sdk from "https://deno.land/x/appwrite/mod.ts";
// Generate a unique ID
sdk.ID.unique()
// Generate a custom ID
sdk.ID.custom()
sdk.ID.custom("my-custom-id")
```
```php
<?php
@@ -212,63 +218,71 @@ use Appwrite\ID;
// Generate a unique ID
ID::unique()
// Generate a custom ID
ID::custom()
ID::custom("my-custom-id")
```
```python
from appwrite.id import ID
# Generate a unique ID
ID.unique()
# Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
```csharp
using Appwrite.ID;
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
```java
io.appwrite.ID
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
```kotlin
io.appwrite.ID
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
```ruby
require 'appwrite'
include Appwrite
// Generate a unique ID
ß
# Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
# Generate a custom ID
ID.custom("my-custom-id")
```
```swift
import Appwrite
// Generate a unique ID
ID.unique()
// Generate a custom ID
ID.custom()
ID.custom("my-custom-id")
```
{% /multicode %}
{% /tabsitem %}

View File

@@ -1,59 +1,59 @@
<script lang="ts">
import Docs from '$lib/layouts/Docs.svelte';
import Sidebar, { type NavParent, type NavTree } from '$lib/layouts/Sidebar.svelte';
import Docs from '$lib/layouts/Docs.svelte';
import Sidebar, { type NavParent, type NavTree } from '$lib/layouts/Sidebar.svelte';
const parent: NavParent = {
href: '/docs',
label: 'Command Line'
};
const parent: NavParent = {
href: '/docs',
label: 'CLI'
};
const navigation: NavTree = [
{
label: 'Guides',
items: [
{
label: 'Installation',
href: '/docs/tooling/command-line/installation'
},
{
label: 'Commands',
href: '/docs/tooling/command-line/commands'
},
{
label: 'Non interactive',
href: '/docs/tooling/command-line/non-interactive'
}
]
},
{
label: 'Deployments',
items: [
{
label: 'Collections',
href: '/docs/tooling/command-line/collections'
},
{
label: 'Functions',
href: '/docs/tooling/command-line/functions'
},
{
label: 'Teams',
href: '/docs/tooling/command-line/teams'
},
{
label: 'Topics',
href: '/docs/tooling/command-line/topics'
},
{
label: 'Buckets',
href: '/docs/tooling/command-line/buckets'
}
]
}
];
const navigation: NavTree = [
{
label: 'Guides',
items: [
{
label: 'Installation',
href: '/docs/tooling/command-line/installation'
},
{
label: 'Commands',
href: '/docs/tooling/command-line/commands'
},
{
label: 'Non interactive',
href: '/docs/tooling/command-line/non-interactive'
}
]
},
{
label: 'Deployments',
items: [
{
label: 'Collections',
href: '/docs/tooling/command-line/collections'
},
{
label: 'Functions',
href: '/docs/tooling/command-line/functions'
},
{
label: 'Teams',
href: '/docs/tooling/command-line/teams'
},
{
label: 'Topics',
href: '/docs/tooling/command-line/topics'
},
{
label: 'Buckets',
href: '/docs/tooling/command-line/buckets'
}
]
}
];
</script>
<Docs variant="two-side-navs">
<Sidebar {navigation} {parent} />
<slot />
<Sidebar {navigation} {parent} />
<slot />
</Docs>

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
body: JSON.stringify({
email,
key,
cloud: true /* not optional on the growth endpoint. */,
}),
},
);

2
terraform/.env.example Normal file
View File

@@ -0,0 +1,2 @@
export TF_VAR_DO_TOKEN=
export TF_VAR_PRIVATE_KEY=

View File

@@ -0,0 +1,16 @@
module "droplets" {
source = "../../modules/digitalocean"
private_key = "${var.PRIVATE_KEY}"
project_name = "hmp"
region = "fra1"
environment = "prd"
base_image = "docker-20-04"
worker_size = "s-2vcpu-2gb-amd"
worker_count = 6
subnet_range = "10.117.0.0/20"
manager_size = "s-2vcpu-2gb-amd"
manager_count = 2
digitalocean_project_name = "Production - Homepage"
}

View File

@@ -0,0 +1,18 @@
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
cloud {
organization = "appwrite"
workspaces {
name = "production-homepage"
}
}
}
provider "digitalocean" {
token = var.DO_TOKEN
}

View File

@@ -0,0 +1,7 @@
variable "DO_TOKEN" {
description = "DigitalOcean API token"
}
variable "PRIVATE_KEY" {
description = "Contents of your local SSH private key file"
default = "$(cat ~/.ssh/id_rsa)"
}

View File

@@ -0,0 +1,16 @@
module "droplets" {
source = "../../modules/digitalocean"
private_key = "${var.PRIVATE_KEY}"
project_name = "hmp"
region = "fra1"
environment = "stg"
base_image = "docker-20-04"
subnet_range = "10.116.0.0/20"
worker_size = "s-1vcpu-2gb"
worker_count = 4
manager_size = "s-1vcpu-2gb"
manager_count = 2
digitalocean_project_name = "Staging - Homepage"
}

View File

@@ -0,0 +1,18 @@
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
cloud {
organization = "appwrite"
workspaces {
name = "staging-homepage"
}
}
}
provider "digitalocean" {
token = var.DO_TOKEN
}

View File

@@ -0,0 +1,7 @@
variable "DO_TOKEN" {
description = "DigitalOcean API token"
}
variable "PRIVATE_KEY" {
description = "Contents of your local SSH private key file"
default = "$(cat ~/.ssh/id_rsa)"
}

View File

@@ -0,0 +1,153 @@
locals {
mount_nfs = "/letsencrypt"
setup_firewall = [
"ufw allow 2377/tcp",
"ufw allow 7946/tcp",
"ufw allow 7946/udp",
"ufw allow 4789/udp",
"ufw reload",
"systemctl restart docker"
]
setup_nfs = [
"ufw allow 2049",
"ufw reload",
"apt install -y nfs-common",
"mkdir -p ${local.mount_nfs}",
"echo '${digitalocean_droplet.nfs.ipv4_address_private}:${local.mount_nfs} ${local.mount_nfs} nfs proto=tcp,port=2049,nfsvers=4,sync,noexec,rw 0 0' >> /etc/fstab",
"mount -a",
]
}
resource "digitalocean_project" "homepage" {
name = var.digitalocean_project_name
description = "Appwrite Homepage"
purpose = "Web Application"
environment = "Development"
resources = flatten([
digitalocean_droplet.leader.urn,
digitalocean_droplet.manager[*].urn,
digitalocean_droplet.worker[*].urn,
digitalocean_droplet.nfs.urn
])
}
# Tags
resource "digitalocean_tag" "worker" {
name = "${var.environment}-worker"
}
resource "digitalocean_tag" "manager" {
name = "${var.environment}-manager"
}
resource "digitalocean_droplet" "leader" {
image = var.base_image
name = "${var.project_name}-${var.region}-${var.environment}-leader-0"
region = var.region
size = var.manager_size
tags = [digitalocean_tag.manager.id]
ssh_keys = [
data.digitalocean_ssh_key.Christy.id
]
vpc_uuid = digitalocean_vpc.subnet.id
connection {
host = self.ipv4_address
user = "root"
type = "ssh"
private_key = var.private_key
timeout = "2m"
}
provisioner "remote-exec" {
inline = concat(local.setup_firewall, local.setup_nfs, [
"docker swarm init --advertise-addr ${self.ipv4_address_private}"
])
}
}
resource "digitalocean_droplet" "manager" {
count = var.manager_count
image = var.base_image
name = "${var.project_name}-${var.region}-${var.environment}-manager-${count.index}"
region = var.region
size = var.manager_size
tags = [digitalocean_tag.manager.id]
vpc_uuid = digitalocean_vpc.subnet.id
ssh_keys = [
data.digitalocean_ssh_key.Christy.id
]
connection {
host = self.ipv4_address
user = "root"
type = "ssh"
private_key = var.private_key
timeout = "2m"
}
provisioner "remote-exec" {
inline = concat(local.setup_firewall, local.setup_nfs, [
"docker swarm join --token ${data.external.swarm_join_token.result.manager} ${digitalocean_droplet.leader.ipv4_address_private}:2377"
])
}
}
resource "digitalocean_droplet" "worker" {
count = var.worker_count
image = var.base_image
name = "${var.project_name}-${var.region}-${var.environment}-worker-${count.index}"
region = var.region
size = var.worker_size
tags = [digitalocean_tag.worker.id]
vpc_uuid = digitalocean_vpc.subnet.id
ssh_keys = [
data.digitalocean_ssh_key.Christy.id
]
connection {
host = self.ipv4_address
user = "root"
type = "ssh"
private_key = var.private_key
timeout = "2m"
}
provisioner "remote-exec" {
inline = concat(local.setup_firewall, [
"docker swarm join --token ${data.external.swarm_join_token.result.worker} ${digitalocean_droplet.leader.ipv4_address_private}:2377"
])
}
}
resource "digitalocean_droplet" "nfs" {
image = var.base_image
name = "${var.project_name}-${var.region}-${var.environment}-nfs-0"
region = var.region
size = var.worker_size
vpc_uuid = digitalocean_vpc.subnet.id
ssh_keys = [
data.digitalocean_ssh_key.Christy.id
]
connection {
host = self.ipv4_address
user = "root"
type = "ssh"
private_key = var.private_key
timeout = "2m"
}
provisioner "remote-exec" {
inline = [
"ufw allow 2049",
"ufw reload",
"sudo apt update",
"sudo apt install -y nfs-kernel-server",
"mkdir -p ${local.mount_nfs}",
"echo '${local.mount_nfs} ${var.subnet_range}(rw,sync,no_root_squash,no_subtree_check)' >> /etc/exports",
"exportfs -arvf",
"systemctl restart nfs-kernel-server",
]
}
}

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Exit if any of the intermediate steps fail
set -e
# Extract input variables
eval "$(jq -r '@sh "HOST=\(.host)"')"
# Get worker join token
WORKER=$(ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$HOST docker swarm join-token worker -q)
MANAGER=$(ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$HOST docker swarm join-token manager -q)
# Pass back a JSON object
jq -n --arg worker $WORKER --arg manager $MANAGER '{"worker":$worker,"manager":$manager}'

View File

@@ -0,0 +1,75 @@
# VPC
resource "digitalocean_vpc" "subnet" {
name = "${var.environment}-subnet"
region = var.region
ip_range = var.subnet_range
}
# Firewall Rules
resource "digitalocean_firewall" "web" {
name = "${var.environment}-web"
tags = [digitalocean_tag.worker.id, digitalocean_tag.manager.id]
# HTTP/HTTPS
inbound_rule {
protocol = "tcp"
port_range = "80"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "tcp"
port_range = "443"
source_addresses = ["0.0.0.0/0", "::/0"]
}
# Outbound communication
outbound_rule {
protocol = "tcp"
port_range = "all"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "udp"
port_range = "all"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "icmp"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
}
resource "digitalocean_firewall" "vpc_communication" {
name = "${var.environment}-vpc-communication"
droplet_ids = [ digitalocean_droplet.nfs.id ]
tags = [digitalocean_tag.worker.id, digitalocean_tag.manager.id]
# Internal communication
inbound_rule {
protocol = "tcp"
port_range = "all"
source_addresses = [var.subnet_range]
}
inbound_rule {
protocol = "udp"
port_range = "all"
source_addresses = [var.subnet_range]
}
}
resource "digitalocean_firewall" "ssh" {
name = "${var.environment}-ssh"
droplet_ids = [ digitalocean_droplet.nfs.id ]
tags = [digitalocean_tag.worker.id, digitalocean_tag.manager.id]
# SSH
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
}
}

View File

@@ -0,0 +1,4 @@
output "leader_public_ip" {
value = digitalocean_droplet.leader.ipv4_address
description = "The public IP address of the leader node"
}

View File

@@ -0,0 +1,8 @@
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
}

View File

@@ -0,0 +1,44 @@
variable "private_key" {
description = "The path to the private key used to SSH into the droplets"
}
variable "project_name" {
description = "Name for the current infrastructure project"
}
variable "region" {
description = "The region to deploy the infrastructure to. See https://docs.digitalocean.com/products/platform/availability-matrix/#available-datacenters"
}
variable "environment" {
description = "Name of the current environment"
}
variable "base_image" {
description = "Base Image to use for all droplets"
}
variable "subnet_range" {
description = "Subnet range for the VPC"
}
variable "worker_size" {
description = "Size of the NFS node. See https://slugs.do-api.dev/"
}
variable "worker_count" {
description = "Count of worker nodes required"
}
variable "manager_size" {
description = "Size of the manager node. See https://slugs.do-api.dev/"
}
variable "manager_count" {
description = "Count of API nodes required"
}
variable "digitalocean_project_name" {
description = "Name of the DigitalOcean Project"
}
data "digitalocean_ssh_key" "Christy" {
name = "Christy"
}
data "external" "swarm_join_token" {
program = ["${path.module}/get-join-token.sh"]
query = {
host = "${digitalocean_droplet.leader.ipv4_address}"
}
}