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.email}
+
+
+
+
+
+ {/* 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 (
+
+ )
+}
\ 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)