mirror of
https://github.com/feeddeck/feeddeck.git
synced 2026-04-29 19:11:45 -05:00
Simplify Self Hosting of Edge Functions (#253)
Until now we had a `find` command to delete existing edge functions before copying them for the self hosting set up. This command is now removed, because we also moved the `main` function to the existing functions directory and just not push it as function to Supabase.
This commit is contained in:
99
supabase/functions/main/index.ts
Normal file
99
supabase/functions/main/index.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { serve } from "https://deno.land/std@0.131.0/http/server.ts";
|
||||||
|
import * as jose from "https://deno.land/x/jose@v4.14.4/index.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `main` function is only used for the self-hosted FeedDeck setup, so that
|
||||||
|
* the edge functions are working. For FeedDeck hosted on the Supabase Cloud
|
||||||
|
* offering the function is ignored and not deployed.
|
||||||
|
*/
|
||||||
|
console.log("main function started");
|
||||||
|
|
||||||
|
const JWT_SECRET = Deno.env.get("JWT_SECRET");
|
||||||
|
const VERIFY_JWT = Deno.env.get("VERIFY_JWT") === "true";
|
||||||
|
|
||||||
|
function getAuthToken(req: Request) {
|
||||||
|
const authHeader = req.headers.get("authorization");
|
||||||
|
if (!authHeader) {
|
||||||
|
throw new Error("Missing authorization header");
|
||||||
|
}
|
||||||
|
const [bearer, token] = authHeader.split(" ");
|
||||||
|
if (bearer !== "Bearer") {
|
||||||
|
throw new Error(`Auth header is not 'Bearer {token}'`);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyJWT(jwt: string): Promise<boolean> {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const secretKey = encoder.encode(JWT_SECRET);
|
||||||
|
try {
|
||||||
|
await jose.jwtVerify(jwt, secretKey);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
serve(async (req: Request) => {
|
||||||
|
if (req.method !== "OPTIONS" && VERIFY_JWT) {
|
||||||
|
try {
|
||||||
|
const token = getAuthToken(req);
|
||||||
|
const isValidJWT = await verifyJWT(token);
|
||||||
|
|
||||||
|
if (!isValidJWT) {
|
||||||
|
return new Response(JSON.stringify({ msg: "Invalid JWT" }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return new Response(JSON.stringify({ msg: e.toString() }), {
|
||||||
|
status: 401,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(req.url);
|
||||||
|
const { pathname } = url;
|
||||||
|
const path_parts = pathname.split("/");
|
||||||
|
const service_name = path_parts[1];
|
||||||
|
|
||||||
|
if (!service_name || service_name === "") {
|
||||||
|
const error = { msg: "missing function name in request" };
|
||||||
|
return new Response(JSON.stringify(error), {
|
||||||
|
status: 400,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const servicePath = `/home/deno/functions/${service_name}`;
|
||||||
|
console.error(`serving the request with ${servicePath}`);
|
||||||
|
|
||||||
|
const memoryLimitMb = 150;
|
||||||
|
const workerTimeoutMs = 1 * 60 * 1000;
|
||||||
|
const noModuleCache = false;
|
||||||
|
const importMapPath = null;
|
||||||
|
const envVarsObj = Deno.env.toObject();
|
||||||
|
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const worker = await EdgeRuntime.userWorkers.create({
|
||||||
|
servicePath,
|
||||||
|
memoryLimitMb,
|
||||||
|
workerTimeoutMs,
|
||||||
|
noModuleCache,
|
||||||
|
importMapPath,
|
||||||
|
envVars,
|
||||||
|
});
|
||||||
|
return await worker.fetch(req);
|
||||||
|
} catch (e) {
|
||||||
|
const error = { msg: e.toString() };
|
||||||
|
return new Response(JSON.stringify(error), {
|
||||||
|
status: 500,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
3
supabase/hosting/.gitignore
vendored
3
supabase/hosting/.gitignore
vendored
@@ -1,6 +1,5 @@
|
|||||||
!volumes/db/_supabase.sql
|
!volumes/db/_supabase.sql
|
||||||
volumes/db/data
|
volumes/db/data
|
||||||
volumes/functions/*
|
volumes/functions
|
||||||
!volumes/functions/main
|
|
||||||
volumes/storage
|
volumes/storage
|
||||||
.env
|
.env
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ be found in the FeedDeck contributing guide and the Supabase documentation.
|
|||||||
2. Copy all edge functions into the correct directory:
|
2. Copy all edge functions into the correct directory:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
find ./volumes/functions -maxdepth 1 ! -name main -exec rm -rf {} +
|
cp -r ../functions/. volumes/functions/
|
||||||
cp -a ../functions/. volumes/functions/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Create a `.env` file and adjust the environment variables as needed:
|
3. Create a `.env` file and adjust the environment variables as needed:
|
||||||
@@ -59,4 +58,5 @@ be found in the FeedDeck contributing guide and the Supabase documentation.
|
|||||||
docker compose down -v
|
docker compose down -v
|
||||||
rm -rf volumes/db/data/
|
rm -rf volumes/db/data/
|
||||||
rm -rf volumes/storage/
|
rm -rf volumes/storage/
|
||||||
|
rm -rf volumes/functions/
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
|
||||||
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
|
|
||||||
|
|
||||||
console.log('main function started')
|
|
||||||
|
|
||||||
const JWT_SECRET = Deno.env.get('JWT_SECRET')
|
|
||||||
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
|
|
||||||
|
|
||||||
function getAuthToken(req: Request) {
|
|
||||||
const authHeader = req.headers.get('authorization')
|
|
||||||
if (!authHeader) {
|
|
||||||
throw new Error('Missing authorization header')
|
|
||||||
}
|
|
||||||
const [bearer, token] = authHeader.split(' ')
|
|
||||||
if (bearer !== 'Bearer') {
|
|
||||||
throw new Error(`Auth header is not 'Bearer {token}'`)
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
|
|
||||||
async function verifyJWT(jwt: string): Promise<boolean> {
|
|
||||||
const encoder = new TextEncoder()
|
|
||||||
const secretKey = encoder.encode(JWT_SECRET)
|
|
||||||
try {
|
|
||||||
await jose.jwtVerify(jwt, secretKey)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
serve(async (req: Request) => {
|
|
||||||
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
|
|
||||||
try {
|
|
||||||
const token = getAuthToken(req)
|
|
||||||
const isValidJWT = await verifyJWT(token)
|
|
||||||
|
|
||||||
if (!isValidJWT) {
|
|
||||||
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
|
|
||||||
status: 401,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
return new Response(JSON.stringify({ msg: e.toString() }), {
|
|
||||||
status: 401,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(req.url)
|
|
||||||
const { pathname } = url
|
|
||||||
const path_parts = pathname.split('/')
|
|
||||||
const service_name = path_parts[1]
|
|
||||||
|
|
||||||
if (!service_name || service_name === '') {
|
|
||||||
const error = { msg: 'missing function name in request' }
|
|
||||||
return new Response(JSON.stringify(error), {
|
|
||||||
status: 400,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const servicePath = `/home/deno/functions/${service_name}`
|
|
||||||
console.error(`serving the request with ${servicePath}`)
|
|
||||||
|
|
||||||
const memoryLimitMb = 150
|
|
||||||
const workerTimeoutMs = 1 * 60 * 1000
|
|
||||||
const noModuleCache = false
|
|
||||||
const importMapPath = null
|
|
||||||
const envVarsObj = Deno.env.toObject()
|
|
||||||
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
|
|
||||||
|
|
||||||
try {
|
|
||||||
const worker = await EdgeRuntime.userWorkers.create({
|
|
||||||
servicePath,
|
|
||||||
memoryLimitMb,
|
|
||||||
workerTimeoutMs,
|
|
||||||
noModuleCache,
|
|
||||||
importMapPath,
|
|
||||||
envVars,
|
|
||||||
})
|
|
||||||
return await worker.fetch(req)
|
|
||||||
} catch (e) {
|
|
||||||
const error = { msg: e.toString() }
|
|
||||||
return new Response(JSON.stringify(error), {
|
|
||||||
status: 500,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user