Add ACMCC app source, Supabase backend, and project config

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 20:19:26 -04:00
parent 313b51b412
commit 183fe0a93c
1422 changed files with 259271 additions and 0 deletions
@@ -0,0 +1,180 @@
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version",
};
const STAFF_ROLES = ["admin", "manager"] as const;
function jsonResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), { status, headers: { ...corsHeaders, "Content-Type": "application/json" } });
}
function quote(value: string) {
return `"${String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
}
async function getAuthorizedClient(req: Request) {
const authHeader = req.headers.get("authorization") || "";
if (!authHeader.startsWith("Bearer ")) return { error: jsonResponse({ success: false, error: "Unauthorized" }, 401) };
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const anonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
const client = createClient(supabaseUrl, anonKey, { global: { headers: { Authorization: authHeader } } });
const token = authHeader.replace("Bearer ", "");
const { data: claimsData, error } = await client.auth.getClaims(token);
const callerId = claimsData?.claims?.sub;
if (error || !callerId) return { error: jsonResponse({ success: false, error: "Unauthorized" }, 401) };
for (const role of STAFF_ROLES) {
const { data: hasRole } = await client.rpc("has_role", { _user_id: callerId, _role: role });
if (hasRole) return { client, callerId };
}
return { error: jsonResponse({ success: false, error: "Insufficient permissions" }, 403) };
}
async function readUntil(reader: ReadableStreamDefaultReader<Uint8Array>, tag: string, timeoutMs = 20000) {
const decoder = new TextDecoder();
let text = "";
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const { value, done } = await reader.read();
if (done) break;
text += decoder.decode(value, { stream: true });
if (text.includes(`${tag} OK`) || text.includes(`${tag} NO`) || text.includes(`${tag} BAD`)) break;
}
return text;
}
async function writeCommand(writer: WritableStreamDefaultWriter<Uint8Array>, tag: string, command: string) {
await writer.write(new TextEncoder().encode(`${tag} ${command}\r\n`));
}
function parseHeaderBlock(block: string) {
const get = (name: string) => {
const match = block.match(new RegExp(`^${name}:\\s*([\\s\\S]*?)(?=\\r?\\n[A-Za-z-]+:|$)`, "im"));
return (match?.[1] || "").replace(/\r?\n\s+/g, " ").trim();
};
const uid = block.match(/UID\s+(\d+)/i)?.[1] || crypto.randomUUID();
const flags = block.match(/FLAGS\s+\(([^)]*)\)/i)?.[1] || "";
return {
id: uid,
from: get("From") || "Unknown sender",
subject: get("Subject") || "(no subject)",
date: get("Date"),
unread: !/\\Seen/i.test(flags),
};
}
function parseMessages(fetchResponse: string) {
return fetchResponse
.split(/\r?\n\)\r?\n(?=\* \d+ FETCH|[A-Z]\d+ OK|$)/g)
.filter((block) => /FETCH/i.test(block))
.map(parseHeaderBlock)
.filter((message) => message.from || message.subject)
.reverse();
}
function filterMessagesByOpenedAt(messages: ReturnType<typeof parseMessages>, openedSince?: string) {
if (!openedSince) return messages;
const openedAt = new Date(openedSince).getTime();
if (!Number.isFinite(openedAt)) return messages;
return messages.filter((message) => {
const receivedAt = new Date(message.date).getTime();
return Number.isFinite(receivedAt) && receivedAt >= openedAt;
});
}
async function fetchImapMessages(config: any, openedSince?: string) {
const conn = config.use_tls
? await Deno.connectTls({ hostname: config.imap_host, port: Number(config.imap_port || 993) })
: await Deno.connect({ hostname: config.imap_host, port: Number(config.imap_port || 143) });
const reader = conn.readable.getReader();
const writer = conn.writable.getWriter();
await readUntil(reader, "*");
await writeCommand(writer, "A1", `LOGIN ${quote(config.imap_username)} ${quote(config.imap_password)}`);
const login = await readUntil(reader, "A1");
if (!login.includes("A1 OK")) throw new Error("IMAP login failed. Check username, password, host, and port.");
await writeCommand(writer, "A2", "SELECT INBOX");
const selected = await readUntil(reader, "A2");
const exists = Number(selected.match(/\*\s+(\d+)\s+EXISTS/i)?.[1] || 0);
if (exists === 0) {
await writeCommand(writer, "A4", "LOGOUT");
await readUntil(reader, "A4", 3000).catch(() => "");
conn.close();
return [];
}
const start = Math.max(1, exists - 49);
await writeCommand(writer, "A3", `FETCH ${start}:${exists} (UID FLAGS BODY.PEEK[HEADER.FIELDS (FROM SUBJECT DATE)])`);
const fetched = await readUntil(reader, "A3");
await writeCommand(writer, "A4", "LOGOUT");
await readUntil(reader, "A4", 3000).catch(() => "");
conn.close();
return filterMessagesByOpenedAt(parseMessages(fetched), openedSince);
}
Deno.serve(async (req) => {
if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });
try {
const auth = await getAuthorizedClient(req);
if (auth.error) return auth.error;
const { client, callerId } = auth;
const body = await req.json().catch(() => ({}));
const action = String(body.action || "fetch_messages");
if (action === "list_configs") {
const { data, error } = await client.from("email_inbox_configs").select("id, display_name, email_address, imap_host, imap_port, imap_username, use_tls, is_active, created_at").order("created_at", { ascending: false });
if (error) throw error;
return jsonResponse({ success: true, configs: data || [] });
}
if (action === "save_config") {
const config = body.config || {};
if (!config.display_name || !config.email_address || !config.imap_host || !config.imap_username || !config.imap_password) {
return jsonResponse({ success: false, error: "Display name, email, IMAP host, username, and password are required." }, 400);
}
const payload = {
user_id: callerId,
display_name: String(config.display_name),
email_address: String(config.email_address).toLowerCase(),
imap_host: String(config.imap_host),
imap_port: Number(config.imap_port || 993),
imap_username: String(config.imap_username),
imap_password: String(config.imap_password),
use_tls: config.use_tls !== false,
is_active: config.is_active !== false,
};
const query = config.id
? client.from("email_inbox_configs").update(payload).eq("id", config.id).select("id").single()
: client.from("email_inbox_configs").insert(payload).select("id").single();
const { data, error } = await query;
if (error) throw error;
return jsonResponse({ success: true, id: data.id });
}
if (action === "delete_config") {
if (!body.id) return jsonResponse({ success: false, error: "Inbox config id is required." }, 400);
const { error } = await client.from("email_inbox_configs").delete().eq("id", body.id);
if (error) throw error;
return jsonResponse({ success: true });
}
const configId = body.config_id;
const query = client.from("email_inbox_configs").select("*").eq("is_active", true).order("created_at", { ascending: false }).limit(1);
const { data: configs, error } = configId ? await client.from("email_inbox_configs").select("*").eq("id", configId).limit(1) : await query;
if (error) throw error;
const config = configs?.[0];
if (!config) return jsonResponse({ success: true, messages: [], configs: [] });
const messages = await fetchImapMessages(config, typeof body.opened_since === "string" ? body.opened_since : undefined);
return jsonResponse({ success: true, messages, mailbox: { id: config.id, display_name: config.display_name, email_address: config.email_address } });
} catch (error) {
console.error("[fetch-imap-inbox]", error);
return jsonResponse({ success: false, error: error instanceof Error ? error.message : "Failed to fetch inbox" }, 500);
}
});