chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
---
|
||||
title: "Triggering tasks from Supabase Database Webhooks"
|
||||
sidebarTitle: "Database webhooks"
|
||||
description: "This guide shows you how to trigger a transcribing task when a row is added to a table in a Supabase database, using a Database Webhook and Edge Function."
|
||||
---
|
||||
|
||||
import Prerequisites from "/snippets/framework-prerequisites.mdx";
|
||||
import SupabasePrerequisites from "/snippets/supabase-prerequisites.mdx";
|
||||
import UsefulNextSteps from "/snippets/useful-next-steps.mdx";
|
||||
import TriggerTaskNextjs from "/snippets/trigger-tasks-nextjs.mdx";
|
||||
import NextjsTroubleshootingMissingApiKey from "/snippets/nextjs-missing-api-key.mdx";
|
||||
import NextjsTroubleshootingButtonSyntax from "/snippets/nextjs-button-syntax.mdx";
|
||||
import SupabaseDocsCards from "/snippets/supabase-docs-cards.mdx";
|
||||
import SupabaseAuthInfo from "/snippets/supabase-auth-info.mdx";
|
||||
|
||||
## Overview
|
||||
|
||||
Supabase and Trigger.dev can be used together to create powerful workflows triggered by real-time changes in your database tables:
|
||||
|
||||
- A Supabase Database Webhook triggers an Edge Function when a row including a video URL is inserted into a table
|
||||
- The Edge Function triggers a Trigger.dev task, passing the `video_url` column data from the new table row as the payload
|
||||
- The Trigger.dev task then:
|
||||
|
||||
- Uses [FFmpeg](https://www.ffmpeg.org/) to extract the audio track from a video URL
|
||||
- Uses [Deepgram](https://deepgram.com) to transcribe the extracted audio
|
||||
- Updates the original table row using the `record.id` in Supabase with the new transcription using `update`
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Ensure you have the [Supabase CLI](https://supabase.com/docs/guides/cli/getting-started) installed
|
||||
- Since Supabase CLI version 1.123.4, you must have [Docker Desktop installed](https://supabase.com/docs/guides/functions/deploy#deploy-your-edge-functions) to deploy Edge Functions
|
||||
- Ensure TypeScript is installed
|
||||
- [Create a Trigger.dev account](https://cloud.trigger.dev)
|
||||
- Create a new Trigger.dev project
|
||||
- [Create a new Deepgram account](https://deepgram.com/) and get your API key from the dashboard
|
||||
|
||||
## GitHub repo
|
||||
|
||||
<Card
|
||||
title="View the project on GitHub"
|
||||
icon="GitHub"
|
||||
href="https://github.com/triggerdotdev/examples/tree/main/supabase-edge-functions"
|
||||
>
|
||||
Click here to view the full code for this project in our examples repository on GitHub. You can
|
||||
fork it and use it as a starting point for your own project.
|
||||
</Card>
|
||||
|
||||
## Initial setup
|
||||
|
||||
<Steps>
|
||||
<SupabasePrerequisites />
|
||||
<Step title="Run the CLI `init` command">
|
||||
|
||||
The easiest way to get started is to use the CLI. It will add Trigger.dev to your existing project, create a `/trigger` folder and give you an example task.
|
||||
|
||||
Run this command in the root of your project to get started:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash npm
|
||||
npx trigger.dev@latest init
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm dlx trigger.dev@latest init
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn dlx trigger.dev@latest init
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
It will do a few things:
|
||||
|
||||
1. Log you into the CLI if you're not already logged in.
|
||||
2. Create a `trigger.config.ts` file in the root of your project.
|
||||
3. Ask where you'd like to create the `/trigger` directory.
|
||||
4. Create the `/trigger` directory with an example task, `/trigger/example.[ts/js]`.
|
||||
|
||||
Choose "None" when prompted to install an example task. We will create a new task for this guide.
|
||||
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
## Create a new table in your Supabase database
|
||||
|
||||
First, in the Supabase project dashboard, you'll need to create a new table to store the video URL and transcription.
|
||||
|
||||
To do this, click on 'Table Editor' <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" /> in the left-hand menu and create a new table. <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" />
|
||||
|
||||

|
||||
|
||||
Call your table `video_transcriptions`. <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" />
|
||||
|
||||
Add two new columns, one called `video_url` with the type `text` <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" />, and another called `transcription`, also with the type `text` <Icon icon="circle-3" iconType="solid" size={20} color="A8FF53" />.
|
||||
|
||||

|
||||
|
||||
## Create and deploy the Trigger.dev task
|
||||
|
||||
### Generate the Database type definitions
|
||||
|
||||
To allow you to use TypeScript to interact with your table, you need to [generate the type definitions](https://supabase.com/docs/guides/api/rest/generating-types) for your Supabase table using the Supabase CLI.
|
||||
|
||||
```bash
|
||||
supabase gen types --lang=typescript --project-id <project-ref> --schema public > database.types.ts
|
||||
```
|
||||
|
||||
<Note> Replace `<project-ref>` with your Supabase project reference ID. This can be found in your Supabase project settings under 'General'. </Note>
|
||||
|
||||
### Create the transcription task
|
||||
|
||||
Create a new task file in your `/trigger` folder. Call it `videoProcessAndUpdate.ts`.
|
||||
|
||||
This task takes a video from a public video url, extracts the audio using FFmpeg and transcribes the audio using Deepgram. The transcription summary will then be updated back to the original row in the `video_transcriptions` table in Supabase.
|
||||
|
||||
You will need to install some additional dependencies for this task:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash npm
|
||||
npm install @deepgram/sdk @supabase/supabase-js fluent-ffmpeg
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm install @deepgram/sdk @supabase/supabase-js fluent-ffmpeg
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn install @deepgram/sdk @supabase/supabase-js fluent-ffmpeg
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
These dependencies will allow you to interact with the Deepgram and Supabase APIs and extract audio from a video using FFmpeg.
|
||||
|
||||
<Warning>
|
||||
When updating your tables from a Trigger.dev task which has been triggered by a database change,
|
||||
be extremely careful to not cause an infinite loop. Ensure you have the correct conditions in
|
||||
place to prevent this.
|
||||
</Warning>
|
||||
|
||||
```ts /trigger/videoProcessAndUpdate.ts
|
||||
// Install any missing dependencies below
|
||||
import { createClient as createDeepgramClient } from "@deepgram/sdk";
|
||||
import { createClient as createSupabaseClient } from "@supabase/supabase-js";
|
||||
import { logger, task } from "@trigger.dev/sdk";
|
||||
import ffmpeg from "fluent-ffmpeg";
|
||||
import fs from "fs";
|
||||
import { Readable } from "node:stream";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { Database } from "../../database.types";
|
||||
|
||||
// Create a single Supabase client for interacting with your database
|
||||
// 'Database' supplies the type definitions to supabase-js
|
||||
const supabase = createSupabaseClient<Database>(
|
||||
// These details can be found in your Supabase project settings under `API`
|
||||
process.env.SUPABASE_PROJECT_URL as string, // e.g. https://abc123.supabase.co - replace 'abc123' with your project ID
|
||||
process.env.SUPABASE_SERVICE_ROLE_KEY as string // Your service role secret key
|
||||
);
|
||||
|
||||
// Your DEEPGRAM_SECRET_KEY can be found in your Deepgram dashboard
|
||||
const deepgram = createDeepgramClient(process.env.DEEPGRAM_SECRET_KEY);
|
||||
|
||||
export const videoProcessAndUpdate = task({
|
||||
id: "video-process-and-update",
|
||||
run: async (payload: { videoUrl: string; id: number }) => {
|
||||
const { videoUrl, id } = payload;
|
||||
|
||||
logger.log(`Processing video at URL: ${videoUrl}`);
|
||||
|
||||
// Generate temporary file names
|
||||
const tempDirectory = os.tmpdir();
|
||||
const outputPath = path.join(tempDirectory, `audio_${Date.now()}.wav`);
|
||||
|
||||
const response = await fetch(videoUrl);
|
||||
|
||||
// Extract the audio using FFmpeg
|
||||
await new Promise((resolve, reject) => {
|
||||
if (!response.body) {
|
||||
return reject(new Error("Failed to fetch video"));
|
||||
}
|
||||
|
||||
ffmpeg(Readable.from(response.body))
|
||||
.outputOptions([
|
||||
"-vn", // Disable video output
|
||||
"-acodec pcm_s16le", // Use PCM 16-bit little-endian encoding
|
||||
"-ar 44100", // Set audio sample rate to 44.1 kHz
|
||||
"-ac 2", // Set audio channels to stereo
|
||||
])
|
||||
.output(outputPath)
|
||||
.on("end", resolve)
|
||||
.on("error", reject)
|
||||
.run();
|
||||
});
|
||||
|
||||
logger.log(`Audio extracted from video`, { outputPath });
|
||||
|
||||
// Transcribe the audio using Deepgram
|
||||
const { result, error } = await deepgram.listen.prerecorded.transcribeFile(
|
||||
fs.readFileSync(outputPath),
|
||||
{
|
||||
model: "nova-2", // Use the Nova 2 model
|
||||
smart_format: true, // Automatically format the transcription
|
||||
diarize: true, // Enable speaker diarization
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const transcription = result.results.channels[0].alternatives[0].paragraphs?.transcript;
|
||||
|
||||
logger.log(`Transcription: ${transcription}`);
|
||||
|
||||
// Delete the temporary audio file
|
||||
fs.unlinkSync(outputPath);
|
||||
logger.log(`Temporary audio file deleted`, { outputPath });
|
||||
|
||||
const { error: updateError } = await supabase
|
||||
.from("video_transcriptions")
|
||||
// Update the transcription column
|
||||
.update({ transcription: transcription })
|
||||
// Find the row by its ID
|
||||
.eq("id", id);
|
||||
|
||||
if (updateError) {
|
||||
throw new Error(`Failed to update transcription: ${updateError.message}`);
|
||||
}
|
||||
|
||||
return {
|
||||
message: `Summary of the audio: ${transcription}`,
|
||||
result,
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Warning>
|
||||
This task uses your service role secret key to bypass Row Level Security. This is not recommended
|
||||
for production use as it has unlimited access and bypasses all security checks.
|
||||
</Warning>
|
||||
|
||||
<SupabaseAuthInfo />
|
||||
|
||||
### Adding the FFmpeg build extension
|
||||
|
||||
Before you can deploy the task, you'll need to add the FFmpeg build extension to your `trigger.config.ts` file.
|
||||
|
||||
```ts trigger.config.ts
|
||||
// Add this import
|
||||
import { ffmpeg } from "@trigger.dev/build/extensions/core";
|
||||
import { defineConfig } from "@trigger.dev/sdk";
|
||||
|
||||
export default defineConfig({
|
||||
project: "<project ref>", // Replace with your project ref
|
||||
// Your other config settings...
|
||||
build: {
|
||||
// Add the FFmpeg build extension
|
||||
extensions: [ffmpeg()],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
[Build extensions](/config/extensions/overview) allow you to hook into the build system and
|
||||
customize the build process or the resulting bundle and container image (in the case of
|
||||
deploying). You can use pre-built extensions or create your own.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
You'll also need to add `@trigger.dev/build` to your `package.json` file under `devDependencies`
|
||||
if you don't already have it there.
|
||||
</Note>
|
||||
|
||||
If you are modifying this example and using popular FFmpeg libraries like `fluent-ffmpeg` you'll also need to add them to [`external`](/config/config-file#external) in your `trigger.config.ts` file.
|
||||
|
||||
### Add your Deepgram and Supabase environment variables to your Trigger.dev project
|
||||
|
||||
You will need to add your `DEEPGRAM_SECRET_KEY`, `SUPABASE_PROJECT_URL` and `SUPABASE_SERVICE_ROLE_KEY` as environment variables in your Trigger.dev project. This can be done in the 'Environment Variables' page in your project dashboard.
|
||||
|
||||

|
||||
|
||||
### Deploying your task
|
||||
|
||||
Now you can now deploy your task using the following command:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash npm
|
||||
npx trigger.dev@latest deploy
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm dlx trigger.dev@latest deploy
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn dlx trigger.dev@latest deploy
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Create and deploy the Supabase Edge Function
|
||||
|
||||
### Add your Trigger.dev prod secret key to the Supabase dashboard
|
||||
|
||||
Go to your Trigger.dev [project dashboard](https://cloud.trigger.dev) and copy the `prod` secret key from the API keys page.
|
||||
|
||||

|
||||
|
||||
Then, in [Supabase](https://supabase.com/dashboard/projects), select the project you want to use, navigate to 'Project settings' <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" />, click 'Edge Functions' <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" /> in the configurations menu, and then click the 'Add new secret' <Icon icon="circle-3" iconType="solid" size={20} color="A8FF53" /> button.
|
||||
|
||||
Add `TRIGGER_SECRET_KEY` <Icon icon="circle-4" iconType="solid" size={20} color="A8FF53" /> with the pasted value of your Trigger.dev `prod` secret key.
|
||||
|
||||

|
||||
|
||||
### Create a new Edge Function using the Supabase CLI
|
||||
|
||||
Now create an Edge Function using the Supabase CLI. Call it `video-processing-handler`. This function will be triggered by the Database Webhook.
|
||||
|
||||
```bash
|
||||
supabase functions new video-processing-handler
|
||||
```
|
||||
|
||||
```ts functions/video-processing-handler/index.ts
|
||||
// Setup type definitions for built-in Supabase Runtime APIs
|
||||
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
||||
import { tasks } from "npm:@trigger.dev/sdk@latest";
|
||||
// Import the videoProcessAndUpdate task from the trigger folder
|
||||
import type { videoProcessAndUpdate } from "../../../src/trigger/videoProcessAndUpdate.ts";
|
||||
// 👆 type only import
|
||||
|
||||
// Sets up a Deno server that listens for incoming JSON requests
|
||||
Deno.serve(async (req) => {
|
||||
const payload = await req.json();
|
||||
|
||||
// This payload will contain the video url and id from the new row in the table
|
||||
const videoUrl = payload.record.video_url;
|
||||
const id = payload.record.id;
|
||||
|
||||
// Trigger the videoProcessAndUpdate task with the videoUrl payload
|
||||
await tasks.trigger<typeof videoProcessAndUpdate>("video-process-and-update", { videoUrl, id });
|
||||
console.log(payload ?? "No name provided");
|
||||
|
||||
return new Response("ok");
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
Tasks in the `trigger` folder use Node, so they must stay in there or they will not run,
|
||||
especially if you are using a different runtime like Deno. Also do not add "`npm:`" to imports
|
||||
inside your task files, for the same reason.
|
||||
</Note>
|
||||
|
||||
### Deploy the Edge Function
|
||||
|
||||
Now deploy your new Edge Function with the following command:
|
||||
|
||||
```bash
|
||||
supabase functions deploy video-processing-handler
|
||||
```
|
||||
|
||||
Follow the CLI instructions, selecting the same project you added your `prod` secret key to, and once complete you should see your new Edge Function deployment in your Supabase Edge Functions dashboard.
|
||||
|
||||
There will be a link to the dashboard in your terminal output.
|
||||
|
||||
## Create the Database Webhook
|
||||
|
||||
In your Supabase project dashboard, click 'Project settings' <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" />, then the 'API' tab <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" />, and copy the `anon` `public` API key from the table <Icon icon="circle-3" iconType="solid" size={20} color="A8FF53" />.
|
||||
|
||||

|
||||
|
||||
Then, go to 'Database' <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" /> click on 'Webhooks' <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" />, and then click 'Create a new hook' <Icon icon="circle-3" iconType="solid" size={20} color="A8FF53" />.
|
||||
|
||||

|
||||
|
||||
<Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" /> Call the hook `edge-function-hook`.
|
||||
|
||||
<Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" /> Select the new table you have created:
|
||||
`public` `video_transcriptions`.
|
||||
|
||||
<Icon icon="circle-3" iconType="solid" size={20} color="A8FF53" /> Choose the `insert` event.
|
||||
|
||||

|
||||
|
||||
<Icon icon="circle-4" iconType="solid" size={20} color="A8FF53" /> Under 'Webhook configuration', select
|
||||
'Supabase Edge Functions'{" "}
|
||||
|
||||
<Icon icon="circle-5" iconType="solid" size={20} color="A8FF53" /> Under 'Edge Function', choose `POST`
|
||||
and select the Edge Function you have created: `video-processing-handler`.{" "}
|
||||
|
||||
<Icon icon="circle-6" iconType="solid" size={20} color="A8FF53" /> Under 'HTTP Headers', add a new header with the key `Authorization` and the value `Bearer <your-api-key>` (replace `<your-api-key>` with the `anon` `public` API key you copied earlier).
|
||||
|
||||
<Info>
|
||||
Supabase Edge Functions require a JSON Web Token [JWT](https://supabase.com/docs/guides/auth/jwts)
|
||||
in the authorization header. This is to ensure that only authorized users can access your edge
|
||||
functions.
|
||||
</Info>
|
||||
|
||||
<Icon icon="circle-7" iconType="solid" size={20} color="A8FF53" /> Click 'Create webhook'.{" "}
|
||||
|
||||

|
||||
|
||||
Your Database Webhook is now ready to use.
|
||||
|
||||
## Triggering the entire workflow
|
||||
|
||||
Your `video-processing-handler` Edge Function is now set up to trigger the `videoProcessAndUpdate` task every time a new row is inserted into your `video_transcriptions` table.
|
||||
|
||||
To do this, go back to your Supabase project dashboard, click on 'Table Editor' <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" /> in the left-hand menu, click on the `video_transcriptions` table <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" /> , and then click 'Insert', 'Insert Row' <Icon icon="circle-3" iconType="solid" size={20} color="A8FF53" />.
|
||||
|
||||

|
||||
|
||||
Add a new item under `video_url`, with a public video url. <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" />.
|
||||
|
||||
You can use the following public video URL for testing: `https://content.trigger.dev/Supabase%20Edge%20Functions%20Quickstart.mp4`.
|
||||
|
||||

|
||||
|
||||
Once the new table row has been inserted, check your [cloud.trigger.dev](https://cloud.trigger.dev) project 'Runs' list <Icon icon="circle-1" iconType="solid" size={20} color="A8FF53" /> and you should see a processing `videoProcessAndUpdate` task <Icon icon="circle-2" iconType="solid" size={20} color="A8FF53" /> which has been triggered when you added a new row with the video url to your `video_transcriptions` table.
|
||||
|
||||

|
||||
|
||||
Once the run has completed successfully, go back to your Supabase `video_transcriptions` table, and you should see that in the row containing the original video URL, the transcription has now been added to the `transcription` column.
|
||||
|
||||

|
||||
|
||||
**Congratulations! You have completed the full workflow from Supabase to Trigger.dev and back again.**
|
||||
|
||||
<SupabaseDocsCards />
|
||||
Reference in New Issue
Block a user