From c69947ceae61560ba71eae99b695230a15e81ab5 Mon Sep 17 00:00:00 2001 From: beilunyang <786220806@qq.com> Date: Tue, 17 Dec 2024 13:26:34 +0800 Subject: [PATCH] feat: profile page & webhook notification --- app/api/webhook/route.ts | 69 ++ app/api/webhook/test/route.ts | 39 ++ app/components/auth/sign-button.tsx | 8 +- app/components/emails/message-list.tsx | 4 +- app/components/profile/profile-card.tsx | 77 +++ app/components/profile/webhook-config.tsx | 155 +++++ app/components/ui/switch.tsx | 28 + app/components/ui/tooltip.tsx | 29 + app/config/index.ts | 9 + app/lib/schema.ts | 15 + app/lib/webhook.ts | 54 ++ app/profile/page.tsx | 27 + drizzle/0003_dashing_dust.sql | 9 + drizzle/meta/0003_snapshot.json | 432 +++++++++++++ drizzle/meta/_journal.json | 7 + middleware.ts | 3 +- package.json | 19 +- pnpm-lock.yaml | 755 ++++++++++++++-------- scripts/webhook-test-server.ts | 43 ++ workers/email-receiver.ts | 39 +- 20 files changed, 1533 insertions(+), 288 deletions(-) create mode 100644 app/api/webhook/route.ts create mode 100644 app/api/webhook/test/route.ts create mode 100644 app/components/profile/profile-card.tsx create mode 100644 app/components/profile/webhook-config.tsx create mode 100644 app/components/ui/switch.tsx create mode 100644 app/components/ui/tooltip.tsx create mode 100644 app/lib/webhook.ts create mode 100644 app/profile/page.tsx create mode 100644 drizzle/0003_dashing_dust.sql create mode 100644 drizzle/meta/0003_snapshot.json create mode 100644 scripts/webhook-test-server.ts diff --git a/app/api/webhook/route.ts b/app/api/webhook/route.ts new file mode 100644 index 0000000..d3bca5d --- /dev/null +++ b/app/api/webhook/route.ts @@ -0,0 +1,69 @@ +import { auth } from "@/lib/auth" +import { createDb } from "@/lib/db" +import { webhooks } from "@/lib/schema" +import { eq } from "drizzle-orm" +import { z } from "zod" + +export const runtime = "edge" + +const webhookSchema = z.object({ + url: z.string().url(), + enabled: z.boolean() +}) + +export async function GET() { + const session = await auth() + + const db = createDb() + const webhook = await db.query.webhooks.findFirst({ + where: eq(webhooks.userId, session!.user!.id!) + }) + + return Response.json(webhook || { enabled: false, url: "" }) +} + +export async function POST(request: Request) { + const session = await auth() + if (!session?.user?.id) { + return Response.json({ error: "Unauthorized" }, { status: 401 }) + } + + try { + const body = await request.json() + const { url, enabled } = webhookSchema.parse(body) + + const db = createDb() + const now = new Date() + + const existingWebhook = await db.query.webhooks.findFirst({ + where: eq(webhooks.userId, session.user.id) + }) + + if (existingWebhook) { + await db + .update(webhooks) + .set({ + url, + enabled, + updatedAt: now + }) + .where(eq(webhooks.userId, session.user.id)) + } else { + await db + .insert(webhooks) + .values({ + userId: session.user.id, + url, + enabled, + }) + } + + return Response.json({ success: true }) + } catch (error) { + console.error("Failed to save webhook:", error) + return Response.json( + { error: "Invalid request" }, + { status: 400 } + ) + } +} \ No newline at end of file diff --git a/app/api/webhook/test/route.ts b/app/api/webhook/test/route.ts new file mode 100644 index 0000000..66a6b1a --- /dev/null +++ b/app/api/webhook/test/route.ts @@ -0,0 +1,39 @@ +import { callWebhook } from "@/lib/webhook" +import { WEBHOOK_CONFIG } from "@/config" +import { z } from "zod" +import { EmailMessage } from "@/lib/webhook" + +export const runtime = "edge" + +const testSchema = z.object({ + url: z.string().url() +}) + +export async function POST(request: Request) { + try { + const body = await request.json() + const { url } = testSchema.parse(body) + + await callWebhook(url, { + event: WEBHOOK_CONFIG.EVENTS.NEW_MESSAGE, + data: { + emailId: "123456789", + messageId: '987654321', + fromAddress: "sender@example.com", + subject: "Test Email", + content: "This is a test email.", + html: "

This is a test email.

", + receivedAt: "2023-03-01T12:00:00Z", + toAddress: "recipient@example.com" + } as EmailMessage + }) + + return Response.json({ success: true }) + } catch (error) { + console.error("Failed to test webhook:", error) + return Response.json( + { error: "Failed to test webhook" }, + { status: 400 } + ) + } +} \ No newline at end of file diff --git a/app/components/auth/sign-button.tsx b/app/components/auth/sign-button.tsx index 09f7a26..8de3d39 100644 --- a/app/components/auth/sign-button.tsx +++ b/app/components/auth/sign-button.tsx @@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button" import Image from "next/image" import { signIn, signOut, useSession } from "next-auth/react" import { Github } from "lucide-react" +import Link from "next/link" export function SignButton() { const { data: session, status } = useSession() @@ -24,7 +25,10 @@ export function SignButton() { return (
-
+ {session.user.image && ( )} {session.user.name} -
+ diff --git a/app/components/emails/message-list.tsx b/app/components/emails/message-list.tsx index 7ab5054..61b6335 100644 --- a/app/components/emails/message-list.tsx +++ b/app/components/emails/message-list.tsx @@ -35,7 +35,7 @@ export function MessageList({ email, onMessageSelect, selectedMessageId }: Messa const [refreshing, setRefreshing] = useState(false) const [nextCursor, setNextCursor] = useState(null) const [loadingMore, setLoadingMore] = useState(false) - const pollTimeoutRef = useRef() + const pollTimeoutRef = useRef() const messagesRef = useRef([]) // 添加 ref 来追踪最新的消息列表 const [total, setTotal] = useState(0) @@ -85,7 +85,7 @@ export function MessageList({ email, onMessageSelect, selectedMessageId }: Messa } const startPolling = () => { - stopPolling() // 先清除之前的轮询 + stopPolling() pollTimeoutRef.current = setInterval(() => { if (!refreshing && !loadingMore) { fetchMessages() diff --git a/app/components/profile/profile-card.tsx b/app/components/profile/profile-card.tsx new file mode 100644 index 0000000..13f24c7 --- /dev/null +++ b/app/components/profile/profile-card.tsx @@ -0,0 +1,77 @@ +"use client" + +import { User } from "next-auth" +import Image from "next/image" +import { Button } from "@/components/ui/button" +import { signOut } from "next-auth/react" +import { Github, Mail, Settings } from "lucide-react" +import { useRouter } from "next/navigation" +import { WebhookConfig } from "./webhook-config" + +interface ProfileCardProps { + user: User +} + +export function ProfileCard({ user }: ProfileCardProps) { + const router = useRouter() + + return ( +
+ {/* 用户信息卡片 */} +
+
+
+ {user.image && ( + {user.name + )} +
+
+
+

{user.name}

+
+ + 已关联 +
+
+

+ {user.email} +

+
+
+
+ + {/* Webhook 配置卡片 */} +
+
+ +

Webhook 配置

+
+ +
+ + {/* 操作按钮 */} +
+ + +
+
+ ) +} \ No newline at end of file diff --git a/app/components/profile/webhook-config.tsx b/app/components/profile/webhook-config.tsx new file mode 100644 index 0000000..5df4e53 --- /dev/null +++ b/app/components/profile/webhook-config.tsx @@ -0,0 +1,155 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +"use client" + +import { useState, useEffect } from "react" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Switch } from "@/components/ui/switch" +import { useToast } from "@/components/ui/use-toast" +import { Loader2, Send } from "lucide-react" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" + +export function WebhookConfig() { + const [enabled, setEnabled] = useState(false) + const [url, setUrl] = useState("") + const [loading, setLoading] = useState(false) + const [testing, setTesting] = useState(false) + const { toast } = useToast() + + useEffect(() => { + fetch("/api/webhook") + .then(res => res.json() as Promise<{ enabled: boolean; url: string }>) + .then(data => { + setEnabled(data.enabled) + setUrl(data.url) + }) + .catch(console.error) + }, []) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!url) return + + setLoading(true) + try { + const res = await fetch("/api/webhook", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ url, enabled }) + }) + + if (!res.ok) throw new Error("Failed to save") + + toast({ + title: "保存成功", + description: "Webhook 配置已更新" + }) + } catch (_error) { + toast({ + title: "保存失败", + description: "请稍后重试", + variant: "destructive" + }) + } finally { + setLoading(false) + } + } + + const handleTest = async () => { + if (!url) return + + setTesting(true) + try { + const res = await fetch("/api/webhook/test", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ url }) + }) + + if (!res.ok) throw new Error("测试失败") + + toast({ + title: "测试成功", + description: "Webhook 调用成功,请检查目标服务器是否收到请求" + }) + } catch (_error) { + toast({ + title: "测试失败", + description: "请检查 URL 是否正确且可访问", + variant: "destructive" + }) + } finally { + setTesting(false) + } + } + + return ( +
+
+
+ +
+ 当收到新邮件时通知指定的 URL +
+
+ +
+ + {enabled && ( +
+ +
+ setUrl(e.target.value)} + type="url" + required + /> + + + + + + + +

发送测试消息到此 Webhook

+
+
+
+
+

+ 我们会向此 URL 发送 POST 请求,包含新邮件的相关信息 +

+
+ )} +
+ ) +} \ No newline at end of file diff --git a/app/components/ui/switch.tsx b/app/components/ui/switch.tsx new file mode 100644 index 0000000..bc046e4 --- /dev/null +++ b/app/components/ui/switch.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } \ No newline at end of file diff --git a/app/components/ui/tooltip.tsx b/app/components/ui/tooltip.tsx new file mode 100644 index 0000000..0cea6f2 --- /dev/null +++ b/app/components/ui/tooltip.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } \ No newline at end of file diff --git a/app/config/index.ts b/app/config/index.ts index b5a1404..ae0f60c 100644 --- a/app/config/index.ts +++ b/app/config/index.ts @@ -2,4 +2,13 @@ export const EMAIL_CONFIG = { MAX_ACTIVE_EMAILS: 30, // Maximum number of active emails POLL_INTERVAL: 10_000, // Polling interval in milliseconds DOMAIN: 'moemail.app', // Email domain +} as const + +export const WEBHOOK_CONFIG = { + MAX_RETRIES: 3, // Maximum retry count + TIMEOUT: 10_000, // Timeout time (milliseconds) + RETRY_DELAY: 1000, // Retry delay (milliseconds) + EVENTS: { + NEW_MESSAGE: 'new_message', + } } as const \ No newline at end of file diff --git a/app/lib/schema.ts b/app/lib/schema.ts index 09e0d9c..9852c1d 100644 --- a/app/lib/schema.ts +++ b/app/lib/schema.ts @@ -66,4 +66,19 @@ export const messages = sqliteTable("message", { receivedAt: integer("received_at", { mode: "timestamp_ms" }) .notNull() .$defaultFn(() => new Date()), +}) + +export const webhooks = sqliteTable('webhook', { + id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + userId: text('user_id') + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + url: text('url').notNull(), + enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), + updatedAt: integer('updated_at', { mode: 'timestamp_ms' }) + .notNull() + .$defaultFn(() => new Date()), }) \ No newline at end of file diff --git a/app/lib/webhook.ts b/app/lib/webhook.ts new file mode 100644 index 0000000..d57d99d --- /dev/null +++ b/app/lib/webhook.ts @@ -0,0 +1,54 @@ +import { WEBHOOK_CONFIG } from "@/config" + +export interface EmailMessage { + emailId: string + messageId: string + fromAddress: string + subject: string + content: string + html: string + receivedAt: string + toAddress: string +} + +export interface WebhookPayload { + event: typeof WEBHOOK_CONFIG.EVENTS[keyof typeof WEBHOOK_CONFIG.EVENTS] + data: EmailMessage +} + +export async function callWebhook(url: string, payload: WebhookPayload) { + let lastError: Error | null = null + + for (let i = 0; i < WEBHOOK_CONFIG.MAX_RETRIES; i++) { + try { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), WEBHOOK_CONFIG.TIMEOUT) + + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Webhook-Event": payload.event, + }, + body: JSON.stringify(payload.data), + signal: controller.signal, + }) + + clearTimeout(timeoutId) + + if (response.ok) { + return true + } + + lastError = new Error(`HTTP error! status: ${response.status}`) + } catch (error) { + lastError = error as Error + + if (i < WEBHOOK_CONFIG.MAX_RETRIES - 1) { + await new Promise(resolve => setTimeout(resolve, WEBHOOK_CONFIG.RETRY_DELAY)) + } + } + } + + throw lastError +} \ No newline at end of file diff --git a/app/profile/page.tsx b/app/profile/page.tsx new file mode 100644 index 0000000..260c449 --- /dev/null +++ b/app/profile/page.tsx @@ -0,0 +1,27 @@ +import { Header } from "@/components/layout/header" +import { ProfileCard } from "@/components/profile/profile-card" +import { auth } from "@/lib/auth" +import { redirect } from "next/navigation" + +export const runtime = "edge" + +export default async function ProfilePage() { + const session = await auth() + + if (!session?.user) { + redirect("/") + } + + return ( +
+
+
+
+
+ +
+
+
+
+ ) +} \ No newline at end of file diff --git a/drizzle/0003_dashing_dust.sql b/drizzle/0003_dashing_dust.sql new file mode 100644 index 0000000..a934569 --- /dev/null +++ b/drizzle/0003_dashing_dust.sql @@ -0,0 +1,9 @@ +CREATE TABLE `webhook` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text NOT NULL, + `url` text NOT NULL, + `enabled` integer DEFAULT true NOT NULL, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..ab5702a --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,432 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "c65780fc-6545-438d-a3b6-fbdafa9b1276", + "prevId": "9f9802ad-fc03-4e1a-847e-8a73866a9f52", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email": { + "name": "email", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email_address_unique": { + "name": "email_address_unique", + "columns": [ + "address" + ], + "isUnique": true + } + }, + "foreignKeys": { + "email_userId_user_id_fk": { + "name": "email_userId_user_id_fk", + "tableFrom": "email", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message": { + "name": "message", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_address": { + "name": "from_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "html": { + "name": "html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_emailId_email_id_fk": { + "name": "message_emailId_email_id_fk", + "tableFrom": "message", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webhook": { + "name": "webhook", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webhook_user_id_user_id_fk": { + "name": "webhook_user_id_user_id_fk", + "tableFrom": "webhook", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 8efa18a..e87a412 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1734184527968, "tag": "0002_military_cobalt_man", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1734454698466, + "tag": "0003_dashing_dust", + "breakpoints": true } ] } \ No newline at end of file diff --git a/middleware.ts b/middleware.ts index 6a0b9de..889310d 100644 --- a/middleware.ts +++ b/middleware.ts @@ -4,7 +4,7 @@ import { NextResponse } from "next/server" export async function middleware() { const session = await auth() - if (!session) { + if (!session?.user) { return NextResponse.json( { error: "Unauthorized" }, { status: 401 } @@ -17,5 +17,6 @@ export async function middleware() { export const config = { matcher: [ "/api/emails/:path*", + "/api/webhook/:path*", ] } \ No newline at end of file diff --git a/package.json b/package.json index 3c3352a..7924342 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,9 @@ "start": "next start", "lint": "next lint", "build:pages": "npx @cloudflare/next-on-pages", - "db:migrate-local": "tsx scripts/migrate.ts local", - "db:migrate-remote": "tsx scripts/migrate.ts remote", + "db:migrate-local": "bun run scripts/migrate.ts local", + "db:migrate-remote": "bun run scripts/migrate.ts remote", + "webhook-test-server": "bun run scripts/webhook-test-server.ts", "generate-test-data": "wrangler dev scripts/generate-test-data.ts", "dev:cleanup": "wrangler dev --config wrangler.cleanup.toml --test-scheduled", "test:cleanup": "curl http://localhost:8787/__scheduled", @@ -22,10 +23,12 @@ "@auth/drizzle-adapter": "^1.7.4", "@cloudflare/next-on-pages": "^1.13.6", "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-radio-group": "^1.2.1", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-toast": "^1.1.5", + "@radix-ui/react-tooltip": "^1.1.6", "@tailwindcss/typography": "^0.5.15", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", @@ -40,14 +43,18 @@ "react": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106", "tailwind-merge": "^2.2.2", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zod": "^3.24.1" }, "devDependencies": { "@cloudflare/workers-types": "^4.20241127.0", + "@iarna/toml": "^3.0.0", + "@types/bun": "^1.1.14", "@types/next-pwa": "^5.6.9", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "bun": "^1.1.39", "drizzle-kit": "^0.28.1", "eslint": "^8", "eslint-config-next": "15.0.3", @@ -55,8 +62,6 @@ "tailwindcss": "^3.4.1", "typescript": "^5", "vercel": "39.1.1", - "wrangler": "^3.91.0", - "@iarna/toml": "^3.0.0", - "tsx": "^4.7.1" + "wrangler": "^3.91.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72989b6..6677ff8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,7 +18,7 @@ importers: specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@radix-ui/react-label': - specifier: ^2.0.2 + specifier: ^2.1.0 version: 2.1.0(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@radix-ui/react-radio-group': specifier: ^1.2.1 @@ -26,9 +26,15 @@ importers: '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-switch': + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@radix-ui/react-toast': specifier: ^1.1.5 version: 1.2.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-tooltip': + specifier: ^1.1.6 + version: 1.1.6(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) '@tailwindcss/typography': specifier: ^0.5.15 version: 0.5.15(tailwindcss@3.4.16(ts-node@10.9.1(@types/node@20.17.9)(typescript@5.7.2))) @@ -40,7 +46,7 @@ importers: version: 2.1.1 drizzle-orm: specifier: ^0.36.4 - version: 0.36.4(@cloudflare/workers-types@4.20241205.0)(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + version: 0.36.4(@cloudflare/workers-types@4.20241205.0)(@types/react@18.3.14)(bun-types@1.1.37)(react@19.0.0-rc-66855b96-20241106) lucide-react: specifier: ^0.363.0 version: 0.363.0(react@19.0.0-rc-66855b96-20241106) @@ -74,6 +80,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.16(ts-node@10.9.1(@types/node@20.17.9)(typescript@5.7.2))) + zod: + specifier: ^3.24.1 + version: 3.24.1 devDependencies: '@cloudflare/workers-types': specifier: ^4.20241127.0 @@ -81,6 +90,9 @@ importers: '@iarna/toml': specifier: ^3.0.0 version: 3.0.0 + '@types/bun': + specifier: ^1.1.14 + version: 1.1.14 '@types/next-pwa': specifier: ^5.6.9 version: 5.6.9(@babel/core@7.26.0)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) @@ -93,6 +105,9 @@ importers: '@types/react-dom': specifier: ^18 version: 18.3.2 + bun: + specifier: ^1.1.39 + version: 1.1.39 drizzle-kit: specifier: ^0.28.1 version: 0.28.1 @@ -108,9 +123,6 @@ importers: tailwindcss: specifier: ^3.4.1 version: 3.4.16(ts-node@10.9.1(@types/node@20.17.9)(typescript@5.7.2)) - tsx: - specifier: ^4.7.1 - version: 4.19.2 typescript: specifier: ^5 version: 5.7.2 @@ -757,12 +769,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/android-arm64@0.17.19': resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} engines: {node: '>=12'} @@ -781,12 +787,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm@0.15.18': resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -811,12 +811,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-x64@0.17.19': resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} engines: {node: '>=12'} @@ -835,12 +829,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/darwin-arm64@0.17.19': resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} engines: {node: '>=12'} @@ -859,12 +847,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-x64@0.17.19': resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} engines: {node: '>=12'} @@ -883,12 +865,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/freebsd-arm64@0.17.19': resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} engines: {node: '>=12'} @@ -907,12 +883,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-x64@0.17.19': resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} engines: {node: '>=12'} @@ -931,12 +901,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/linux-arm64@0.17.19': resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} engines: {node: '>=12'} @@ -955,12 +919,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm@0.17.19': resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} engines: {node: '>=12'} @@ -979,12 +937,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-ia32@0.17.19': resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} engines: {node: '>=12'} @@ -1003,12 +955,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-loong64@0.15.18': resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} @@ -1033,12 +979,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-mips64el@0.17.19': resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} engines: {node: '>=12'} @@ -1057,12 +997,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-ppc64@0.17.19': resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} engines: {node: '>=12'} @@ -1081,12 +1015,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-riscv64@0.17.19': resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} engines: {node: '>=12'} @@ -1105,12 +1033,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-s390x@0.17.19': resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} engines: {node: '>=12'} @@ -1129,12 +1051,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-x64@0.17.19': resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} engines: {node: '>=12'} @@ -1153,12 +1069,6 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/netbsd-x64@0.17.19': resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} engines: {node: '>=12'} @@ -1177,18 +1087,6 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-x64@0.17.19': resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} engines: {node: '>=12'} @@ -1207,12 +1105,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/sunos-x64@0.17.19': resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} engines: {node: '>=12'} @@ -1231,12 +1123,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/win32-arm64@0.17.19': resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} engines: {node: '>=12'} @@ -1255,12 +1141,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-ia32@0.17.19': resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} engines: {node: '>=12'} @@ -1279,12 +1159,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-x64@0.17.19': resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} engines: {node: '>=12'} @@ -1303,12 +1177,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1331,6 +1199,21 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -1630,6 +1513,61 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@oven/bun-darwin-aarch64@1.1.39': + resolution: {integrity: sha512-YreKvFEYgXsNK7FVC5ym4B3ZvaKM/2lhxMnp5E5TyG8zmAFinQzTxZ0cjaDAr3dkVMxMQJqdQil/1fXCEQWAYA==} + cpu: [arm64] + os: [darwin] + + '@oven/bun-darwin-x64-baseline@1.1.39': + resolution: {integrity: sha512-HfVDH6N84nkjJd/plkTKu+v9BiWZ+nex3JFGC38MclS4g9dQ5syQYssEJo6XGWbl8LAqPY65f2yRvlu1pOP3Kg==} + cpu: [x64] + os: [darwin] + + '@oven/bun-darwin-x64@1.1.39': + resolution: {integrity: sha512-8Ai5YUZu7TGS5I/MuBh/hK8BWsiknRao9PJ7o22UDqhewhQkAFFysGd9rsOf8xaKU5RA4x4UdMKXl2kv+D8ZgA==} + cpu: [x64] + os: [darwin] + + '@oven/bun-linux-aarch64-musl@1.1.39': + resolution: {integrity: sha512-qhTDb2DmocHTryFFWPtoRCr1RHb+tdX4KrnVzEpLve2H2t1WcjrqfsZQWNXjwYvU8pqoRvPY4WDTtfgO02mYqQ==} + cpu: [aarch64] + os: [linux] + + '@oven/bun-linux-aarch64@1.1.39': + resolution: {integrity: sha512-s4KxVmu9vxsCKfKVZoYNUzOpvjIZ9XFyFeGH5ewhRYC6HozYHm3C658uv3ZmkQPwh0jofp9a8tNTeF2g06bV3A==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-x64-baseline@1.1.39': + resolution: {integrity: sha512-keDDYbezEmMAw5rmK5d4RjFPoggf4oN8au2bFnRfuBy45sEbRFJd11m9N1Am3jhWNVcYtJ2scYZDT4TsFw5dbw==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl-baseline@1.1.39': + resolution: {integrity: sha512-J90Gd1MXjpJ9y3MJRqXTj1l0Bet5Rel5Ng4rFLe6wUL4/Pje+2+x8O5/t1PEycO6eNC3AJtoJz5m/GJSAse6rQ==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl@1.1.39': + resolution: {integrity: sha512-yIZvzW6/w1CzwXaHuw/BubqJZ7Uz+mlxF/IvTTLRe1FaTZ+5vlWGfvq9S6EPzrI2TfUomCY2XndmY5zcfRcgmg==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64@1.1.39': + resolution: {integrity: sha512-diWeKuwcrGyEzEne0Z638XHkazupN4jJxZlAddbnep1pOIhqfxtfa8v1c0PFaaAWCQdFoAeMZWsoYt4QA4qz1Q==} + cpu: [x64] + os: [linux] + + '@oven/bun-windows-x64-baseline@1.1.39': + resolution: {integrity: sha512-yMF0TjDMD82TC+/oCkiW/NiIjKNXXUJOzwXQ3solKr8eRA2lMFtd0oCMuB6RCDIsxqg8RBCCogO80qhKTTlAjA==} + cpu: [x64] + os: [win32] + + '@oven/bun-windows-x64@1.1.39': + resolution: {integrity: sha512-m66FR24GV73kBBrozduPwPDnD1nrGbECqhZ4TdRYVPNosqR28GHHUD6x65pzkiBAOEMY+k9DVBndeNIepyAOxw==} + cpu: [x64] + os: [win32] + '@panva/hkdf@1.2.1': resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} @@ -1640,6 +1578,22 @@ packages: '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + '@radix-ui/primitive@1.1.1': + resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + + '@radix-ui/react-arrow@1.1.1': + resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.0': resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} peerDependencies: @@ -1662,6 +1616,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -1715,6 +1678,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dismissable-layer@1.1.3': + resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: @@ -1759,6 +1735,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popper@1.2.1': + resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.2': resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} peerDependencies: @@ -1772,6 +1761,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.1.3': + resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.1.1': resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==} peerDependencies: @@ -1785,6 +1787,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.0': resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} peerDependencies: @@ -1798,6 +1813,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.0.1': + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-radio-group@1.2.1': resolution: {integrity: sha512-kdbv54g4vfRjja9DNWPMxKvXblzqbpEC8kspEkZ6dVP7kQksGCn+iZHkcCz2nb00+lPdRvxrqy4WrvvV1cNqrQ==} peerDependencies: @@ -1833,6 +1861,28 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.1.1': + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.1.2': + resolution: {integrity: sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toast@1.2.2': resolution: {integrity: sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==} peerDependencies: @@ -1846,6 +1896,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tooltip@1.1.6': + resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -1891,6 +1954,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-size@1.1.0': resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} peerDependencies: @@ -1913,6 +1985,22 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.1.1': + resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -1990,6 +2078,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/bun@1.1.14': + resolution: {integrity: sha512-opVYiFGtO2af0dnWBdZWlioLBoxSdDO5qokaazLhq8XQtGZbY4pY3/JxY8Zdf/hEwGubbp7ErZXoN1+h2yesxA==} + '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} @@ -2026,6 +2117,9 @@ packages: '@types/node@16.18.11': resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==} + '@types/node@20.12.14': + resolution: {integrity: sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==} + '@types/node@20.17.9': resolution: {integrity: sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==} @@ -2044,6 +2138,9 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/ws@8.5.13': + resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} + '@typescript-eslint/eslint-plugin@8.17.0': resolution: {integrity: sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2454,6 +2551,14 @@ packages: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} + bun-types@1.1.37: + resolution: {integrity: sha512-C65lv6eBr3LPJWFZ2gswyrGZ82ljnH8flVE03xeXxKhi2ZGtFiO4isRKTKnitbSqtRAcaqYSR6djt1whI66AbA==} + + bun@1.1.39: + resolution: {integrity: sha512-9BMRchlxJqEc7B+t64hLLXXLGoZQiWOD8rFoyd4hTJTdoCewBH2pwC8TSEcZ/nl+UzBggYxcoZ5Aw585WFbLiA==} + os: [darwin, linux, win32] + hasBin: true + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -3172,11 +3277,6 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.23.1: - resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} - engines: {node: '>=18'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -4990,11 +5090,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.19.2: - resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} - engines: {node: '>=18.0.0'} - hasBin: true - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -5042,6 +5137,9 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -5340,8 +5438,8 @@ packages: youch@3.3.4: resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==} - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} snapshots: @@ -6069,7 +6167,7 @@ snapshots: '@cloudflare/workers-shared@0.10.0': dependencies: mime: 3.0.0 - zod: 3.23.8 + zod: 3.24.1 '@cloudflare/workers-types@4.20241205.0': {} @@ -6119,9 +6217,6 @@ snapshots: '@esbuild/aix-ppc64@0.19.12': optional: true - '@esbuild/aix-ppc64@0.23.1': - optional: true - '@esbuild/android-arm64@0.17.19': optional: true @@ -6131,9 +6226,6 @@ snapshots: '@esbuild/android-arm64@0.19.12': optional: true - '@esbuild/android-arm64@0.23.1': - optional: true - '@esbuild/android-arm@0.15.18': optional: true @@ -6146,9 +6238,6 @@ snapshots: '@esbuild/android-arm@0.19.12': optional: true - '@esbuild/android-arm@0.23.1': - optional: true - '@esbuild/android-x64@0.17.19': optional: true @@ -6158,9 +6247,6 @@ snapshots: '@esbuild/android-x64@0.19.12': optional: true - '@esbuild/android-x64@0.23.1': - optional: true - '@esbuild/darwin-arm64@0.17.19': optional: true @@ -6170,9 +6256,6 @@ snapshots: '@esbuild/darwin-arm64@0.19.12': optional: true - '@esbuild/darwin-arm64@0.23.1': - optional: true - '@esbuild/darwin-x64@0.17.19': optional: true @@ -6182,9 +6265,6 @@ snapshots: '@esbuild/darwin-x64@0.19.12': optional: true - '@esbuild/darwin-x64@0.23.1': - optional: true - '@esbuild/freebsd-arm64@0.17.19': optional: true @@ -6194,9 +6274,6 @@ snapshots: '@esbuild/freebsd-arm64@0.19.12': optional: true - '@esbuild/freebsd-arm64@0.23.1': - optional: true - '@esbuild/freebsd-x64@0.17.19': optional: true @@ -6206,9 +6283,6 @@ snapshots: '@esbuild/freebsd-x64@0.19.12': optional: true - '@esbuild/freebsd-x64@0.23.1': - optional: true - '@esbuild/linux-arm64@0.17.19': optional: true @@ -6218,9 +6292,6 @@ snapshots: '@esbuild/linux-arm64@0.19.12': optional: true - '@esbuild/linux-arm64@0.23.1': - optional: true - '@esbuild/linux-arm@0.17.19': optional: true @@ -6230,9 +6301,6 @@ snapshots: '@esbuild/linux-arm@0.19.12': optional: true - '@esbuild/linux-arm@0.23.1': - optional: true - '@esbuild/linux-ia32@0.17.19': optional: true @@ -6242,9 +6310,6 @@ snapshots: '@esbuild/linux-ia32@0.19.12': optional: true - '@esbuild/linux-ia32@0.23.1': - optional: true - '@esbuild/linux-loong64@0.15.18': optional: true @@ -6257,9 +6322,6 @@ snapshots: '@esbuild/linux-loong64@0.19.12': optional: true - '@esbuild/linux-loong64@0.23.1': - optional: true - '@esbuild/linux-mips64el@0.17.19': optional: true @@ -6269,9 +6331,6 @@ snapshots: '@esbuild/linux-mips64el@0.19.12': optional: true - '@esbuild/linux-mips64el@0.23.1': - optional: true - '@esbuild/linux-ppc64@0.17.19': optional: true @@ -6281,9 +6340,6 @@ snapshots: '@esbuild/linux-ppc64@0.19.12': optional: true - '@esbuild/linux-ppc64@0.23.1': - optional: true - '@esbuild/linux-riscv64@0.17.19': optional: true @@ -6293,9 +6349,6 @@ snapshots: '@esbuild/linux-riscv64@0.19.12': optional: true - '@esbuild/linux-riscv64@0.23.1': - optional: true - '@esbuild/linux-s390x@0.17.19': optional: true @@ -6305,9 +6358,6 @@ snapshots: '@esbuild/linux-s390x@0.19.12': optional: true - '@esbuild/linux-s390x@0.23.1': - optional: true - '@esbuild/linux-x64@0.17.19': optional: true @@ -6317,9 +6367,6 @@ snapshots: '@esbuild/linux-x64@0.19.12': optional: true - '@esbuild/linux-x64@0.23.1': - optional: true - '@esbuild/netbsd-x64@0.17.19': optional: true @@ -6329,12 +6376,6 @@ snapshots: '@esbuild/netbsd-x64@0.19.12': optional: true - '@esbuild/netbsd-x64@0.23.1': - optional: true - - '@esbuild/openbsd-arm64@0.23.1': - optional: true - '@esbuild/openbsd-x64@0.17.19': optional: true @@ -6344,9 +6385,6 @@ snapshots: '@esbuild/openbsd-x64@0.19.12': optional: true - '@esbuild/openbsd-x64@0.23.1': - optional: true - '@esbuild/sunos-x64@0.17.19': optional: true @@ -6356,9 +6394,6 @@ snapshots: '@esbuild/sunos-x64@0.19.12': optional: true - '@esbuild/sunos-x64@0.23.1': - optional: true - '@esbuild/win32-arm64@0.17.19': optional: true @@ -6368,9 +6403,6 @@ snapshots: '@esbuild/win32-arm64@0.19.12': optional: true - '@esbuild/win32-arm64@0.23.1': - optional: true - '@esbuild/win32-ia32@0.17.19': optional: true @@ -6380,9 +6412,6 @@ snapshots: '@esbuild/win32-ia32@0.19.12': optional: true - '@esbuild/win32-ia32@0.23.1': - optional: true - '@esbuild/win32-x64@0.17.19': optional: true @@ -6392,9 +6421,6 @@ snapshots: '@esbuild/win32-x64@0.19.12': optional: true - '@esbuild/win32-x64@0.23.1': - optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -6420,6 +6446,23 @@ snapshots: '@fastify/busboy@2.1.1': {} + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@floating-ui/dom': 1.6.12 + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + + '@floating-ui/utils@0.2.8': {} + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -6633,6 +6676,39 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@oven/bun-darwin-aarch64@1.1.39': + optional: true + + '@oven/bun-darwin-x64-baseline@1.1.39': + optional: true + + '@oven/bun-darwin-x64@1.1.39': + optional: true + + '@oven/bun-linux-aarch64-musl@1.1.39': + optional: true + + '@oven/bun-linux-aarch64@1.1.39': + optional: true + + '@oven/bun-linux-x64-baseline@1.1.39': + optional: true + + '@oven/bun-linux-x64-musl-baseline@1.1.39': + optional: true + + '@oven/bun-linux-x64-musl@1.1.39': + optional: true + + '@oven/bun-linux-x64@1.1.39': + optional: true + + '@oven/bun-windows-x64-baseline@1.1.39': + optional: true + + '@oven/bun-windows-x64@1.1.39': + optional: true + '@panva/hkdf@1.2.1': {} '@pkgjs/parseargs@0.11.0': @@ -6640,6 +6716,17 @@ snapshots: '@radix-ui/primitive@1.1.0': {} + '@radix-ui/primitive@1.1.1': {} + + '@radix-ui/react-arrow@1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) @@ -6658,6 +6745,12 @@ snapshots: optionalDependencies: '@types/react': 18.3.14 + '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106)': + dependencies: + react: 19.0.0-rc-66855b96-20241106 + optionalDependencies: + '@types/react': 18.3.14 + '@radix-ui/react-context@1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106)': dependencies: react: 19.0.0-rc-66855b96-20241106 @@ -6711,6 +6804,19 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106)': dependencies: react: 19.0.0-rc-66855b96-20241106 @@ -6744,6 +6850,24 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-popper@1.2.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-arrow': 1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/rect': 1.1.0 + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-portal@1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) @@ -6754,6 +6878,16 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-presence@1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) @@ -6764,6 +6898,16 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-primitive@2.0.0(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) @@ -6773,6 +6917,15 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-radio-group@1.2.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -6815,6 +6968,28 @@ snapshots: optionalDependencies: '@types/react': 18.3.14 + '@radix-ui/react-slot@1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-switch@1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-toast@1.2.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/primitive': 1.1.0 @@ -6835,6 +7010,26 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-tooltip@1.1.6(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) + '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106)': dependencies: react: 19.0.0-rc-66855b96-20241106 @@ -6867,6 +7062,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.14 + '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/rect': 1.1.0 + react: 19.0.0-rc-66855b96-20241106 + optionalDependencies: + '@types/react': 18.3.14 + '@radix-ui/react-use-size@1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106) @@ -6883,6 +7085,17 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106) + react: 19.0.0-rc-66855b96-20241106 + react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + + '@radix-ui/rect@1.1.0': {} + '@rollup/plugin-babel@5.3.1(@babel/core@7.26.0)(rollup@2.79.2)': dependencies: '@babel/core': 7.26.0 @@ -6968,6 +7181,10 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/bun@1.1.14': + dependencies: + bun-types: 1.1.37 + '@types/cookie@0.6.0': {} '@types/eslint-scope@3.7.7': @@ -7018,6 +7235,10 @@ snapshots: '@types/node@16.18.11': {} + '@types/node@20.12.14': + dependencies: + undici-types: 5.26.5 + '@types/node@20.17.9': dependencies: undici-types: 6.19.8 @@ -7039,6 +7260,10 @@ snapshots: '@types/trusted-types@2.0.7': {} + '@types/ws@8.5.13': + dependencies: + '@types/node': 20.17.9 + '@typescript-eslint/eslint-plugin@8.17.0(@typescript-eslint/parser@8.17.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -7609,6 +7834,25 @@ snapshots: builtin-modules@3.3.0: {} + bun-types@1.1.37: + dependencies: + '@types/node': 20.12.14 + '@types/ws': 8.5.13 + + bun@1.1.39: + optionalDependencies: + '@oven/bun-darwin-aarch64': 1.1.39 + '@oven/bun-darwin-x64': 1.1.39 + '@oven/bun-darwin-x64-baseline': 1.1.39 + '@oven/bun-linux-aarch64': 1.1.39 + '@oven/bun-linux-aarch64-musl': 1.1.39 + '@oven/bun-linux-x64': 1.1.39 + '@oven/bun-linux-x64-baseline': 1.1.39 + '@oven/bun-linux-x64-musl': 1.1.39 + '@oven/bun-linux-x64-musl-baseline': 1.1.39 + '@oven/bun-windows-x64': 1.1.39 + '@oven/bun-windows-x64-baseline': 1.1.39 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -7853,10 +8097,11 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.36.4(@cloudflare/workers-types@4.20241205.0)(@types/react@18.3.14)(react@19.0.0-rc-66855b96-20241106): + drizzle-orm@0.36.4(@cloudflare/workers-types@4.20241205.0)(@types/react@18.3.14)(bun-types@1.1.37)(react@19.0.0-rc-66855b96-20241106): optionalDependencies: '@cloudflare/workers-types': 4.20241205.0 '@types/react': 18.3.14 + bun-types: 1.1.37 react: 19.0.0-rc-66855b96-20241106 dunder-proto@1.0.0: @@ -8250,33 +8495,6 @@ snapshots: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 - esbuild@0.23.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 - escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -9167,7 +9385,7 @@ snapshots: workerd: 1.20241205.0 ws: 8.18.0 youch: 3.3.4 - zod: 3.23.8 + zod: 3.24.1 transitivePeerDependencies: - bufferutil - supports-color @@ -10238,13 +10456,6 @@ snapshots: tslib@2.8.1: {} - tsx@4.19.2: - dependencies: - esbuild: 0.23.1 - get-tsconfig: 4.8.1 - optionalDependencies: - fsevents: 2.3.3 - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -10301,6 +10512,8 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.0 + undici-types@5.26.5: {} + undici-types@6.19.8: {} undici@5.28.4: @@ -10718,4 +10931,4 @@ snapshots: mustache: 4.2.0 stacktracey: 2.1.8 - zod@3.23.8: {} + zod@3.24.1: {} diff --git a/scripts/webhook-test-server.ts b/scripts/webhook-test-server.ts new file mode 100644 index 0000000..77d0c41 --- /dev/null +++ b/scripts/webhook-test-server.ts @@ -0,0 +1,43 @@ +import { EmailMessage } from "../app/lib/webhook" +import Bun from 'bun' + +const server = Bun.serve({ + port: 3001, + async fetch(request: Request) { + if (request.method !== "POST") { + return new Response("Method not allowed", { status: 405 }) + } + + try { + const data = await request.json() as EmailMessage + + console.log("\n=== Webhook Received ===") + console.log("Event:", request.headers.get("X-Webhook-Event")) + console.log("Received At:", data.receivedAt) + console.log("\nEmail Details:") + console.log("From:", data.fromAddress) + console.log("To:", data.toAddress) + console.log("Subject:", data.subject) + console.log("Raw Content:", data.content) + console.log("HTML Content:", data.html) + console.log("Message ID:", data.messageId) + console.log("Email ID:", data.emailId) + console.log("=== End ===\n") + + return new Response(JSON.stringify({ success: true }), { + headers: { "Content-Type": "application/json" } + }) + } catch (error) { + console.error("Error processing webhook:", error) + return new Response( + JSON.stringify({ error: "Invalid request" }), + { + status: 400, + headers: { "Content-Type": "application/json" } + } + ) + } + }, +}) + +console.log(`Webhook test server listening on http://localhost:${server.port}`) \ No newline at end of file diff --git a/workers/email-receiver.ts b/workers/email-receiver.ts index d527989..5a75a00 100644 --- a/workers/email-receiver.ts +++ b/workers/email-receiver.ts @@ -1,12 +1,13 @@ import { Env } from '../types' import { drizzle } from 'drizzle-orm/d1' -import { messages, emails } from '../app/lib/schema' +import { messages, emails, webhooks } from '../app/lib/schema' import { eq } from 'drizzle-orm' import PostalMime from 'postal-mime' - +import { WEBHOOK_CONFIG } from '../app/config' +import { EmailMessage } from '../app/lib/webhook' const handleEmail = async (message: ForwardableEmailMessage, env: Env) => { - const db = drizzle(env.DB, { schema: { messages, emails } }) + const db = drizzle(env.DB, { schema: { messages, emails, webhooks } }) const parsedMessage = await PostalMime.parse(message.raw) @@ -22,15 +23,43 @@ const handleEmail = async (message: ForwardableEmailMessage, env: Env) => { return } - await db.insert(messages).values({ - // @ts-expect-error to fix + const savedMessage = await db.insert(messages).values({ + // @ts-expect-error "ignore" emailId: targetEmail.id, fromAddress: message.from, subject: parsedMessage.subject, content: parsedMessage.text, html: parsedMessage.html || null, + }).returning().get() + + const webhook = await db.query.webhooks.findFirst({ + where: eq(webhooks.userId, targetEmail!.userId!) }) + if (webhook?.enabled) { + try { + await fetch(webhook.url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Webhook-Event': WEBHOOK_CONFIG.EVENTS.NEW_MESSAGE + }, + body: JSON.stringify({ + emailId: targetEmail.id, + messageId: savedMessage.id, + fromAddress: savedMessage.fromAddress, + subject: savedMessage.subject, + content: savedMessage.content, + html: savedMessage.html, + receivedAt: savedMessage.receivedAt.toISOString(), + toAddress: targetEmail.address + } as EmailMessage) + }) + } catch (error) { + console.error('Failed to send webhook:', error) + } + } + console.log(`Email processed: ${parsedMessage.subject}`) } catch (error) { console.error('Failed to process email:', error)