Add Supabase CA certificate, update TypeScript configuration, and implement calendar synchronization task
This commit is contained in:
parent
8d7252832f
commit
75e72c1207
15 changed files with 127 additions and 51 deletions
15
api/.env.production
Normal file
15
api/.env.production
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
SUPABASE_URL=https://mhcafqvzbrrwvahpvvzd.supabase.co
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1oY2FmcXZ6YnJyd3ZhaHB2dnpkIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MTI0MTMyMSwiZXhwIjoyMDU2ODE3MzIxfQ.9r33CUsu6ZR4vyv4ed-UY6cLE1FZzSSxTNE8pFUKjN4
|
||||
|
||||
STREAM_CHAT_API_KEY=v4yf8rs94aa8
|
||||
STREAM_CHAT_API_SECRET=jq2szvv73ua7sz9tvr9y24dxg37sw8ue8t576fu7ggr4h6wvcmunby4gvte8tm8f
|
||||
|
||||
EMAIL_USER="baptiste@xtablo.com"
|
||||
EMAIL_KEY="jayf pzpj nrsv vtim"
|
||||
|
||||
XTABLO_URL=https://app.xtablo.com
|
||||
CORS_ORIGIN="https://app.xtablo.com"
|
||||
|
||||
R2_ACCOUNT_ID="9715fa14c5e5d1612301572cf1c6bbee"
|
||||
R2_ACCESS_KEY_ID="caeb987bbcd601708a93c6aa562064ef"
|
||||
R2_SECRET_ACCESS_KEY="42e455b25804687f7cff3d15be23c1f0f47ca742d7a41b6fa1a05a91041e0215"
|
||||
|
|
@ -8,4 +8,8 @@ EMAIL_USER="baptiste@xtablo.com"
|
|||
EMAIL_KEY="jayf pzpj nrsv vtim"
|
||||
|
||||
XTABLO_URL="https://app-staging.xtablo.com"
|
||||
CORS_ORIGIN="https://app-staging.xtablo.com"
|
||||
CORS_ORIGIN="https://app-staging.xtablo.com"
|
||||
|
||||
R2_ACCOUNT_ID="9715fa14c5e5d1612301572cf1c6bbee"
|
||||
R2_ACCESS_KEY_ID="caeb987bbcd601708a93c6aa562064ef"
|
||||
R2_SECRET_ACCESS_KEY="42e455b25804687f7cff3d15be23c1f0f47ca742d7a41b6fa1a05a91041e0215"
|
||||
3
api/.gitignore
vendored
3
api/.gitignore
vendored
|
|
@ -16,8 +16,7 @@ node_modules/
|
|||
.wrangler
|
||||
|
||||
# env
|
||||
.env
|
||||
.env.production
|
||||
.env.development
|
||||
.dev.vars
|
||||
|
||||
# logs
|
||||
|
|
|
|||
1
api/src/crontab
Normal file
1
api/src/crontab
Normal file
|
|
@ -0,0 +1 @@
|
|||
*/5 * * * * syncCalendars
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import type { EventAndTablo } from "./types.js";
|
||||
import type { EventAndTablo } from "@/types.ts";
|
||||
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||
import type { SupabaseClient } from "@supabase/supabase-js";
|
||||
|
||||
export const generateICSFromEvents = (
|
||||
events: EventAndTablo[],
|
||||
|
|
@ -67,3 +69,40 @@ export const generateICSFromEvents = (
|
|||
icsContent += "\r\n" + "END:VCALENDAR";
|
||||
return icsContent;
|
||||
};
|
||||
|
||||
export const writeCalendarFileToR2 = async (
|
||||
s3_client: S3Client,
|
||||
supabase: SupabaseClient,
|
||||
inputs: {
|
||||
token: string;
|
||||
tabloName: string;
|
||||
tablo_id: string;
|
||||
}
|
||||
) => {
|
||||
const { token, tabloName, tablo_id } = inputs;
|
||||
|
||||
const bucketName = "calendars";
|
||||
|
||||
const key = `${token}/${tabloName}.ics`;
|
||||
|
||||
const { data: events, error: eventsError } = await supabase
|
||||
.from("events_and_tablos")
|
||||
.select("*")
|
||||
.eq("tablo_id", tablo_id);
|
||||
|
||||
if (eventsError || !events) {
|
||||
throw new Error("Failed to generate events");
|
||||
}
|
||||
|
||||
const eventsData = events as EventAndTablo[];
|
||||
|
||||
const icsContent = generateICSFromEvents(eventsData, tabloName);
|
||||
|
||||
await s3_client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: key,
|
||||
Body: icsContent,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { Hono } from "hono";
|
||||
import { serve } from "@hono/node-server";
|
||||
import { logger } from "hono/logger";
|
||||
import { mainRouter } from "./routers.js";
|
||||
import { mainRouter } from "@/routers.ts";
|
||||
|
||||
import { cors } from "hono/cors";
|
||||
import { config } from "./config.js";
|
||||
import { config } from "@/config.ts";
|
||||
import { run } from "graphile-worker";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
|
@ -39,7 +39,7 @@ app.route("/api/v1", mainRouter);
|
|||
const worker = async () => {
|
||||
const connectionString = `${
|
||||
config.SUPABASE_CONNECTION_STRING
|
||||
}?ssl=true&sslrootcert=${path.resolve(__dirname, "supabase_ca.crt")}`;
|
||||
}?ssl=true&sslrootcert=${path.resolve(__dirname, "..", "supabase_ca.crt")}`;
|
||||
|
||||
const runner = await run({
|
||||
connectionString,
|
||||
|
|
@ -47,6 +47,7 @@ const worker = async () => {
|
|||
pollInterval: 1000,
|
||||
taskDirectory: path.resolve(__dirname, "tasks"),
|
||||
noPreparedStatements: true,
|
||||
crontabFile: path.resolve(__dirname, "crontab"),
|
||||
});
|
||||
|
||||
await runner.promise;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { createClient, type User } from "@supabase/supabase-js";
|
|||
import type { Context, Next } from "hono";
|
||||
import nodemailer from "nodemailer";
|
||||
import { StreamChat } from "stream-chat";
|
||||
import { config } from "./config.js";
|
||||
import { config } from "@/config.ts";
|
||||
|
||||
// Create authentication middleware
|
||||
export const authMiddleware = async (c: Context, next: Next) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Hono } from "hono";
|
||||
import { userRouter } from "./user.js";
|
||||
import { supabaseMiddleware } from "./middleware.js";
|
||||
import { tabloRouter } from "./tablo.js";
|
||||
import { userRouter } from "@/user.ts";
|
||||
import { supabaseMiddleware } from "@/middleware.ts";
|
||||
import { tabloRouter } from "@/tablo.ts";
|
||||
|
||||
export const mainRouter = new Hono<{
|
||||
Bindings: {
|
||||
|
|
|
|||
|
|
@ -4,24 +4,24 @@ import {
|
|||
emailMiddleware,
|
||||
r2Middleware,
|
||||
streamChatMiddleware,
|
||||
} from "./middleware.js";
|
||||
} from "@/middleware.ts";
|
||||
import {
|
||||
PostgrestError,
|
||||
type SupabaseClient,
|
||||
type User,
|
||||
} from "@supabase/supabase-js";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import { generateToken } from "./token.js";
|
||||
import { config } from "./config.js";
|
||||
import type { Tables } from "./database.types.js";
|
||||
import { generateToken } from "@/token.ts";
|
||||
import { config } from "@/config.ts";
|
||||
import type { Tables } from "@/database.types.ts";
|
||||
import type { StreamChat } from "stream-chat";
|
||||
import type {
|
||||
TabloInsert,
|
||||
EventInsertInTablo,
|
||||
EventAndTablo,
|
||||
} from "./types.js";
|
||||
} from "@/types.ts";
|
||||
import { PutObjectCommand, type S3Client } from "@aws-sdk/client-s3";
|
||||
import { generateICSFromEvents } from "./helpers.js";
|
||||
import { generateICSFromEvents, writeCalendarFileToR2 } from "@/helpers.ts";
|
||||
|
||||
export const tabloRouter = new Hono<{
|
||||
Variables: {
|
||||
|
|
@ -399,31 +399,18 @@ tabloRouter.post("/webcal/generate-url", async (c) => {
|
|||
if (error) {
|
||||
return c.json({ error: "Failed to generate token" }, 500);
|
||||
}
|
||||
const bucketName = "calendars";
|
||||
|
||||
const key = `${token}/${tabloName}.ics`;
|
||||
|
||||
const { data: events, error: eventsError } = await supabase
|
||||
.from("events_and_tablos")
|
||||
.select("*")
|
||||
.eq("tablo_id", tablo_id);
|
||||
|
||||
if (eventsError || !events) {
|
||||
return c.json({ error: "Failed to generate events" }, 500);
|
||||
try {
|
||||
await writeCalendarFileToR2(s3_client, supabase, {
|
||||
token,
|
||||
tabloName,
|
||||
tablo_id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("error writing calendar file to R2", error);
|
||||
return c.json({ error: "Failed to write calendar file to R2" }, 500);
|
||||
}
|
||||
|
||||
const eventsData = events as EventAndTablo[];
|
||||
|
||||
const icsContent = generateICSFromEvents(eventsData, tabloName);
|
||||
|
||||
await s3_client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: key,
|
||||
Body: icsContent,
|
||||
})
|
||||
);
|
||||
|
||||
// Return the webcal URL
|
||||
// const webcalUrl = `webcal://${
|
||||
// c.req.header("host") || "localhost:3000"
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import type { JobHelpers } from "graphile-worker";
|
||||
|
||||
export const helloWorld = async (
|
||||
payload: unknown,
|
||||
helpers: JobHelpers
|
||||
): Promise<void> => {
|
||||
helpers.logger.info("Hello World");
|
||||
};
|
||||
34
api/src/tasks/syncCalendars.cjs
Normal file
34
api/src/tasks/syncCalendars.cjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
const { createClient } = require("@supabase/supabase-js");
|
||||
const { S3Client } = require("@aws-sdk/client-s3");
|
||||
const { config } = require("../config");
|
||||
const { writeCalendarFileToR2 } = require("../helpers");
|
||||
|
||||
module.exports = async (payload, helpers) => {
|
||||
const supabase = createClient(
|
||||
config.SUPABASE_URL,
|
||||
config.SUPABASE_SERVICE_ROLE_KEY
|
||||
);
|
||||
const s3 = new S3Client({
|
||||
region: "auto",
|
||||
endpoint: `https://${config.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
accessKeyId: config.R2_ACCESS_KEY_ID,
|
||||
secretAccessKey: config.R2_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
const { data, error } = await supabase.from("calendar_subscriptions").select("token, tablo_id, tablos(name)");
|
||||
if (error) {
|
||||
helpers.logger.error(error);
|
||||
}
|
||||
|
||||
data.forEach(async (subscription) => {
|
||||
const tabloName = subscription.tablos.name.replace(/ /g, "_");
|
||||
await writeCalendarFileToR2(s3, supabase, {
|
||||
tabloName,
|
||||
token: subscription.token,
|
||||
tablo_id: subscription.tablo_id,
|
||||
});
|
||||
});
|
||||
|
||||
helpers.logger.info("Synced calendars");
|
||||
};
|
||||
|
|
@ -3,7 +3,7 @@ import type {
|
|||
Tables,
|
||||
TablesInsert,
|
||||
TablesUpdate,
|
||||
} from "./database.types.js";
|
||||
} from "@/database.types.ts";
|
||||
|
||||
export type Tablo = Database["public"]["Tables"]["tablos"];
|
||||
export type TabloInsert = Tablo["Insert"];
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Hono } from "hono";
|
||||
import { authMiddleware, streamChatMiddleware } from "./middleware.js";
|
||||
import { authMiddleware, streamChatMiddleware } from "@/middleware.ts";
|
||||
import type { SupabaseClient, User } from "@supabase/supabase-js";
|
||||
import { StreamChat } from "stream-chat";
|
||||
import type { Transporter } from "nodemailer";
|
||||
import type { Tables } from "./database.types.js";
|
||||
import type { Tables } from "@/database.types.ts";
|
||||
|
||||
export const userRouter = new Hono<{
|
||||
Variables: {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@
|
|||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"allowImportingTsExtensions": true
|
||||
"allowImportingTsExtensions": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue