Merge pull request #12568 from supabase/master

Prod deploy Dashboard
This commit is contained in:
Joshen Lim authored and GitHub committed 2023-02-21 13:48:02 +08:00
commit 91c7ea864d
62 files changed
+311 -167

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code.
uses: actions/checkout@v1
uses: actions/checkout@v3
- name: misspell
uses: reviewdog/action-misspell@v1
with:
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Run prettier
run: |-
npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}'
@@ -41,7 +41,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Run prettier
run: |-
npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}'
+2 -2
View File
@@ -18,9 +18,9 @@ jobs:
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
+2 -2
View File
@@ -24,9 +24,9 @@ jobs:
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
@@ -43,6 +43,12 @@ Supabase supports a suite of social providers. Follow these guides to configure
))}
</div>
## Provider Tokens
Once the OAuth flow completes, Supabase Auth will sign your user in. You will receive a copy of the provider token used in the OAuth flow in case you need to use it further. For example, you can use the Google provider token to access Google APIs on behalf of your user.
Provider tokens are intentionally not stored in your project's database, however. This is because provider tokens give access to potentially sensitive user data in third-party systems. Different applications have different needs, and one application's OAuth scopes may be significantly more permissive than another. If you do want to use the provider token outside of the browser that completed the OAuth flow, you will have to send it manually to a secure server under your control.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -73,7 +73,7 @@ serve(async (_req) => {
```bash
supabase start
supabase functions serve upstash-redis-counter --no-verify-jwt --env-file supabase/functions/upstash-redis-counter/.env
supabase functions serve --no-verify-jwt --env-file supabase/functions/upstash-redis-counter/.env
```
Navigate to http://localhost:54321/functions/v1/upstash-redis-counter.
@@ -10,7 +10,7 @@ You can run your Edge Function locally using [`supabase functions serve`](/docs/
```bash
supabase start # start the supabase stack
supabase functions serve hello-world # start the Function watcher
supabase functions serve # start the Functions watcher
```
The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server.
@@ -20,7 +20,7 @@ The `functions serve` command has hot-reloading capabilities. It will watch for
While serving your local Edge Function, you can invoke it using curl:
```bash
curl --request POST 'http://localhost:54321/functions/v1/hello-world' \
curl --request POST 'http://localhost:54321/functions/v1/function-name' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
--header 'Content-Type: application/json' \
--data '{ "name":"Functions" }'
@@ -37,7 +37,7 @@ const supabase = createClient(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'
)
const { data, error } = await supabase.functions.invoke('hello-world', {
const { data, error } = await supabase.functions.invoke('function-name', {
body: { name: 'Functions' },
})
```
+1 -1
View File
@@ -46,7 +46,7 @@ console.log(Deno.env.get('MY_NAME'))
Now we can invoke our function locally, by serving it with our new `.env.local` file:
```bash
supabase functions serve hello-world --env-file ./supabase/.env.local
supabase functions serve --env-file ./supabase/.env.local
```
When the function starts you should see the name “Yoda” output to the terminal.
@@ -0,0 +1,58 @@
---
title: HappyTeams unlocks better performance and reduces cost with Supabase
description: How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back
author: rory_wilding
image: 2023-02-16-case-study-happyteams/case-study-happyteams.png
thumb: 2023-02-16-case-study-happyteams/case-study-happyteams.png
tags:
- case-study
date: '2023-02-16'
toc_depth: 3
---
## The Challenge 
> Finding a painless and hands-free database experience to support growth
[HappyTeams](https://happyteams.io/) is a growing startup with an ambitious vision. The company is bootstrapped, so technical resources are scarce. Devs need to do more with less. This means staying laser-focused on delivering features, rather than database timeouts or performance issues.
The company launched its app on Heroku Postgres. But one year in, they were experiencing scaling issues. They needed to handle thousands of users while also shipping new features and building out integrations. Issues with database slowness and concurrent connections were holding them back.
Michael Dever, the founder and CTO of HappyTeams, realized it was time for a change. A large overhaul of the app in early 2022 presented the opportunity to do so: "When you're in that first sprint of building a product, you're kind of locked with that first database decision you make. When we did a rebuild, it was time to look at something like Supabase."
![HappyTeams Home Page](/images/blog/2023-02-16-case-study-happyteams/happyteams-home-page.png)
## Why Supabase?
There were four main factors that led HappyTeams to switch from Heroku Postgres to Supabase:
1. **Better performance**. The team was experiencing uneven performance with Heroku, which was sluggish at times. Supabase was consistently fast.
2. **Enabling a large number of direct connections and connection pooling with PgBouncer**. HappyTeams was constantly hitting limits on the number of database connections. They could solve this problem with Supabase's [flexible plans](https://supabase.com/pricing) and [support for PgBouncer](https://supabase.com/blog/supabase-pgbouncer).
3. **Quality of support, documentation, and onboarding**: While Heroku provided no support at the tier they were on, the Supabase team was readily available: "Responses were powerful and they answered all my questions, even on a Sunday. That just further sold the product for me." He was also impressed with the [documentation](https://supabase.com/docs) on the Supabase website and the onboarding process.
4. **Lower costs**. Moving to Supabase reduced HappyTeams's costs: The base plan comes with everything they need to scale the product.
<Quote img="michael-dever-happyteams.jpeg" caption="Michael Dever - CTO of HappyTeams">
Great documentation, great product, great pricing. It sells itself, really.
</Quote>
## Supabase provides a fast and reliable production database
After a 30 minute migration, HappyTeams can iterate quickly, add new features, and improve their product based on customer feedback
Once they made the decision, migrating from Heroku to Supabase was simple. HappyTeams completed their database migration in less than an hour using pg_dump and pg_restore.
Performance is no longer an issue. "Supabase just worked", says Michael. Pooling connections using PgBouncer was effortless - solving a recurring pain point for HappyTeams.
"Supabase is so fast. Way, way, way faster than our Heroku Postgres database, which I think was located in the same region."
Today, Supabase is an integral part of HappyTeams's tech stack. It has allowed the company to move fast and rapidly release new features - with minimal tech resources devoted to their backend infrastructure.
Thanks to Supabase's ease of use and reliability, HappyTeams can iterate quickly based on feature requests and deliver more value to their user base. They are planning to expand their usage, exploring functionality such as [Edge Functions](https://supabase.com/edge-functions) and the [API client](https://supabase.com/docs/guides/api).
<Quote img="michael-dever-happyteams.jpeg" caption="Michael Dever - CTO of HappyTeams">
When we needed to add a comments feature to our app, we could get it done in just two days.
Supabase is really like a dream come true for a developer.
</Quote>
Binary file not shown.

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+8 -1
View File
@@ -5,9 +5,16 @@
<link>https://supabase.com</link>
<description>Latest news from Supabase</description>
<language>en</language>
<lastBuildDate>Mon, 13 Feb 2023 16:04:00 +0000</lastBuildDate>
<lastBuildDate>Wed, 15 Feb 2023 16:04:00 +0000</lastBuildDate>
<atom:link href="https://supabase.com/rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/case-study-happyteams</guid>
<title>HappyTeams unlocks better performance and reduces cost with Supabase</title>
<link>https://supabase.com/blog/case-study-happyteams</link>
<description>How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back</description>
<pubDate>Wed, 15 Feb 2023 16:04:00 +0000</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/flutter-real-time-multiplayer-game</guid>
<title>How to build a real-time multiplayer game with Flutter Flame</title>
<link>https://supabase.com/blog/flutter-real-time-multiplayer-game</link>
+1
View File
@@ -2,3 +2,4 @@ volumes/db/data
volumes/storage
.env
test.http
docker-compose.override.yml
+9 -12
View File
@@ -6,23 +6,17 @@
## Example Functions
The function examples are located in [`./supabase/functions`](./supabase/functions):
- [`browser-with-cors`](./supabase/functions/browser-with-cors/index.ts): Handle CORS headers for function invocations from browser environments.
- [`select-from-table-with-auth-rls`](./supabase/functions/select-from-table-with-auth-rls/index.ts): Retrieve data from an authenticated user via RLS.
- [`send-email-smtp`](./supabase/functions/send-email-smtp/index.ts): Send an email using SMTP credentials.
- [`stripe-webhooks`](./supabase/functions/stripe-webhooks/index.ts): Handle Stripe Webhooks.
- [`telegram-bot`](./supabase/functions/telegram-bot/index.ts): Webhook handler for Telegram bots using [grammY](https://grammy.dev/).
We're constantly adding new Function Examples, [check our docs](https://supabase.com/docs/guides/functions#examples) for a complete list!
## Develop locally
- Run `supabase start` (make sure your Docker daemon is running.)
- Run `mv ./supabase/.env.local.example ./supabase/.env.local` to rename the local `.env` file.
- Set the required variables to run the desired edge functions in the `.env.local` file.
- Run `supabase functions serve your-function-name --env-file ./supabase/.env.local`
- Run `cp ./supabase/.env.local.example ./supabase/.env.local` to create your local `.env` file.
- Set the required variables for the corresponding edge functions in the `.env.local` file.
- Run `supabase functions serve --env-file ./supabase/.env.local --no-verify-jwt`
- Run the CURL command in the example function, or use the [invoke method](https://supabase.com/docs/reference/javascript/invoke) on the Supabase client or use the test client [app](./app/).
## Test
## Test Client
This example includes a create-react-app in the [`./app/`](./app/) directory which you can use as a sort of postman to make test requests both locally and to your deployed functions.
@@ -45,10 +39,13 @@ Note: when testing locally, the select dropdown doesn't have any effect, and inv
- Link your project
- Within your project root run `supabase link --project-ref your-project-ref`
- Set up your secrets
- Run `supabase secrets set --env-file ./supabase/.env.local` to set the environment variables.
(This is assuming your local and production secrets are the same. The recommended way is to create a separate `.env` file for storing production secrets, and then use it to set the environment variables while deploying.)
- You can run `supabase secrets list` to check that it worked and also to see what other env vars are set by default.
- Deploy the function
- Within your project root run `supabase functions deploy your-function-name`
- In your [`./app/.env`](./app/.env) file remove the `SUPA_FUNCTION_LOCALHOST` variable and restart your Expo app.
@@ -18,6 +18,7 @@
"node_modules/": true,
"app/": true,
"supabase/functions/": true
}
},
"deno.importMap": "./supabase/functions/import_map.json"
}
}
@@ -1,3 +1,21 @@
# cloudflare-turnstile
CLOUDFLARE_TURNSTILE_SECRET_KEY=your_secret_key
# discord-bot
DISCORD_PUBLIC_KEY=
# location
IPINFO_TOKEN="your https://ipinfo.io token"
# openai
OPENAI_API_KEY="<YOUR API KEY HERE>"
# postgres-on-the-edge
DATABASE_URL=
# puppeteer
PUPPETEER_BROWSERLESS_IO_TOKEN=
# send-email-smtp
SMTP_HOSTNAME="your.hostname.com"
SMTP_PORT="2587"
@@ -5,13 +23,14 @@ SMTP_USERNAME="your_username"
SMTP_PASSWORD="your_password"
SMTP_FROM="no-reply@example.com"
# telegram-bot
TELEGRAM_BOT_TOKEN="get it from https://t.me/BotFather"
FUNCTION_SECRET="random secret"
# stripe-webhooks
STRIPE_API_KEY="<YOUR API KEY HERE>"
STRIPE_WEBHOOK_SIGNING_SECRET="<YOUR WEBHOOK SIGNING HERE>"
# openai
OPENAI_API_KEY="<YOUR API KEY HERE>"
# telegram-bot
TELEGRAM_BOT_TOKEN="get it from https://t.me/BotFather"
FUNCTION_SECRET="random secret"
# upstash-redis-counter
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
@@ -1,5 +1,5 @@
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": true
"deno.unstable": true,
"deno.importMap": "./import_map.json"
}
@@ -2,7 +2,7 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { serve } from 'std/server'
import { corsHeaders } from '../_shared/cors.ts'
console.log(`Function "browser-with-cors" up and running!`)
@@ -2,6 +2,10 @@
Turnstile is Cloudflare's CAPTCHA alternative: https://developers.cloudflare.com/turnstile/get-started/
## Watch the Video Tutorial
[![video tutorial](https://img.youtube.com/vi/OwW0znboh60/0.jpg)](https://www.youtube.com/watch?v=OwW0znboh60)
## Setup
- Follow these steps to set up a new site: https://developers.cloudflare.com/turnstile/get-started/
@@ -2,7 +2,7 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { serve } from 'std/server'
import { corsHeaders } from '../_shared/cors.ts'
console.log(`Function "cloudflare-turnstile" up and running!`)
@@ -55,7 +55,7 @@ serve(async (req) => {
})
// To invoke:
// curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
// curl -i --location --request POST 'http://localhost:54321/functions/v1/cloudflare-turnstile' \
// --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
// --header 'Content-Type: application/json' \
// --data '{"token":"cf-turnstile-response"}'
@@ -5,6 +5,10 @@
- https://deno.com/deploy/docs/tutorial-discord-slash
- https://discord.com/developers/docs/interactions/application-commands#slash-commands
## Watch the Video Tutorial
[![video tutorial](https://img.youtube.com/vi/J24Bvo_m7DM/0.jpg)](https://www.youtube.com/watch?v=J24Bvo_m7DM)
## Create an application on Discord Developer Portal
1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications) (login using your discord account if required).
@@ -59,6 +63,6 @@ Open Discord, type `/Promise` and press **Enter**.
## Run locally
```bash
supabase functions serve discord-bot --no-verify-jwt --env-file ./supabase/functions/discord-bot/.env
supabase functions serve discord-bot --no-verify-jwt --env-file ./supabase/.env.local
ngrok http 54321
```
@@ -5,10 +5,10 @@
// Sift is a small routing library that abstracts away details like starting a
// listener on a port, and provides a simple function (serve) that has an API
// to invoke a function for a specific path.
import { json, serve, validateRequest } from 'https://deno.land/x/sift@0.6.0/mod.ts'
import { json, serve, validateRequest } from 'sift'
// TweetNaCl is a cryptography library that we use to verify requests
// from Discord.
import nacl from 'https://cdn.skypack.dev/tweetnacl@v1.0.3?dts'
import nacl from 'nacl'
enum DiscordCommandType {
Ping = 1,
@@ -1,8 +1,8 @@
// This example shows how to use Edge Functions to read incoming multipart/form-data request,
// and write files to Supabase Storage and other fields to a database table.
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { Application } from 'oak'
import { createClient } from '@supabase/supabase-js'
const MB = 1024 * 1024
@@ -25,9 +25,9 @@ app.use(async (ctx) => {
const supabaseClient = createClient(
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_URL')!,
// Supabase API ANON KEY - env var exported by default.
Deno.env.get('SUPABASE_ANON_KEY') ?? ''
Deno.env.get('SUPABASE_ANON_KEY')!
)
//upload image to Storage
@@ -15,9 +15,11 @@ Good luck trying to GET a T-Shirt!
### Serve this function locally
```bash
supabase functions serve --no-verify-jwt get-tshirt-competition
supabase functions serve --no-verify-jwt
```
Navigate to http://localhost:54321/functions/v1/get-tshirt-competition?email=testr@test.de&twitter=thorwebdev&size=2XL&answer=20
### Deploy this function
```bash
@@ -2,8 +2,8 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { serve } from 'std/server'
import { createClient } from '@supabase/supabase-js'
import { corsHeaders } from '../_shared/cors.ts'
console.log(`Function "get-tshirt-competition" up and running!`)
@@ -2,23 +2,18 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from "https://deno.land/std@0.131.0/http/server.ts";
import { serve } from 'std/server'
console.log("Hello from Functions!");
console.log('Hello from Functions!')
serve(async (req) => {
const { name } = await req.json();
serve((_req) => {
const data = {
message: `I was deployed via GitHub Actions!`,
};
}
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
});
headers: { 'Content-Type': 'application/json' },
})
})
// To invoke:
// curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
// --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
// --header 'Content-Type: application/json' \
// --data '{"name":"Functions"}'
// To invoke: http://localhost:54321/functions/v1/github-action-deploy
@@ -0,0 +1,20 @@
{
"imports": {
"denomailer": "https://deno.land/x/denomailer@0.12.0/mod.ts",
"nacl": "https://cdn.skypack.dev/tweetnacl@v1.0.3?dts",
"oak": "https://deno.land/x/oak@v11.1.0/mod.ts",
"og_edge": "https://deno.land/x/og_edge@0.0.4/mod.ts",
"openai": "https://esm.sh/openai@3.1.0",
"grammy": "https://deno.land/x/grammy@v1.8.3/mod.ts",
"react": "https://esm.sh/react@18.2.0",
"std/server": "https://deno.land/std@0.177.0/http/server.ts",
"stripe": "https://esm.sh/stripe@11.1.0?target=deno",
"sift": "https://deno.land/x/sift@0.6.0/mod.ts",
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2.7.1",
"postgres": "https://deno.land/x/postgres@v0.14.2/mod.ts",
"puppeteer": "https://deno.land/x/puppeteer@16.2.0/mod.ts",
"React": "https://esm.sh/react@18.2.0?deno-std=0.177.0",
"upstash_redis": "https://deno.land/x/upstash_redis@v1.19.3/mod.ts",
"xhr_polyfill": "https://deno.land/x/xhr@0.3.0/mod.ts"
}
}
@@ -4,6 +4,14 @@ This example shows how you can get user location based on the IP provided in X-F
You will need to signup for an account in https://ipinfo.io and provide it as `IPINFO_TOKEN` environment variable ([learn how to set environment variables to your functions](https://supabase.com/docs/guides/functions#secrets-and-environment-variables)).
## Develop locally
```bash
supabase functions serve --env-file ./supabase/.env.local --no-verify-jwt
```
Navigate to http://localhost:54321/functions/v1/location
## Deploy
```bash
@@ -2,20 +2,29 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from "https://deno.land/std@0.131.0/http/server.ts"
import { serve } from 'std/server'
function ips(req: Request) {
return req.headers.get("x-forwarded-for")?.split(/\s*,\s*/);
return req.headers.get('x-forwarded-for')?.split(/\s*,\s*/)
}
serve(async (req) => {
const clientIps = ips(req) || [''];
const res = await fetch(`https://ipinfo.io/${clientIps[0]}?token=${Deno.env.get('IPINFO_TOKEN')}`, {
headers: { 'Content-Type': 'application/json'}});
const { city, country } = await res.json();
return new Response(
JSON.stringify(`You're accessing from ${city}, ${country}`),
{ headers: { "Content-Type": "application/json" } },
const clientIps = ips(req) || ['']
const res = await fetch(
`https://ipinfo.io/${clientIps[0]}?token=${Deno.env.get('IPINFO_TOKEN')}`,
{
headers: { 'Content-Type': 'application/json' },
}
)
if (res.ok) {
const { city, country } = await res.json()
return new Response(JSON.stringify(`You're accessing from ${city}, ${country}`), {
headers: { 'Content-Type': 'application/json' },
})
} else {
return new Response(await res.text(), {
status: 400,
})
}
})
@@ -5,7 +5,7 @@ This example shows how you can write functions using Oak server middleware (http
## Run locally
```bash
supabase functions serve oak-server
supabase functions serve --no-verify-jwt
```
Use cURL or Postman to make a POST request to http://localhost:54321/functions/v1/oak-server.
@@ -20,5 +20,5 @@ Use cURL or Postman to make a POST request to http://localhost:54321/functions/v
## Deploy
```bash
supabase functions deploy oak-server
supabase functions deploy oak-server --no-verify-jwt
```
@@ -1,4 +1,4 @@
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
import { Application, Router } from 'oak'
const router = new Router()
router
@@ -9,7 +9,7 @@ Generate Open Graph images with Deno and Supabase Edge Functions and cache the g
## Run locally
```bash
supabase functions serve og-image-with-storage-cdn --no-verify-jwt
supabase functions serve --no-verify-jwt
```
Navigate to http://localhost:54321/functions/v1/og-image-with-storage-cdn?ticketNumber=3524&username=thorwebdev&name=Thor%20%E9%9B%B7%E7%A5%9E%20Schaeff
@@ -1,6 +1,6 @@
import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0'
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import React from 'React'
import { ImageResponse } from 'og_edge'
import { createClient } from '@supabase/supabase-js'
import { corsHeaders } from '../_shared/cors.ts'
const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw6'
@@ -213,7 +213,7 @@ export async function handler(req: Request) {
})
if (error) throw error
return generatedImage
return await fetch(`${STORAGE_URL}/tickets/${username}.png?v=3`)
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
@@ -2,7 +2,7 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.140.0/http/server.ts'
import { serve } from 'std/server'
import { handler } from './handler.tsx'
@@ -9,7 +9,7 @@ cp supabase/.env.local.example supabase/.env.local
## Run locally
```bash
supabase functions serve --no-verify-jwt --env-file ./supabase/.env.local openai
supabase functions serve --env-file ./supabase/.env.local --no-verify-jwt
```
Use cURL or Postman to make a POST request to http://localhost:54321/functions/v1/openai.
@@ -1,6 +1,6 @@
import 'https://deno.land/x/xhr@0.3.0/mod.ts'
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { CreateCompletionRequest } from 'https://esm.sh/openai@3.1.0'
import 'xhr_polyfill'
import { serve } from 'std/server'
import { CreateCompletionRequest } from 'openai'
serve(async (req) => {
const { query } = await req.json()
@@ -8,7 +8,7 @@ Generate Open Graph images with Deno and Supabase Edge Functions, no framework n
## Run locally
```bash
supabase functions serve opengraph --no-verify-jwt
supabase functions serve --no-verify-jwt
```
Navigate to http://localhost:54321/functions/v1/opengraph
@@ -1,5 +1,5 @@
import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0'
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts'
import React from 'React'
import { ImageResponse } from 'og_edge'
export function handler(req: Request) {
return new ImageResponse(
@@ -2,7 +2,7 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.140.0/http/server.ts'
import { serve } from 'std/server'
import { handler } from './handler.tsx'
@@ -1,5 +1,5 @@
import * as postgres from 'https://deno.land/x/postgres@v0.14.2/mod.ts'
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import * as postgres from 'postgres'
import { serve } from 'std/server'
// Get the connection string from the environment variable "DATABASE_URL"
const databaseUrl = Deno.env.get('DATABASE_URL')!
@@ -4,6 +4,14 @@ This example shows how you can use Puppeteer and a headless-browser to generate
Since Edge Functions cannot run a Headless Browser instance due to resource constraints, you will need to use a hosted browser service like https://browserless.io.
## Develop locally
```bash
supabase functions serve --env-file ./supabase/.env.local --no-verify-jwt
```
Navigate to http://localhost:54321/functions/v1/puppeteer
## Deploy
```bash
@@ -1,11 +1,14 @@
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import puppeteer from 'https://deno.land/x/puppeteer@16.2.0/mod.ts'
import { serve } from 'std/server'
import puppeteer from 'puppeteer'
serve(async (req) => {
try {
console.log(`wss://chrome.browserless.io?token=${Deno.env.get('PUPPETEER_BROWSERLESS_IO_KEY')}`)
// Visit browserless.io to get your free API token
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://chrome.browserless.io?token=YOUR_API_TOKEN',
browserWSEndpoint: `wss://chrome.browserless.io?token=${Deno.env.get(
'PUPPETEER_BROWSERLESS_IO_KEY'
)}`,
})
const page = await browser.newPage()
@@ -17,7 +20,7 @@ serve(async (req) => {
return new Response(screenshot, { headers: { 'Content-Type': 'image/png' } })
} catch (e) {
console.error(e)
return new Response(JSON.stringify(`Error occurred when generating the screenshot`), {
return new Response(JSON.stringify({ error: e.message }), {
headers: { 'Content-Type': 'application/json' },
status: 500,
})
@@ -2,8 +2,8 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { serve } from 'std/server'
import { createClient } from '@supabase/supabase-js'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
@@ -2,8 +2,8 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { createClient, SupabaseClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { serve } from 'std/server'
import { createClient, SupabaseClient } from '@supabase/supabase-js'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
@@ -2,8 +2,8 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { serve } from 'std/server'
import { createClient } from '@supabase/supabase-js'
import { corsHeaders } from '../_shared/cors.ts'
console.log(`Function "select-from-table-with-auth-rls" up and running!`)
@@ -13,4 +13,4 @@ Note: `SMTP_PORT` must be a port other than `25`, `465`, and `587` as Deno Deplo
## Test locally
- `cp ./supabase/.env.local.example ./supabase/.env.local`
- `supabase functions serve send-email-smtp --env-file ./supabase/.env.local`
- `supabase functions serve --env-file ./supabase/.env.local`
@@ -2,14 +2,14 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { SmtpClient } from 'https://deno.land/x/denomailer@0.12.0/mod.ts'
import { serve } from 'std/server'
import { SmtpClient } from 'denomailer'
const smtp = new SmtpClient()
console.log(`Function "send-email-smtp" up and running!`)
serve(async (req) => {
serve(async (_req) => {
await smtp.connect({
hostname: Deno.env.get('SMTP_HOSTNAME')!,
port: Number(Deno.env.get('SMTP_PORT')!),
@@ -24,7 +24,7 @@ serve(async (req) => {
subject: `Hello from Supabase Edge Functions`,
content: `Hello Functions \o/`,
})
} catch (error: any) {
} catch (error) {
return new Response(error.message, { status: 500 })
}
@@ -41,7 +41,7 @@ serve(async (req) => {
})
// To invoke:
// curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
// curl -i --location --request POST 'http://localhost:54321/functions/v1/send-email-smtp' \
// --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
// --header 'Content-Type: application/json' \
// --data '{"name":"Functions"}'
@@ -3,7 +3,7 @@
## Run locally
```bash
supabase functions serve --no-verify-jwt streams
supabase functions serve --no-verify-jwt
```
Use cURL or Postman to make a GET request to http://localhost:54321/functions/v1/streams.
@@ -1,4 +1,4 @@
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { serve } from 'std/server'
const msg = new TextEncoder().encode('data: hello\r\n\r\n')
@@ -9,7 +9,7 @@ Also check out our full Stripe Payments examples for [React Native (Expo)](https
## Test locally
- Terminal 1:
- `supabase functions serve --no-verify-jwt stripe-webhooks --env-file ./supabase/.env.local`
- `supabase functions serve --no-verify-jwt --env-file ./supabase/.env.local`
- Terminal 2:
- `stripe listen --forward-to localhost:54321/functions/v1/`
- Terminal 3 (optional):
@@ -2,20 +2,21 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.132.0/http/server.ts'
import { serve } from 'std/server'
// esm.sh is used to compile stripe-node to be compatible with ES modules.
import Stripe from 'https://esm.sh/stripe@10.13.0?target=deno&no-check&deno-std=0.132.0'
// Import via bare specifier thanks to the import_map.json file.
import Stripe from 'stripe'
const stripe = Stripe(Deno.env.get('STRIPE_API_KEY'), {
const stripe = new Stripe(Deno.env.get('STRIPE_API_KEY') as string, {
// This is needed to use the Fetch API rather than relying on the Node http
// package.
apiVersion: '2022-11-15',
httpClient: Stripe.createFetchHttpClient(),
})
// This is needed in order to use the Web Crypto API in Deno.
const cryptoProvider = Stripe.createSubtleCryptoProvider()
console.log(`Function "stripe-webhooks" up and running!`)
console.log('Hello from Stripe Webhook!')
serve(async (request) => {
const signature = request.headers.get('Stripe-Signature')
@@ -27,8 +28,8 @@ serve(async (request) => {
try {
receivedEvent = await stripe.webhooks.constructEventAsync(
body,
signature,
Deno.env.get('STRIPE_WEBHOOK_SIGNING_SECRET'),
signature!,
Deno.env.get('STRIPE_WEBHOOK_SIGNING_SECRET')!,
undefined,
cryptoProvider
)
@@ -36,25 +37,5 @@ serve(async (request) => {
return new Response(err.message, { status: 400 })
}
console.log(`🔔 Event received: ${receivedEvent.id}`)
// Secondly, we use this event to query the Stripe API in order to avoid
// handling any forged event. If available, we use the idempotency key.
const requestOptions =
receivedEvent.request && receivedEvent.request.idempotency_key
? {
idempotencyKey: receivedEvent.request.idempotency_key,
}
: {}
let retrievedEvent
try {
retrievedEvent = await stripe.events.retrieve(receivedEvent.id, requestOptions)
} catch (err) {
return new Response(err.message, { status: 400 })
}
return new Response(JSON.stringify({ id: retrievedEvent.id, status: 'ok' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
return new Response(JSON.stringify({ ok: true }), { status: 200 })
})
@@ -2,34 +2,28 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from "https://deno.land/std@0.131.0/http/server.ts"
import { serve } from 'std/server'
console.log(`Function "telegram-bot" up and running!`)
import { Bot, webhookCallback } from "https://deno.land/x/grammy@v1.8.3/mod.ts";
import { Bot, webhookCallback } from 'grammy'
const bot = new Bot(Deno.env.get('TELEGRAM_BOT_TOKEN') || '');
const bot = new Bot(Deno.env.get('TELEGRAM_BOT_TOKEN') || '')
bot.command("start", (ctx) => ctx.reply("Welcome! Up and running."));
bot.command('start', (ctx) => ctx.reply('Welcome! Up and running.'))
bot.command('ping', (ctx) => ctx.reply(`Pong! ${new Date()} ${Date.now()}`))
const handleUpdate = webhookCallback(bot, "std/http");
const handleUpdate = webhookCallback(bot, 'std/http')
serve(async (req) => {
try {
const url = new URL(req.url);
if (url.searchParams.get('secret') !== Deno.env.get('FUNCTION_SECRET'))
const url = new URL(req.url)
if (url.searchParams.get('secret') !== Deno.env.get('FUNCTION_SECRET'))
return new Response('not allowed', { status: 405 })
return await handleUpdate(req);
return await handleUpdate(req)
} catch (err) {
console.error(err);
console.error(err)
}
})
// To invoke:
// curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
// --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
// --header 'Content-Type: application/json' \
// --data '{"name":"Functions"}'
@@ -1,2 +0,0 @@
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
@@ -9,7 +9,7 @@ Create a Redis database using the [Upstash Console](https://console.upstash.com/
Select the `Global` type to minimize the latency from all edge locations. Copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your .env file. You'll find them under **Details > REST API > .env**.
```bash
cp supabase/functions/upstash-redis-counter/.env.example supabase/functions/upstash-redis-counter/.env
cp supabase/.env.local.example supabase/.env.local
```
## Run locally
@@ -18,7 +18,7 @@ Make sure you have the latest version of the [Supabase CLI installed](https://su
```bash
supabase start
supabase functions serve upstash-redis-counter --no-verify-jwt --env-file supabase/functions/upstash-redis-counter/.env
supabase functions serve --no-verify-jwt --env-file supabase/.env.local
```
Navigate to http://localhost:54321/functions/v1/upstash-redis-counter.
@@ -27,5 +27,5 @@ Navigate to http://localhost:54321/functions/v1/upstash-redis-counter.
```bash
supabase functions deploy upstash-redis-counter --no-verify-jwt
supabase secrets set --env-file supabase/functions/upstash-redis-counter/.env
supabase secrets set --env-file supabase/.env.local
```
@@ -2,8 +2,8 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'
import { Redis } from 'https://deno.land/x/upstash_redis@v1.19.3/mod.ts'
import { serve } from 'std/server'
import { Redis } from 'upstash_redis'
console.log(`Function "upstash-redis-counter" up and running!`)
+1 -2
View File
@@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
"name": "supabase",
"version": "0.0.0",
"license": "Apache-2.0",
"workspaces": [
@@ -46167,7 +46166,7 @@
"@sinclair/typebox": "^0.25.1",
"pg": "^8.7.1",
"pg-format": "^1.0.4",
"pgsql-parser": "13.4.0",
"pgsql-parser": "^13.3.0",
"postgres-array": "^3.0.1",
"prettier": "^2.6.0",
"prettier-plugin-sql": "^0.12.1"
+2 -2
View File
@@ -380,12 +380,12 @@ commands:
```
- id: supabase-functions-serve
title: supabase functions serve
summary: Serve a Function locally
summary: Serve Functions locally
tags: []
links: []
usage: |-
```sh
supabase functions serve <Function name> [flags]
supabase functions serve [flags]
```
subcommands: []
options: |-
+2 -2
View File
@@ -761,10 +761,10 @@ commands:
flags: []
- id: supabase-functions-serve
title: supabase functions serve
summary: Serve a Function locally
summary: Serve all functions locally
tags: []
links: []
usage: supabase functions serve <Function name> [flags]
usage: supabase functions serve [flags]
subcommands: []
flags:
- id: env-file
+1 -1
View File
@@ -239,7 +239,7 @@
},
{
"id": "supabase-functions-serve",
"title": "Serve a function",
"title": "Serve functions locally",
"slug": "supabase-functions-serve",
"type": "cli-command"
},
+3
View File
@@ -2258,6 +2258,9 @@ functions:
```
hideCodeBlock: true
isSpotlight: true
description: |
When using [reserved words](https://www.postgresql.org/docs/current/sql-keywords-appendix.html) for column names you need
to add double quotes e.g. `.gt('"order"', 2)`
- id: gte
title: gte()
$ref: '@supabase/postgrest-js.PostgrestFilterBuilder.gte'
@@ -1,10 +1,14 @@
import dayjs from 'dayjs'
import { observer } from 'mobx-react-lite'
import { FC, useEffect, useState } from 'react'
import { IconArrowRight } from 'ui'
import { Badge, Button, IconArrowRight, IconExternalLink } from 'ui'
import { useParams, useStore } from 'hooks'
import { TIME_PERIODS_INFRA, USAGE_APPROACHING_THRESHOLD } from 'lib/constants'
import {
PRICING_TIER_PRODUCT_IDS,
TIME_PERIODS_INFRA,
USAGE_APPROACHING_THRESHOLD,
} from 'lib/constants'
import { formatBytes } from 'lib/helpers'
import { NextPageWithLayout } from 'types'
@@ -71,6 +75,10 @@ const DatabaseUsage: FC<any> = () => {
const egressIsApproaching = databaseSizeUsageRatio >= USAGE_APPROACHING_THRESHOLD
const egressIsExceeded = databaseSizeUsageRatio >= 1
const subscriptionTier = ui.selectedProject?.subscription_tier
const isPaidTier = subscriptionTier !== PRICING_TIER_PRODUCT_IDS.FREE
return (
<>
<div>
@@ -94,6 +102,25 @@ const DatabaseUsage: FC<any> = () => {
labelTop={databaseSizeLimit > 0 ? formatBytes(databaseSizeLimit) : ''}
/>
</div>
<div className="flex justify-between items-center mt-3">
<div className="flex flex-row space-x-3 text-scale-1000 text-sm">
{usage?.disk_volume_size_gb && (
<span>Disk Size: {usage.disk_volume_size_gb} GB</span>
)}
{isPaidTier && <Badge>Auto-Scaling</Badge>}
</div>
<Button type="default" icon={<IconExternalLink size={14} strokeWidth={1.5} />}>
<a
target="_blank"
href="https://supabase.com/docs/guides/platform/database-usage"
>
What is disk size?
</a>
</Button>
</div>
</Panel.Content>
<Panel.Content>
<div className="space-y-1">