mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-09 21:07:46 +00:00
Merge branch 'main' into tailwind-integration
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,3 +16,6 @@ package-lock.json
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
.history
|
||||
terraform/**/.t*
|
||||
terraform/**/.env
|
||||
terraform/**/**/*.tfstate*
|
||||
2
STYLE.md
2
STYLE.md
@@ -51,7 +51,7 @@ APIs section:
|
||||
|
||||
Tooling section:
|
||||
|
||||
- [Command Line](https://appwrite.io/docs/command-line)
|
||||
- [CLI](https://appwrite.io/docs/command-line)
|
||||
- [Command center](https://appwrite.io/docs/tooling/command-center)
|
||||
- [Assistant](https://appwrite.io/docs/tooling/assistant)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
email,
|
||||
cloud: true /* not optional on the growth endpoint. */,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -3,6 +3,7 @@ layout: post
|
||||
title: "Announcing: A new Init. Faster. Smoother. Better."
|
||||
description: Init is about to begin, and we’re 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
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
label: 'Tooling',
|
||||
items: [
|
||||
{
|
||||
label: 'Command Line',
|
||||
label: 'CLI',
|
||||
href: '/docs/tooling/command-line/installation',
|
||||
icon: 'icon-terminal',
|
||||
isParent: true
|
||||
|
||||
@@ -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)" %}
|
||||
|
||||
@@ -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,6 +860,8 @@ return function ($context) {
|
||||
};
|
||||
```
|
||||
```python
|
||||
from appwrite.input_file import InputFile
|
||||
|
||||
def main(context):
|
||||
type = context.req.query['type']
|
||||
|
||||
@@ -793,6 +869,8 @@ def main(context):
|
||||
return context.res.empty()
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
const parent: NavParent = {
|
||||
href: '/docs',
|
||||
label: 'Command Line'
|
||||
label: 'CLI'
|
||||
};
|
||||
|
||||
const navigation: NavTree = [
|
||||
|
||||
@@ -1,358 +1,357 @@
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { Tabs } from "$lib/UI";
|
||||
import { visible } from "$lib/actions/visible";
|
||||
import { isHeaderHidden } from "$lib/layouts/Main.svelte";
|
||||
import { getScrollDir } from "$lib/utils/getScrollDir";
|
||||
import { isVisible } from "$lib/utils/isVisible";
|
||||
import { createAccordion, melt } from "@melt-ui/svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import { fly } from "svelte/transition";
|
||||
import { browser } from '$app/environment';
|
||||
import { Tabs } from '$lib/UI';
|
||||
import { visible } from '$lib/actions/visible';
|
||||
import { isHeaderHidden } from '$lib/layouts/Main.svelte';
|
||||
import { getScrollDir } from '$lib/utils/getScrollDir';
|
||||
import { createAccordion, melt } from '@melt-ui/svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { fly } from 'svelte/transition';
|
||||
import Tooltip from '../../lib/components/Tooltip.svelte';
|
||||
|
||||
type Table = {
|
||||
title: string;
|
||||
rows: {
|
||||
title: string;
|
||||
info?: string;
|
||||
free: string | true;
|
||||
pro: string | true;
|
||||
scale: string | true;
|
||||
}[];
|
||||
};
|
||||
|
||||
const cols = ["free", "pro", "scale"] as const;
|
||||
const cols = ['free', 'pro', 'scale'] as const;
|
||||
|
||||
const tables = [
|
||||
{
|
||||
title: "Resources",
|
||||
title: 'Resources',
|
||||
rows: [
|
||||
{
|
||||
title: "Bandwidth",
|
||||
free: "10GB",
|
||||
pro: "300GB",
|
||||
scale: "300GB",
|
||||
title: 'Bandwidth',
|
||||
free: '10GB',
|
||||
pro: '300GB',
|
||||
scale: '300GB'
|
||||
},
|
||||
{
|
||||
title: "Additional bandwidth",
|
||||
free: "-",
|
||||
pro: "$40 per 100GB",
|
||||
scale: "$40 per 100GB",
|
||||
title: 'Additional bandwidth',
|
||||
free: '-',
|
||||
pro: '$40 per 100GB',
|
||||
scale: '$40 per 100GB'
|
||||
},
|
||||
{
|
||||
title: "Storage",
|
||||
free: "2GB",
|
||||
pro: "150GB",
|
||||
scale: "150GB",
|
||||
title: 'Storage',
|
||||
free: '2GB',
|
||||
pro: '150GB',
|
||||
scale: '150GB'
|
||||
},
|
||||
{
|
||||
title: "Additional storage",
|
||||
free: "-",
|
||||
pro: "$3 per 100GB",
|
||||
scale: "$3 per 100GB",
|
||||
title: 'Additional storage',
|
||||
free: '-',
|
||||
pro: '$3 per 100GB',
|
||||
scale: '$3 per 100GB'
|
||||
},
|
||||
{
|
||||
title: "Compute",
|
||||
free: "750K executions",
|
||||
pro: "3.5M executions",
|
||||
scale: "3.5M executions",
|
||||
},
|
||||
],
|
||||
title: 'Compute',
|
||||
free: '750K executions',
|
||||
pro: '3.5M executions',
|
||||
scale: '3.5M executions'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Platform",
|
||||
title: 'Platform',
|
||||
rows: [
|
||||
{
|
||||
title: "Number of projects",
|
||||
free: "Unlimited",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Number of projects',
|
||||
free: 'Unlimited',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "Projects pausing",
|
||||
free: "Never",
|
||||
pro: "Never",
|
||||
scale: "Never",
|
||||
title: 'Projects pausing',
|
||||
free: 'Never',
|
||||
pro: 'Never',
|
||||
scale: 'Never'
|
||||
},
|
||||
{
|
||||
title: "Organization Members",
|
||||
free: "1",
|
||||
pro: "1",
|
||||
scale: "Unlimited",
|
||||
title: 'Organization Members',
|
||||
free: '1',
|
||||
pro: '1',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "Additional Organization members",
|
||||
free: "-",
|
||||
pro: "$15 per member",
|
||||
scale: "$0",
|
||||
title: 'Additional Organization members',
|
||||
free: '-',
|
||||
pro: '$15 per member',
|
||||
scale: '$0'
|
||||
},
|
||||
{
|
||||
title: "Connected websites and apps",
|
||||
free: "3 per project",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Connected websites and apps',
|
||||
free: '3 per project',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "Custom domains",
|
||||
free: "Unlimited",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Custom domains',
|
||||
free: 'Unlimited',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "No Appwrite branding on emails",
|
||||
free: "-",
|
||||
title: 'No Appwrite branding on emails',
|
||||
free: '-',
|
||||
pro: true,
|
||||
scale: true,
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
title: "Custom SMTP",
|
||||
free: "-",
|
||||
title: 'Custom SMTP',
|
||||
free: '-',
|
||||
pro: true,
|
||||
scale: true,
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
title: "Webhooks",
|
||||
free: "2 per project",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Webhooks',
|
||||
free: '2 per project',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "Logs retention",
|
||||
free: "1 hour",
|
||||
pro: "7 days",
|
||||
scale: "28 days",
|
||||
title: 'Logs retention',
|
||||
free: '1 hour',
|
||||
pro: '7 days',
|
||||
scale: '28 days'
|
||||
},
|
||||
{
|
||||
title: "Budget caps and alerts",
|
||||
free: "Not needed",
|
||||
title: 'Budget caps and alerts',
|
||||
free: 'Not needed',
|
||||
pro: true,
|
||||
scale: true,
|
||||
},
|
||||
],
|
||||
scale: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Auth",
|
||||
title: 'Auth',
|
||||
rows: [
|
||||
{
|
||||
title: "Users",
|
||||
free: "75,000 monthly active users",
|
||||
pro: "200,000 monthly active users",
|
||||
scale: "200,000 monthly active users",
|
||||
title: 'Users',
|
||||
free: '75,000 monthly active users',
|
||||
pro: '200,000 monthly active users',
|
||||
scale: '200,000 monthly active users'
|
||||
},
|
||||
{
|
||||
title: "Additional users",
|
||||
free: "-",
|
||||
pro: "$3 per 1,000 users",
|
||||
scale: "$3 per 1,000 users",
|
||||
title: 'Additional users',
|
||||
free: '-',
|
||||
pro: '$3 per 1,000 users',
|
||||
scale: '$3 per 1,000 users'
|
||||
},
|
||||
{
|
||||
title: "Teams",
|
||||
free: "100 per project",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Teams',
|
||||
free: '100 per project',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "SSO",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: "Coming soon",
|
||||
},
|
||||
],
|
||||
title: 'SSO',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: 'Coming soon'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Databases",
|
||||
title: 'Databases',
|
||||
rows: [
|
||||
{
|
||||
title: "Databases",
|
||||
free: "1 per project",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Databases',
|
||||
free: '1 per project',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "Documents",
|
||||
free: "Unlimited",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Documents',
|
||||
free: 'Unlimited',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "Reads & Writes",
|
||||
free: "Unlimited",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Reads & Writes',
|
||||
free: 'Unlimited',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "Dedicated databases",
|
||||
free: "-",
|
||||
pro: "Coming Soon",
|
||||
scale: "Coming soon",
|
||||
},
|
||||
],
|
||||
title: 'Dedicated databases',
|
||||
free: '-',
|
||||
pro: 'Coming Soon',
|
||||
scale: 'Coming soon'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Storage",
|
||||
title: 'Storage',
|
||||
rows: [
|
||||
{
|
||||
title: "Buckets",
|
||||
free: "3 per project",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Buckets',
|
||||
free: '3 per project',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "File size limit",
|
||||
free: "50MB",
|
||||
pro: "5GB",
|
||||
scale: "5GB",
|
||||
title: 'File size limit',
|
||||
free: '50MB',
|
||||
pro: '5GB',
|
||||
scale: '5GB'
|
||||
},
|
||||
{
|
||||
title: "Image transformations",
|
||||
free: "Unlimited",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
},
|
||||
],
|
||||
title: 'Image transformations',
|
||||
free: 'Unlimited',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Functions",
|
||||
title: 'Functions',
|
||||
rows: [
|
||||
{
|
||||
title: "Functions",
|
||||
free: "5 per project",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
title: 'Functions',
|
||||
free: '5 per project',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
},
|
||||
{
|
||||
title: "Executions",
|
||||
free: "750K",
|
||||
pro: "3.5M",
|
||||
scale: "3.5M",
|
||||
title: 'Executions',
|
||||
free: '750K',
|
||||
pro: '3.5M',
|
||||
scale: '3.5M'
|
||||
},
|
||||
{
|
||||
title: "Additional executions",
|
||||
free: "-",
|
||||
pro: "$2 per 1 Million",
|
||||
scale: "$2 per 1 Million",
|
||||
title: 'Additional executions',
|
||||
free: '-',
|
||||
pro: '$2 per 1 Million',
|
||||
scale: '$2 per 1 Million'
|
||||
},
|
||||
{
|
||||
title: "Express builds",
|
||||
free: "-",
|
||||
title: 'Express builds',
|
||||
info: 'Dedicated priority queues for build jobs',
|
||||
free: '-',
|
||||
pro: true,
|
||||
scale: true,
|
||||
},
|
||||
],
|
||||
scale: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Realtime",
|
||||
title: 'Realtime',
|
||||
rows: [
|
||||
{
|
||||
title: "Concurrent connections",
|
||||
free: "250",
|
||||
pro: "500",
|
||||
scale: "500",
|
||||
title: 'Concurrent connections',
|
||||
free: '250',
|
||||
pro: '500',
|
||||
scale: '500'
|
||||
},
|
||||
{
|
||||
title: "Additional concurrent connections",
|
||||
free: "-",
|
||||
pro: "$5 per 1,000",
|
||||
scale: "$5 per 1,000",
|
||||
title: 'Additional concurrent connections',
|
||||
free: '-',
|
||||
pro: '$5 per 1,000',
|
||||
scale: '$5 per 1,000'
|
||||
},
|
||||
{
|
||||
title: "Messages",
|
||||
free: "3M",
|
||||
pro: "Unlimited",
|
||||
scale: "Unlimited",
|
||||
},
|
||||
],
|
||||
title: 'Messages',
|
||||
free: '3M',
|
||||
pro: 'Unlimited',
|
||||
scale: 'Unlimited'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Security",
|
||||
title: 'Security',
|
||||
rows: [
|
||||
{
|
||||
title: "SOC-2",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: true,
|
||||
title: 'SOC-2',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
title: "HIPAA",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: true,
|
||||
title: 'HIPAA',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
title: "BAA",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: true,
|
||||
title: 'BAA',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
title: "Additional organization roles",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: "Coming Soon",
|
||||
title: 'Additional organization roles',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: 'Coming Soon'
|
||||
},
|
||||
{
|
||||
title: "Network logs",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: "Coming Soon",
|
||||
title: 'Network logs',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: 'Coming Soon'
|
||||
},
|
||||
{
|
||||
title: "Activity logs",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: "Coming Soon",
|
||||
},
|
||||
],
|
||||
title: 'Activity logs',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: 'Coming Soon'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Support",
|
||||
title: 'Support',
|
||||
rows: [
|
||||
{
|
||||
title: "Community",
|
||||
title: 'Community',
|
||||
free: true,
|
||||
pro: true,
|
||||
scale: true,
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
title: "Email",
|
||||
free: "-",
|
||||
title: 'Email',
|
||||
free: '-',
|
||||
pro: true,
|
||||
scale: true,
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
title: "Priority",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: true,
|
||||
title: 'Priority',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: true
|
||||
},
|
||||
{
|
||||
title: "SLA",
|
||||
free: "-",
|
||||
pro: "-",
|
||||
scale: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
title: 'SLA',
|
||||
free: '-',
|
||||
pro: '-',
|
||||
scale: true
|
||||
}
|
||||
]
|
||||
}
|
||||
] satisfies Table[];
|
||||
|
||||
let tab = "free";
|
||||
let tab = 'free';
|
||||
|
||||
const {
|
||||
elements: { root, trigger, content, heading, item },
|
||||
states: { value },
|
||||
states: { value }
|
||||
} = createAccordion({
|
||||
defaultValue: ["Resources"],
|
||||
defaultValue: ['Resources'],
|
||||
multiple: true,
|
||||
forceVisible: true,
|
||||
forceVisible: true
|
||||
});
|
||||
|
||||
const visibleTables = writable([] as string[]);
|
||||
$: activeTable = $visibleTables.sort((a, b) => {
|
||||
return (
|
||||
tables.findIndex((t) => t.title === a) -
|
||||
tables.findIndex((t) => t.title === b)
|
||||
);
|
||||
return tables.findIndex((t) => t.title === a) - tables.findIndex((t) => t.title === b);
|
||||
})[0];
|
||||
|
||||
let scrollDir = "down";
|
||||
let scrollDir = 'down';
|
||||
let shouldShowTable = false;
|
||||
</script>
|
||||
|
||||
@@ -360,9 +359,9 @@
|
||||
|
||||
<div class="web-big-padding-section-level-1 web-white-section theme-light">
|
||||
<div class="web-big-padding-section-level-2">
|
||||
<div class="relative">
|
||||
<div class="u-position-relative">
|
||||
<article use:melt={$root}>
|
||||
<div class="container">
|
||||
<div class="web-container">
|
||||
<header
|
||||
class="web-u-text-align-center"
|
||||
use:visible
|
||||
@@ -371,14 +370,13 @@
|
||||
}}
|
||||
>
|
||||
<h3 class="web-title web-u-color-text-primary">Compare plans</h3>
|
||||
<p class="web-main-body-500 mt-4">
|
||||
Discover our plans and find the one that fits your project’s
|
||||
needs.
|
||||
<p class="web-main-body-500 u-margin-block-start-16">
|
||||
Discover our plans and find the one that fits your project’s needs.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="web-is-only-mobile web-u-padding-block-start-48 web-u-padding-inline-8 web-u-margin-inline-8-negative web-u-filter-blur-8"
|
||||
class="web-is-only-mobile web-u-padding-block-start-48 web-u-padding-inline-8 web-u-margin-inline-8-negative web-u-filter-blur-8 /u-position-sticky /u-z-index-5"
|
||||
style:--inset-block-start="2rem"
|
||||
>
|
||||
<Tabs bind:tab tabs={cols} let:TabsList>
|
||||
@@ -390,34 +388,31 @@
|
||||
--p-secondary-tabs-bg-color-selected: var(--web-color-accent) / 0.08;"
|
||||
let:tab
|
||||
>
|
||||
<span class="web-main-body-500 capitalize">{tab}</span>
|
||||
<span class="web-main-body-500 u-capitalize">{tab}</span>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="web-is-not-mobile web-u-grid-auto-column-1fr is-with-footer-border web-u-padding-inline-8 web-u-margin-inline-8-negative web-u-filter-blur-8 web-u-container-query-inline sticky z-[5] gap-8"
|
||||
style:--inset-block-start={$isHeaderHidden ? "0px" : "70px"}
|
||||
class="web-is-not-mobile web-u-grid-auto-column-1fr is-with-footer-border u-gap-32 web-u-padding-inline-8 web-u-margin-inline-8-negative web-u-filter-blur-8 u-position-sticky u-z-index-5 web-u-container-query-inline"
|
||||
style:--inset-block-start={$isHeaderHidden ? '0px' : '70px'}
|
||||
style:transition="inset-block-start 0.3s ease"
|
||||
>
|
||||
<div
|
||||
class="web-description web-u-color-text-primary web-self-center"
|
||||
class="web-description web-u-color-text-primary web-u-cross-child-center"
|
||||
style:opacity={browser ? 1 : 0}
|
||||
style:position={browser ? "relative" : undefined}
|
||||
style:position={browser ? 'relative' : undefined}
|
||||
>
|
||||
<span style="opacity: 0;">{activeTable}</span>
|
||||
{#key `${activeTable}-${shouldShowTable}`}
|
||||
<div
|
||||
style="position: absolute; top: 50%; transform: translateY(-50%);"
|
||||
in:fly={{
|
||||
y: scrollDir === "down" ? 16 : -16,
|
||||
y: scrollDir === 'down' ? 16 : -16,
|
||||
delay: 250,
|
||||
duration: 250,
|
||||
}}
|
||||
out:fly={{
|
||||
y: scrollDir === "down" ? -16 : 16,
|
||||
duration: 250,
|
||||
duration: 250
|
||||
}}
|
||||
out:fly={{ y: scrollDir === 'down' ? -16 : 16, duration: 250 }}
|
||||
>
|
||||
{#if shouldShowTable && activeTable}
|
||||
{activeTable}
|
||||
@@ -426,7 +421,9 @@
|
||||
{/key}
|
||||
</div>
|
||||
<div class="web-mini-card">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div
|
||||
class="u-flex u-cross-center u-gap-16 u-flex-wrap u-main-space-between"
|
||||
>
|
||||
<h4 class="web-label web-u-color-text-primary">Free</h4>
|
||||
<a
|
||||
href="https://cloud.appwrite.io/register"
|
||||
@@ -437,7 +434,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="web-mini-card">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div
|
||||
class="u-flex u-cross-center u-gap-16 u-flex-wrap u-main-space-between"
|
||||
>
|
||||
<h4 class="web-label web-u-color-text-primary">Pro</h4>
|
||||
<a
|
||||
class="web-button"
|
||||
@@ -451,7 +450,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="web-mini-card">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div
|
||||
class="u-flex u-cross-center u-gap-16 u-flex-wrap u-main-space-between"
|
||||
>
|
||||
<h4 class="web-label web-u-color-text-primary">Scale</h4>
|
||||
<button class="web-button is-secondary" disabled>
|
||||
<span class="web-sub-body-500">Coming soon</span>
|
||||
@@ -482,7 +483,7 @@
|
||||
<caption
|
||||
class="web-compare-table-caption web-description web-u-color-text-primary"
|
||||
use:melt={$heading({ level: 3 })}
|
||||
style:position={browser ? "unset" : undefined}
|
||||
style:position={browser ? 'unset' : undefined}
|
||||
>
|
||||
<button
|
||||
class="web-compare-table-caption-button"
|
||||
@@ -496,16 +497,28 @@
|
||||
</button>
|
||||
</caption>
|
||||
|
||||
<tbody
|
||||
class="web-compare-table-body"
|
||||
use:melt={$content(table.title)}
|
||||
>
|
||||
<tbody class="web-compare-table-body" use:melt={$content(table.title)}>
|
||||
{#each table.rows as row}
|
||||
<tr>
|
||||
<th class="web-sub-body-500">{row.title}</th>
|
||||
<th class="web-sub-body-500">
|
||||
<div class="u-flex u-gap-4">
|
||||
{row.title}
|
||||
{#if row.info}
|
||||
<Tooltip placement="top">
|
||||
<span class="icon-info" aria-hidden="true" />
|
||||
<svelte:fragment slot="tooltip">
|
||||
{row.info}
|
||||
</svelte:fragment>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
</th>
|
||||
{#each cols as col, index}
|
||||
<td class="level-{index}" class:is-selected={col === tab}>
|
||||
{#if typeof row[col] === "string"}
|
||||
<td
|
||||
class="level-{index}"
|
||||
class:is-selected={col === tab}
|
||||
>
|
||||
{#if typeof row[col] === 'string'}
|
||||
{row[col]}
|
||||
{:else}
|
||||
<img
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
key,
|
||||
cloud: true /* not optional on the growth endpoint. */,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
2
terraform/.env.example
Normal file
2
terraform/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
export TF_VAR_DO_TOKEN=
|
||||
export TF_VAR_PRIVATE_KEY=
|
||||
16
terraform/environments/production/main.tf
Normal file
16
terraform/environments/production/main.tf
Normal 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"
|
||||
}
|
||||
18
terraform/environments/production/provider.tf
Normal file
18
terraform/environments/production/provider.tf
Normal 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
|
||||
}
|
||||
7
terraform/environments/production/variables.tf
Normal file
7
terraform/environments/production/variables.tf
Normal 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)"
|
||||
}
|
||||
16
terraform/environments/staging/main.tf
Normal file
16
terraform/environments/staging/main.tf
Normal 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"
|
||||
}
|
||||
18
terraform/environments/staging/provider.tf
Normal file
18
terraform/environments/staging/provider.tf
Normal 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
|
||||
}
|
||||
7
terraform/environments/staging/variables.tf
Normal file
7
terraform/environments/staging/variables.tf
Normal 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)"
|
||||
}
|
||||
153
terraform/modules/digitalocean/droplets.tf
Normal file
153
terraform/modules/digitalocean/droplets.tf
Normal 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",
|
||||
]
|
||||
}
|
||||
}
|
||||
14
terraform/modules/digitalocean/get-join-token.sh
Executable file
14
terraform/modules/digitalocean/get-join-token.sh
Executable 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}'
|
||||
75
terraform/modules/digitalocean/networking.tf
Normal file
75
terraform/modules/digitalocean/networking.tf
Normal 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"]
|
||||
}
|
||||
}
|
||||
4
terraform/modules/digitalocean/outputs.tf
Normal file
4
terraform/modules/digitalocean/outputs.tf
Normal file
@@ -0,0 +1,4 @@
|
||||
output "leader_public_ip" {
|
||||
value = digitalocean_droplet.leader.ipv4_address
|
||||
description = "The public IP address of the leader node"
|
||||
}
|
||||
8
terraform/modules/digitalocean/provider.tf
Normal file
8
terraform/modules/digitalocean/provider.tf
Normal file
@@ -0,0 +1,8 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
digitalocean = {
|
||||
source = "digitalocean/digitalocean"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
44
terraform/modules/digitalocean/variables.tf
Normal file
44
terraform/modules/digitalocean/variables.tf
Normal 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}"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user